第 10 节 自动配置与条件化注解@Conditional

您所在的位置:网站首页 config注解配合autowired会为nu 第 10 节 自动配置与条件化注解@Conditional

第 10 节 自动配置与条件化注解@Conditional

2024-06-12 16:14| 来源: 网络整理| 查看: 265

1 自动配置的实现

​ 在前一节介绍的 @EnableAutoConfiguration 注解主要用于启用 SpringBoot 框架的自动配置功能,即 @EnableAutoConfiguration 注解担任的是一个开关的作用,打开这个开关之后,SpringBoot 的自动配置功能才可以生效。生效之后就需要在项目中自动加载和配置对应的 bean 对象,这个过程就涉及到自动配置的实现了。

1.1 自动配置与个性化配置

​ 在讲解自动配置实现之前,我们先聊下自动配置与个性化配置的问题。

​ 虽然 SpringBoot 提供的自动配置功能可以帮助我们自动生成 bean 对象和注入到 Spring 的 IOC 容器中,使得我们在项目使用 @Autowired 注解直接注入这些对象使用即可。但是有些时候我们可能并不想使用 SpringBoot 自动生成的这些 bean 对象,而是使用自己在项目中配置的 bean 对象,或者压根就不想要加载某些 bean 对象,即需要个性化配置。

​ SpringBoot 的自动配置与个性化配置其实不是自相矛盾的,而是存在一个优先级关系,这也是 SpringBoot 框架自动配置的优雅之处。这个优先级的定义是:如果我们在项目中定义了提供相同功能的 bean 对象,SpringBoot 框架会优先使用这个 bean 对象,而不会再自动配置。说白了就是个性化配置优于自动配置,而这个主要是基于条件化注解 @Conditional 实现的,下面我们来详细分析一下 @Conditional 注解的相关知识。

1.2 @Conditional 条件化注解 1.2.1 原理分析

​ @Conditional 条件化注解是 Spring 4.0 开始提供的一个注解,该注解的作用是只有当指定的条件满足时,才会创建对应的 bean 对象。说白了就是给 bean 对象的创建增加了一些限制条件,给应用代码更多灵活的操作空间,可以在应用代码中决定是否创建某个 bean 对象。

​ @Conditional 注解的定义如下:

@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class[] value(); }

​ 由注解定义可知,@Conditional 注解既可以用在类上面,也可以用在方法上面。对于类级别,可以与 @Component 注解及相关注解,如 @Service 注解等,或者 @Configuration 注解一起使用。如果与 @Configuration 注解一起用在配置类上,此时可以通过自定义 Condition 接口的实现类来作为 @Conditional 注解的值 value,从而定义该配置类的生效条件。对于方法级别则一般与 @Bean 注解一起使用。

1.2.2 用法分析

​ 如下举个例子来说明一下 @Conditional 注解的使用方法,例子设计为:判断在 application.properties 文件是否配置了以 property.test 为 key 的数据,如果存在,则条件成立并在应用中打印这个数据的值;否则条件不成立。

实现 Condition 接口定义条件:检查 application.properties 配置文件是否存在使用 property.test 作为 key 的数据。

public class PropertyCondition implements Condition { public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); // 检查是否存在property.test作为key的数据 if (environment.getProperty("property.test") != null) { return true; } return false; } }

定义配置类并使用 @Conditional 注解定义生效条件:如果条件生效,则会通过 propertyData 方法创建一个 propertyData 对象并注册到 IOC 容器中。

@Configuration @Conditional(PropertyCondition.class) public class ConditionConfig { @Autowired private Environment environment; @Bean public String propertyData() { String data = environment.getProperty("property.test"); return data; } }

在 application.properties 文件中,通过配置与取消配置 property.test 数据来检查该配置类是否生效。

(1)配置

property.test=hello world

打印如下:

propertyData is hello world Process finished with exit code 0

(2)取消配置

#property.test=hello world

打印如下:抛 NoSuchBeanDefinitionException 异常,显示没有 propertyData 这个 bean 对象。

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:353) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1093) at com.yzxie.demo.springdemo.context.BootApplication.main(BootApplication.java:26)

拓展点

@Conditional 注解与 @Configuration 注解一起用在配置类时,如果条件不满足,则这个配置类的所有配置都不会加载和生效,包括 @Bean 方法和 @Import 关联类。例如,配置类内部使用 @Bean 注解的方法是不会创建对应的 bean 对象并注册到 Spring 的 IOC 容器的。在 @Conditional 注解中定义的条件是不支持继承的,即在父类中使用 @Conditional 注解定义的条件只会在父类生效,在子类中是不会生效的。 1.3 SpringBoot 基于 @Conditional 注解实现自动配置

​ 通过以上对 @Conditional 注解的原理和用法的讲解之后,在这里继续介绍 SpringBoot 基于条件化注解 @Conditional 来实现自动配置就不难理解了。

1.3.1 @Conditional 注解的使用

​ 我们知道 SpringBoot 自动配置的优先级是低于应用程序自身的自定义配置的,即如果应用代码本身已经显式配置和定义了某个 bean 对象,则 SpringBoot 不会再自动创建该 bean 对象。

​ 在内部实现层面,SpringBoot 基于 @Conditional 注解定义了多个用于判断是否需要加载某个 bean 对象的条件注解,具体为从类和类对象实例两个维度来定义。与 SpringBoot 的自动配置紧密相关的注解的定义如下:

