Spring 高级依赖注入 —— Bean的延迟依赖查找功能,ObjectFactory 和 ObjectProvider

这篇具有很好参考价值的文章主要介绍了Spring 高级依赖注入 —— Bean的延迟依赖查找功能,ObjectFactory 和 ObjectProvider。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

介绍

首先明确一下什么是延迟查找,一般来说通过@Autowired注解注入一个具体对象的方式是属于实时依赖查找,注入的前提是要保证对象已经被创建。而使用延迟查找的方式是我可以不注入对象的本身,而是通过注入一个代理对象,在需要用到的地方再去取其中真实的对象来使用 ,ObjectFactory提供的就是这样一种能力。

先来看一下ObjectFactoryObjectProvider的源码

@FunctionalInterface
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {

    T getObject(Object... args) throws BeansException;

    
    @Nullable
    T getIfAvailable() throws BeansException;
    
    default T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
        T dependency = getIfAvailable();
        return (dependency != null ? dependency : defaultSupplier.get());
    }

    default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
        T dependency = getIfAvailable();
        if (dependency != null) {
            dependencyConsumer.accept(dependency);
        }
    }

    @Nullable
    T getIfUnique() throws BeansException;

    
    default T getIfUnique(Supplier<T> defaultSupplier) throws BeansException {
        T dependency = getIfUnique();
        return (dependency != null ? dependency : defaultSupplier.get());
    }

    default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
        T dependency = getIfUnique();
        if (dependency != null) {
            dependencyConsumer.accept(dependency);
        }
    }

    @Override
    default Iterator<T> iterator() {
        return stream().iterator();
    }


    default Stream<T> stream() {
        throw new UnsupportedOperationException("Multi element access not supported");
    }

    
    default Stream<T> orderedStream() {
        throw new UnsupportedOperationException("Ordered element access not supported");
    }

}

通过源码可以看出ObjectFactory是一个顶层接口,内部只提供了直接获取对象的功能,如果对象在容器中不存则直接抛出NoSuchBeanDefinitionException异常。ObjectProvider提供了更强大的功能,支持迭代,stream 流等特性,通过getIfAvailable方法还可以避免NoSuchBeanDefinitionException 异常

用法演示

下面通过代码来演示ObjectFactoryObjectProvider的使用方式

public class ObjectFactoryLazyLookupDemo {

    // DefaultListableBeanFactory$DependencyObjectProvider
    @Autowired
    private ObjectFactory<User> objectFactory;
    
    // DefaultListableBeanFactory$DependencyObjectProvider
    @Autowired
    private ObjectProvider<User> objectProvider;


    public static void main(String[] args) {
        // 创建应用上下文
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        
        // 注册当前类为配置类
        applicationContext.register(ObjectFactoryLazyLookupDemo.class);
        
        // 启动应用上下文
        applicationContext.refresh();

        // 获取当前类的实例
        ObjectFactoryLazyLookupDemo lazyLookupDemo = applicationContext.getBean(ObjectFactoryLazyLookupDemo.class);

        // 获取通过依赖注入的ObjectFactory和ObjectProvider对象
        ObjectFactory<User> objectFactory = lazyLookupDemo.objectFactory;
        ObjectProvider<User> objectProvider = lazyLookupDemo.objectProvider;

        // true
        System.out.println(objectFactory.getClass() == objectProvider.getClass());
        // true
        System.out.println(objectFactory.getObject() == objectProvider.getObject());

        // User{id=1, name='lazy lookup'}
        System.out.println(objectFactory.getObject());
    }

    @Bean
    private User user() {
        User user = new User();
        user.setId(1L);
        user.setName("lazy lookup");
        return user;
    }
}

