美文网首页Spring开源框架-Spring系列
Spring—ObjectProvider更加宽泛的依赖注入

Spring—ObjectProvider更加宽泛的依赖注入

作者: 小胖学编程 | 来源:发表于2021-01-04 17:00 被阅读0次

    1. Spring依赖注入

    在Spring4.3之后,引入了一个新特性:当构造方法只有一个参数时,可以不使用@ Autowired注解。

    @Service
    public class FooService {
    
        private  FooRepository fooRepository;
    
        public FooService(FooRepository fooRepository){
            this.fooRepository=fooRepository;
        }
    }
    

    在SpringBoot的自动装配类中,这种形式被大量使用。

    在Spring4.3版本,引入了ObjectProvider接口。

    1.1 ObjectProvider的作用

    • 如果注入实例为空时,使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常;
    • 如果有多个实例,ObjectProvider的方法会根据Bean实现的Ordered接口或@Order注解指定的先后顺序获取一个Bean。从而了提供了一个更加宽松的依赖注入方式。

    可以看做是依赖注入的懒加载,异常将从启动阶段转移到业务运行的阶段。

    @Service
    public class FooService {
        private FooRepository fooRepository;
        public FooService(ObjectProvider<FooRepository> fooRepositoryObjectProvider){
            this.fooRepository=fooRepositoryObjectProvider.getIfAvailable();
        }
    }
    

    在Spring5.1版本后提供了基于orderedStream方法来获取有序的Stream方法。

    @Service
    public class FooService {
        private FooRepository fooRepository;
        public FooService(ObjectProvider<FooRepository> fooRepositoryObjectProvider){
            //可以灵活的选择注入的bean
            this.fooRepository=fooRepositoryObjectProvider.orderedStream().findFirst().get();
        }
    }
    

    1.2 ObjectProvider的实际运用场景

    在某些业务中,我们需要这样一种场景:若配置文件配置了某些参数,那么便启用某个功能。

    SpringBoot为我们提供了@ConditionalOnProperty(prefix = "xxx.rabbit", name = "thresholdTime")注解

    例如:配置了一个拦截器,只有在yml文件中含有xxx.rabbit.thresholdTime的配置时,才会将timeoutInterceptor这个bean放入到Spring容器中。

    @Slf4j
    @Configuration
    public class RabbitInterceptorAutoConfiguration {
    
        @Autowired
        private ApplicationContext applicationContext;
    
        @Bean
        @ConditionalOnProperty(prefix = "xxx.rabbit", name = "thresholdTime")
        @ConditionalOnMissingBean
        public TimeoutInterceptor timeoutInterceptor(XxxRabbitProperty property) {
            TimeoutInterceptor timeoutInterceptor = new TimeoutInterceptor();
            timeoutInterceptor.setThresholdTime(property.getThresholdTime());
            timeoutInterceptor.setApplicationContext(applicationContext);
            return timeoutInterceptor;
        }
    }
    

    但是若没有配置的话,Spring容器中没有该Bean,那么如何宽松的注入呢?

    @Slf4j
    @EnableRabbit
    @Configuration
    @EnableConfigurationProperties(value =XxxRabbitProperty.class)
    public class RabbitConfiguration {
    
        @Autowired
        private ObjectProvider<TimeoutInterceptor> timeoutInterceptorObjectProvider;
    
        @Bean(name = "singleListenerContainer")
        public SimpleRabbitListenerContainerFactory listenerContainerFactory(CachingConnectionFactory connectionFactory) {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            factory.setConnectionFactory(connectionFactory);
            factory.setConcurrentConsumers(1);
            factory.setMaxConcurrentConsumers(10);
            factory.setPrefetchCount(250);
            /* 设置当rabbitmq收到nack/reject确认信息时的处理方式,设为true,扔回queue头部,设为false,丢弃。 */
            factory.setDefaultRequeueRejected(true);
            //慢消息触发事件通知
            List<Advice> adviceList = new ArrayList<>();
            //运行的时候才会判断是否存在该Bean。
            TimeoutInterceptor timeoutInterceptor = timeoutInterceptorObjectProvider.getIfAvailable();
            //拦截器
            if (timeoutInterceptor != null) {
                adviceList.add(timeoutInterceptor);
            }
            factory.setAdviceChain(adviceList.toArray(new Advice[adviceList.size()]));
            //自动确认
            factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
            return factory;
        }
    }
    

    1.3 相关API

    public interface ObjectProvider<T> extends ObjectFactory<T> {
    
         //返回指定类型的Bean。
         //如果容器中不存在,那么抛出NoSuchBeanDefinitionException异常;
         //如果容器中存在多个此类型的bean,抛出NoUniqueBeanDefinitionException异常。
        T getObject(Object... args) throws BeansException;
    
        //如果指定类型的bean注册到容器中,返回bean实例,否则返回null。
       //当存在多个实例时,抛出NoUniqueBeanDefinitionException异常
        @Nullable
        T getIfAvailable() throws BeansException;
    
        //Spring5.0后的方法
        // 如果返回对象不存在,则进行回调,回调对象由Supplier传入
        default T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
            T dependency = getIfAvailable();
            return (dependency != null ? dependency : defaultSupplier.get());
        }
    
         //Spring5.0方法
          // 若bean存在,可以回调处理(消费)该Bean
        default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
            T dependency = getIfAvailable();
            if (dependency != null) {
                dependencyConsumer.accept(dependency);
            }
        }
    
          //如果不可用或不唯一(没有指定primary)则返回null。否则,返回对象。
        @Nullable
        T getIfUnique() throws BeansException;
    
        // 如果存在唯一对象,则调用Supplier的回调函数
        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);
            }
        }
    }
    

    注意点:

    1. T getObject(Object... args)的参数含义可参考ApplicationContext之getBean方法详解。即若是设置args参数获取bean。

    这种方式本质还是通过bean的id或者name来获取bean,通过第二个参数Object[] args可以给bean的属性赋值,赋值的方式有两种:构造方法和工厂方法。但是通过这种方式获取的bean必须把scope属性设置为prototype,也就是非单例模式。

    推荐阅读

    Spring Boot 注解之ObjectProvider源码追踪

    相关文章

      网友评论

        本文标题:Spring—ObjectProvider更加宽泛的依赖注入

        本文链接:https://www.haomeiwen.com/subject/zddvoktx.html