@ConditionalOnClass:当类路径存在该类时生效 @ConditionalOnMissingClass : 与 @ConditionalOnClass 注解相反,当类路径不存在该类时生效 @ConditionalOnBean : 当IOC容器已经存在某个bean对象时生效 @ConditionalOnMissingBean : 与 @ConditionalOnBean 注解相反,当IOC容器不存在某个bean对象时生效 @ConditionalOnProperty:当 application.properties 配置文件中存在指定的配置时生效 @ConditionalOnWebApplication : 应用程序是Web应用时生效 @ConditionalOnNotWebApplication : 应用程序是非Web应用时生效

​ 以 @ConditionalOnClass 的定义为例,具体定义如下:使用 @Conditional 注解指定了生效条件 OnClassCondition。由于 @ConditionalOnClass 的作用是当类路径存在指定类时生效,故 OnClassCondition 在内部会检查类路径是否存在 @ConditionalOnClass 的 value 指定的类,如果存在,则条件生效。

@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented // 使用 @Conditional 注解,具体检查类路径是否存在 value 指定的类是在 OnClassCondition 内部分析 @Conditional(OnClassCondition.class) public @interface ConditionalOnClass { // 需要检查的类 Class[] value() default {}; String[] name() default {}; } 1.3.2 案例分析: Redis 自动配置的实现

​ Redis 是我们在应用中使用非常频繁的一个分布式缓存实现,故在这里使用 Redis 的相关类的自动配置作为案例来分析 SpringBoot 如何使用上面介绍的这些条件化注解来实现的。

​ 在上一节我们分析过,SpringBoot 通过 @EnableAutoConfiguration 注解来启用自动配置功能,并且在内部实现当中会使用 AutoConfigurationImportSelector 这个类来加载功能组件的配置类,通过处理对应的配置类来决定是否自动创建该功能组件的相关 bean 对象。

​ Redis 这个功能组件对应的配置类为:RedisAutoConfiguration,这个类也在上一节提到的 spring.factories 文件的以 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为 key 的类列表当中。如下是 RedisAutoConfiguration 的定义:

// 当类路径存在 RedisOperations 接口实现类时,条件生效 @ConditionalOnClass(RedisOperations.class) // 读取 application.properties 文件中关于Redis的相关配置信息 @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }

首先在 RedisAutoConfiguration 类上使用了 @ConditionalOnClass 注解,并且@ConditionalOnClass 注解的 value 为 RedisOperations.class,其中 RedisOperations 是一个接口。由 @ConditionalOnClass 的定义可知,只有在当前的类路径中,存在 RedisOperations 接口的实现类时,条件才会生效,即才能进行往下执行,处理该配置类的其他配置,否则不处理直接返回。

拓展

以上的 RedisAutoConfiguration 配置类只有 @ConditionalOnClass(RedisOperations.class) 这一个条件,故只要在应用中引入了 spring-data-redis 这个 jar 包,具体为通过 spring-boot-starter-data-redis 这个 starter 包引入,则 SpringBoot 都会对 RedisAutoConfiguration 内部的方法进行处理,即使在应用中根本没有使用 spring-data-redis 包的相关类。

而其他一些组件的配置类则会使用 @ConditionalOnBean 注解,当应用中确实使用了该组件的类,如通过 @Autowired 注入使用时,配置才会生效,从而继续处理配置类内部的 @Bean 方法。

还有一些配置类是使用 @ConditionalOnProperty 注解来检查在 application.properties 文件是否对该功能组件进行了配置,如 Redis 的配置是以 spring.redis 作为前戳的。

由 spring-data-redis 包的定义可知,RedisTemplate 是 RedisOperations 接口的实现类,故只要类路径中存在了 RedisTemplate 这个类,则以上条件生效。简单来说,就是只要在项目中引入了 spring-data-redis 这个包,则该条件就会生效。

如果条件成立,则 SpringBoot 会继续处理 RedisAutoConfiguration 上的其他注解,以及类内部的方法,如处理使用了 @Bean 注解的 redisTemplate 方法,判断是否需要创建该 bean 对象。

对于 redisTemplate 方法的处理会继续使用了 @ConditionalOnMissingBean(name = “redisTemplate”) 注解。该注解的作用是只有当应用没有配置名字 beanName 为 redisTemplate 的 bean 对象时,才会处理这个方法并生成 redisTemplate 这个 bean 对象和注入到 IOC 容器中。

Tips

对于 Redis,SpringBoot 是通过名字,即byName,而不是通过类型,即byType,来判断是否需要自动配置一个 redisTemplate 对象。

所以如果应用中需要自定义实现,而不需要 SpringBoot 自动配置的 redisTemplate 对象,则需要显式指定这个自定义的 RedisTemplate 类对象实例的名字为 redisTemplate,否则通过 @Autowired 注入 redisTemplate 对象时,注入的是 SpringBoot 自动配置的。

2 总结

​ 我们回顾一下本节所讲的内容,首先我们介绍了条件化注解 @Conditional 的由来和作用,即可以使用 @Conditional 注解来控制 bean 对象的创建。具体为可以用在使用了 @Configuration 注解的配置类上,或者使用了 @Bean 注解的方法上,从而可以根据设置的条件来判断是否需要加载这个配置类的内容,或者是否需要调用该使用了 @Bean 注解的方法来创建的 bean 对象来注册到 IOC 容器中。

​ 在对 @Conditional 注解的用法和原理的讲解的基础之上,我们再进一步展开对 SpringBoot 基于 @Conditional 注解实现自动配置的原理的分析。具体为 SpringBoot 基于 @Conditional 注解并且以类和类对象实例是否存在两个维度来拓展定义了一系列条件化注解。通过这些注解和相应的条件定义来判断是否需要为应用自动配置相应的 bean 对象,从而实现自动配置,以及实现个性化配置优于自动配置的理念。

代码仓库

1.2.2 例子:https://github.com/yzxie/java-framework-demo/tree/master/spring-demo



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3