在上述代码中,创建了一个User对象,在注入的时候并没有直接注入对象本身,而是分别了注入了ObjectFactory<User>ObjectProvider<User>对象,在真正使用时才通过objectFactory.getObject()去获取真实对象,在注入ObjectFactoryObjectProvider时并没有触发依赖查找的动作,这种方式就是典型的延迟依赖查找。通过两种方式获取的User对象也是同一个对象

底层原理

DefaultListableBeanFactory中有一个resolveDependency(DependencyDescriptor, String, Set<String>, TypeConverter) 方法,通过名称可以看出此方法专门用来解析依赖。在框架内部处理@Autowired注解时会调用此方法,方法内部会通过依赖查找的方式查出需要进行依赖注入的Bean。源码如下

    public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

        descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
        // 处理Optional类型的依赖注入
        if (Optional.class == descriptor.getDependencyType()) {
            return createOptionalDependency(descriptor, requestingBeanName);
        }
        // 处理ObjectFactory和ObjectProvider类型
        else if (ObjectFactory.class == descriptor.getDependencyType() ||
                ObjectProvider.class == descriptor.getDependencyType()) {
            return new DependencyObjectProvider(descriptor, requestingBeanName);
        }
        // 处理JSR330 相关的依赖注入
        else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
            return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
        }
        else {
            // 查找具体的依赖注入对象
            Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
                    descriptor, requestingBeanName);
            if (result == null) {
                result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
            }
            return result;
        }
}

在代码中可以看出,如果需要进行依赖注入的Bean类型为ObjectFactory或者ObjectProvider,则直接创建一个类型为DependencyObjectProvider的实例返回。如果注入的是具体类型则代码会走最后的else分支,doResolveDependency()方法本质上就是通过依赖查找的方式去获取对应的Bean

DefaultListableBeanFactory的一个内部类,结构如下

private interface BeanObjectProvider<T> extends ObjectProvider<T>, Serializable {
}

private class DependencyObjectProvider implements BeanObjectProvider<Object> {

        private final DependencyDescriptor descriptor;

        private final boolean optional;

        @Nullable
        private final String beanName;

        public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable String beanName) {
            // 需要注入对象的类型描述,在本例中即User类型
            this.descriptor = new NestedDependencyDescriptor(descriptor);
            // 是否是Optional类型
            this.optional = (this.descriptor.getDependencyType() == Optional.class);
            // 被依赖注入的对象,本例中为objectFactoryLazyLookupDemo
            this.beanName = beanName;
        }

        @Override
        public Object getObject() throws BeansException {
            if (this.optional) {
                return createOptionalDependency(this.descriptor, this.beanName);
            }
            else {
                // 内部实际上就是通过依赖查找的方式查出所需的Bean
                Object result = doResolveDependency(this.descriptor, this.beanName, null, null);
                if (result == null) {
                    throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType());
                }
                return result;
            }
        }

        // 省略其他方法.....
    }

通过代码可以看出DependencyObjectProvider实际上就是ObjectProvider类型,这里我只保留其getObject()方法,通过该方法可以看出,只有当使用者调用ObjectProvider#getObject()方法时,才会通过依赖查找的方式获取对应的Bean

总结和使用场景

通过示例代码和源码分析可以更确定延迟的概念,所谓延迟依赖查找就是等真正用到对象的时候才去获取对象。

那么使用延迟查找的应用场景有哪些呢

  • 可以让依赖的资源充分等到初始化完成之后再使用

  • 可以和@Lazy注解配合充分实现延迟初始化

    在本例的代码中,我们只在user()方法上面简单标注了@Bean注解,还可以通过标注@Lazy注解实现User对象的延迟初始化,和ObjectFactory配合使用就可以实现真正用到该对象的那一刻才进行初始化操作。

  • 可用于解决构造器级别的循环依赖文章来源地址https://www.toymoban.com/news/detail-453472.html

到了这里,关于Spring 高级依赖注入 —— Bean的延迟依赖查找功能,ObjectFactory 和 ObjectProvider的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring高手之路1——深入理解与实现IOC依赖查找与依赖注入

    本文从 xml 开始讲解,注解后面给出   首先,我们需要明白什么是 IOC (控制反转)和依赖查找。在 Spring Framework 中,控制反转是一种设计模式,可以帮助我们解耦模块间的关系,这样我们就可以把注意力更多地集中在核心的业务逻辑上,而不是在对象的创建和管理上。  

    2024年02月09日
    浏览(47)
  • 【Spring专题】Spring之Bean的生命周期源码解析——阶段二(二)(IOC之属性填充/依赖注入)

    由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序): Spring底层核心原理解析【学习难度: ★★☆☆☆ 】 手写简易

    2024年02月12日
    浏览(30)
  • Springboot依赖注入Bean的三种方式,final+构造器注入Bean

    @Autowired注解的一大使用场景就是Field Injection。 通过Java的反射机制实现,所以private的成员也可以被注入具体的对象 优点 代码少,简洁明了。 新增依赖十分方便,不需要修改原有代码 缺点 容易出现空指针异常。Field 注入允许构建对象实例时依赖的对象为空,导致空指针异常

    2024年02月02日
    浏览(36)
  • quarkus依赖注入之三:用注解选择注入bean

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本文是《quarkus依赖注入》系列的第三篇,前文咱们掌握了创建bean的几种方式,本篇趁热打铁,学习一个与创建bean有关的重要知识点:一个接口如果有多个实现类时,bean实例应该如何选择其中的一个

    2024年02月14日
    浏览(34)
  • quarkus依赖注入之一:创建bean

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 对一名java程序员来说,依赖注入应该是个熟悉的概念,简单的说就是:我要用XXX,但我不负责XXX的生产 以下代码来自spring官方,serve方法要使用MyComponent类的doWork方法,但是不负责MyComponent对象的实

    2024年02月15日
    浏览(26)
  • quarkus依赖注入之九:bean读写锁

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《quarkus依赖注入》的第九篇,目标是在轻松的气氛中学习一个小技能:bean锁 quarkus的bean锁本身很简单:用两个注解修饰bean和方法即可,但涉及到多线程同步问题,欣宸愿意花更多篇幅与各位

    2024年02月14日
    浏览(26)
  • 基于Xml方式Bean的配置-Bean的依赖注入以及·自动装配

    Bean的依赖注入方式 注入方式 配置方式 通过Bean的set方法注入 通过构造Bean的方法进行注入 其中,ref是reference的缩写形式,翻译为:涉及,参考的意思,用于引用其它Bean的id,value用于指定属性值 注入数据类型 普通数据类型:String、int、boolean,通过value属性指定 引用数据类型

    2024年02月07日
    浏览(37)
  • quarkus依赖注入之二:bean的作用域

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 官方资料:https://lordofthejars.github.io/quarkus-cheat-sheet/#_injection 作为《quarkus依赖注入》系列的第二篇,继续学习一个重要的知识点:bean的作用域(scope),每个bean的作用域是唯一的,不同类型的作用域

    2024年02月15日
    浏览(28)
  • quarkus依赖注入之十:学习和改变bean懒加载规则

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇是《quarkus依赖注入》系列的第十篇,来看一个容易被忽略的知识点:bean的懒加载,咱们先去了解quarkus框架下的懒加载规则,然后更重要的是掌握如何改变规则,以达到提前实例化的目标 总的来

    2024年02月14日
    浏览(24)
  • spring注册bean和注入bean的方法

    一个对象加入到Spring容器中。 XML配置方式 @Component注解@Controler、@Service、@Repository + @ComponentScan包扫描方式 @Configuration + @Bean方式 @Import方式 @Import + ImportSelector方式 @Import + ImportBeanDefinitionRegistrar方式 FactoryBean方式 BeanDefinitionRegistryPostProcessor方式 BeanFactoryPostProcessor方式 参考:添

    2024年02月15日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包