自动配置绝对算得上是Spring Boot的最大亮点,完美的展现了CoC约定优于配置; Spring Boot能自动配置Spring各类子项目(Spring MVC, Spring Security, Spring Data, Spring Cloud, Spring Integration, Spring Batch等)以及第三方开源框架所须要定义的各类Bean。 Spring Boot内部定义了各类各样的XxxxAutoConfiguration配置类,预先定义好了各类所需的Bean。只有在特定的状况下这些配置类才会被起效。 html
1、概述
SpringBoot的Auto-configuration的核心实现都位于spring-boot-autoconfigure-xxx.jar;其中SpringBoot将依据classpath里面的依赖内容来自动配置bean到IoC容器,Auto-configuration会尝试推断哪些beans是用户可能会须要的。java
自动化配置并非spring boot才有的,从spring framework3.1开始,这个特性就有了,像@EnableAspectJAutoProxy、@EnableAsync都是从spring 3.1开始就有了。org.springframework.context.annotation包下面拥有自动配置的全部的相关的基础设施。web
基础设施
org.springframework.context.annotation包下面提供了各类基于注解配置的基础设施:
1. @Profile:可跟@Bean配合、
2. @Bean、@Scope、@DependsOn、@Primary、@Lazy、@Role、@Description:
3. @Conditional、Condition:@Conditional注解标识在类或者方法上,标识在方法上,符合条件,建立该方法的返回值类型的Bean;标识在类上,符合条件所有建立。
4. @Import(@ImportResource):
5. @Configuration表示的Class(@EnableLoadTimeWeaving)、ImportSelector接口实现(@EnableAsync)或者ImportBeanDefinitionRegistrar接口实现(@EnableAspectJAutoProxy)
6. ImportSelector、DeferredImportSelector:
7. ImportRegistry
8. ImportBeanDefinitionRegistrar:用来手动注册bean定义的, 能够实现相似于Mybatis-Spring提供的扫描Mapper接口并注册其bean定义, 事实上@MapperScan注解就@Import了MapperScannerRegistrar这个类, 而这个类实现了上面的接口, 来扫描Mapper并注册bean定义.再多说点吧, Spring解析Java配置类的时候, 会判断类是否是标注了@Import注解, 而后会判断, 若是Import注解的value是ImportBeanDefinitionRegistrar类型, 会存到一个变量, 后面初始化bean工程完成后, 会回调ImportBeanDefinitionRegistrar.
9. @Configuration:跟@Controller、@Servcice和@Repository是同样的套路,都用@Component注解了,做为特定类型的组件
10. @PropertySource
11. Condition、ConfigurationCondition、@Conditionalspring
spring boot autoconfigure
Spring Boot AutoConfigure替代了XML风格的配置文件,带来了史无前例的体验。Spring Boot AutoConfigure模块基于Spring Framework和Spring Boot提供的基础设施,构建类配置Bean+属性文件配置行为的配置方式,Java类配置Bean为咱们提供了更好的编程体验,属性文件配置行为的方式使这种方式拥有跟XML外部配置文件配置方式一样的灵活性。数据库
org.springframework.boot.autoconfigure
首先,Spring Boot AutoConfigure在Spring Framework和Spring Boot提供的基础设施上作了不少的扩展工做:
1. 顺序控制:AutoConfigureOrder、AutoConfigureAfter、AutoConfigureBefore;
2. AutoConfigurationPackage:在spring boot mian class上标识EnableAutoConfiguration以后,全部子包下面的spring 组件都能被扫描到,就是这个注解的能力;
3. EnableAutoConfiguration/ImportAutoConfiguration:EnableAutoConfiguration开启自动配置,自动应用spring.factories中配置的各类*AutoConfiguration;ImportAutoConfiguration跟EnableAutoConfiguration相比,只是没有自动配置的功能,给ImportAutoConfiguration传入谁的AutoConfiguration就应用谁的,单元测试等的场景用到的比较多;
4. 其余的一些工具类,过滤器之类的东西你们能够本身去看下编程
org.springframework.boot.autoconfigure.context.condition
ConditionalOnCloudPlatform:是否在云环境下,spring boot cloud模块提供了两种实现,CLOUD_FOUNDRY和HEROKU,国内应该用不到这个注解了
ConditionalOnJava:指定的Java版本
ConditionalOnWebApplication:是Web环境的时候
ConditionalOnNotWebApplication:不是web环境的时候
ConditionalOnJndi:JNDI环境下使用
ConditionalOnClass:classpath中存在某个类
ConditionalOnMissingClass:classpath中不存在某个类
ConditionalOnBean:BeanFactory中存在某个类的Bean
ConditionalOnMissingBean:BeanFactory中不存在某个类的Bean
ConditionalOnExpression:SpEL的结果
ConditionalOnProperty:Environment中是否有某个属性的配置信息
ConditionalOnResource:classpath中是否存在指定名称的资源
ConditionalOnSingleCandidate:指定的类在BeanFactory中只有一个候选的bean,或者有多个候选的bean,可是其中一个指定了primary时
各类*AutoConfiguration的实现:
全部的*AutoConfiguration的具体实现包括两部分,一个是标识了@Configuration注解的配置类,另外一个是Property文件。有些模块比较复杂,像security的oauth2模块,主要文件也是这两类,剩下的是一些工具。数组
*AutoConfiguration也是Configuration,被@Configuration注解,只不过spring boot autoconfigure模块内置的 *AutoConfiguration被配置到了 spring.factories文件中,启动的时候自动配置。springboot
自动配置是Spring Boot的最大亮点,完美的展现了CoC约定优于配置。网络
2、源码解析
2.一、从SpringBoot启动时的自动配置加载过程
查看源码能够看看自动配置类是如何被引入的。
a) 应用入口 架构
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
b) 类注解 @SpringBootApplication = @EnableAutoConfiguration + @ComponentScan + @Configuration(而其中的@EnableAutoConfiguration
则正是实现Auto Config的关键之所在)
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...
}
@Configuration
public @interface SpringBootConfiguration {
// ...
}
c)开启自动配置注解 @EnableAutoConfiguration,是auto config 关键所在。
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ...
}
d)导入配置类 EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector
@EnableAutoConfiguration
注解会导入AutoConfigurationImportSelector
类的实例被引入到Spring容器中,而该类的继承链以下:
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
所以对于 AutoConfigurationImportSelector类, 咱们重点关注的是其实现自ImportSelector接口的方法selectImports,而直接继承的DeferredImportSelector做为一个标志性接口,主要做用是为了Defer(推迟;延期;)。
在咱们继续探索以前,让咱们暂停一下,先来回顾下Spring是如何执行到这里来的,即如何调用到AutoConfigurationImportSelector.selectImports方法的。
在selectImports方法上打个断点,启动任意一个springboot项目,调用链以下图:

从上述堆栈中咱们能够看到 ConfigurationClassParser.parse() 被调用,而其参数candidates ,做为一个集合参数其中只包含咱们在启动SpringBoot时传入的那个AutoConfigSpringBootApplication类包裹所造成的BeanDefinitionHolder实例。
该ConfigurationClassParser.parse(Set<BeanDefinitionHolder> configCandidates)方法最终会调用到自身内部私有的processDeferredImportSelectors()方法:
// 本方法位于 protected 访问级别的 ConfigurationClassParser 中
private void processDeferredImportSelectors() {
// @EnableAutoConfiguration注解上修饰的@Import(AutoConfigurationImportSelector.class) 注解的解析是由 ConfigurationClassParser.parse中开始调度完成(本类中的processImports方法), 进而载入到本类的 deferredImportSelectors 字段中。
// 这里要特别注意,正由于AutoConfigurationImportSelector是一个DeferredImportSelector实例,因此其生效时机晚于@Import生效的时机,这也使得逻辑时序能够正确地运行下去。
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
// 这里取到的configClass就是咱们自定义的 AutoConfigSpringBootApplication
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
// 核心逻辑就是下面这两句了
// 首先是这行, 负责回调咱们上面使用@Import导入的AutoConfigurationImportSelector里的逻辑实现, 详情将在本文接下来的内容
// 最终的返回值是通过筛选,知足要求的类名
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
// configClass 就是咱们传递给 SpringApplication.run 的AutoConfigSpringBootApplication类
// 该方法最终会跳转到 本类内部的doProcessConfigurationClass方法中,来将相应Bean注册进容器, Auto Config完成。
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
}
catch {
// 异常处理略
}
}
}
绕了一圈终于回到本小节本来关注的内容——有关AutoConfigurationImportSelector
实现的selectImports
方法:
// AutoConfigurationImportSelector (位于package - org.springframework.boot.autoconfigure, 因此是SpringBoot自带的)
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
// 加载 META-INF/spring-autoconfigure-metadata.properties 中的相关配置信息, 注意这主要是供Spring内部使用的
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取全部经过META-INF/spring.factories配置的, 此时还不会进行过滤和筛选
// KEY为 : org.springframework.boot.autoconfigure.EnableAutoConfiguration
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 开始对上面取到的进行过滤,去重,排序等操做
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
// 这里返回的知足条件, 经过筛选的配置类
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
A、getCandidateConfigurations()方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 获取全部经过META-INF/spring.factories配置的, 此时还不会进行过滤和筛选KEY为:org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置的value(类路径+类名称)
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
//返回EnableAutoConfiguration.class
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
//此时为org.springframework.boot.autoconfigure.EnableAutoConfiguration
String factoryClassName = factoryClass.getName();
try {
//配置项的默认位置META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//从多个配置文件中查找,例如个人有:spring-boot-admin-starter-client-1.5.6.jar!/META-INF/spring.factories和stat-log-0.0.1-SNAPSHOT.jar!/META-INF/spring.factories
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
看上面的源码可知经过SpringFactoriesLoader.loadFactoryNames()把多个jar的/META-INF/spring.factories配置文件中的有EnableAutoConfiguration配置项都抓出来。
spring.factories的位置截图以下:

B、在完成了对spring.factories中全部的EnableAutoConfiguration的解析后,对其过滤,去重,排序等操做等,返回。
C、ConfigurationClassParser.processImports(),ConfigurationClassParser工具类自身的逻辑并不注册bean定义,它的主要任务是发现@Configuration注解的全部配置类并将这些配置类交给调用者(调用者会经过其余方式注册其中的bean定义),而对于非@Configuration注解的其余bean定义,好比@Component注解的bean定义,该工具类使用另一个工具ComponentScanAnnotationParser扫描和注册它们。
以上正是Springboot完成Auto Config功能的关键点之一了。在本实现中,SpringBoot只是告知Spring须要去加载(Import)哪些Config类,剩下的工做依然是Spring那已经通过千锤百炼的逻辑来完成; 这正是 “微核 + 扩展”的优秀架构设计经验的极致体现。
2.二、springboot自动配置DIY
经过上面的源码分析,能够将以下的spring.factories的全部配置类以下:
spring-boot-1.5.10.RELEASE.jar/META-INF/spring.factories
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
spring-boot-autoconfigure-1.5.10.RELEASE.jar/META-INF/spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
相应在作war时也是把当前的SpringBootDemoApplication做为source传给了ServletInitializer。
那么,咱们能够这样启动springboot,注解类不用SpringApplication,配置类也可自行导入
2.三、关键类EnableAutoConfiguration
2.3.一、EnableAutoConfiguration源码解析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
从上往下:
- 首先,最关键的要属@Import(AutoConfigurationImportSelector.class),借助AutoConfigurationImportSelector,@EnableAutoConfiguration能够帮助SpringBoot应用将全部符合条件的@Configuration配置都加载到当前SpringBoot建立并使用的IoC容器。(Import主要是配合Configuration来使用的,用来导出更多的Configuration类,ConfigurationClassPostProcessor会读取Import的内容来实现具体的逻辑。)借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration能够智能的自动配置(扫描每一个jar中的spring.factories)!
- 再看EnableAutoConfiguration的方法,就两个方法exclude和excludeName,做用是自动配置过程当中包含和排查指定的类。
2.3.二、自定义EnableAutoConfiguration示例
一、自定义EnableAutoConfiguration,这里Import了MyEnableAutoConfigurationImport。
package com.dxz.autoconfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(MyEnableAutoConfigurationImport.class)
public @interface MyEnableAutoConfiguration {
}
二、自定义EnableAutoConfigurationImport,注入了ClassLoader,并调用SpringFactoriesLoader.loadFactoryNames()方法,导出Configuration的类。
package com.dxz.autoconfig;
import java.util.List;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
public class MyEnableAutoConfigurationImport implements DeferredImportSelector, BeanClassLoaderAware {
private ClassLoader classLoader;
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> beanNames = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
return beanNames.toArray(new String[beanNames.size()]);
}
}
三、入口类,这里使用了MyEnableAutoConfiguration注解。
package com.dxz.autoconfig;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Configuration
@MyEnableAutoConfiguration
public class CustomizeEnableAutoConfigure {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(CustomizeEnableAutoConfigure.class);
application.run(args);
}
@Controller
public static class MyController {
@RequestMapping
@ResponseBody
public Map index() {
Map<String, String> map = new HashMap<String, String>();
map.put("hello", "world2");
return map;
}
}
}
结果:

2.四、condition包
2.4.一、condition示例讲解
springboot为咱们提供了一批实用的XxxCondition,查看了他们的源码后发现,他们都实现了spring提供的Condition接口,而后编写对应的annotation。咱们在使用他们的时候,只须要在须要的地方写上这些annotation就行了。这些注解都在springboot提供的jar包中
package org.springframework.boot.autoconfigure.condition。
提供这些condition主要目的:上面咱们讨论的AutoConfigurationImportSelector只能告诉Spring哪些类须要加载,但判断所配置的类是否能够被加载(即Auto Config里的Auto)是一个很是繁琐的逻辑,若是由某个中央控制系统来处理的话,必然会形成代码耦合和复杂性猛增,所以SpringBoot最终使用了一向的作法——将判断是否加载的权限下放给了各个须要进行自动配置的需求方自己,这样在springboot中扩展了不少condition。
基于Spring的@Conditional,SpringBoot提供了丰富的条件配置
@ConditionalOnClass : classpath中存在该类时起效
@ConditionalOnMissingClass : classpath中不存在该类时起效
@ConditionalOnBean : DI容器中存在该类型Bean时起效
@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
@ConditionalOnExpression : SpEL表达式结果为true时
@ConditionalOnProperty : 参数设置或者值一致时起效
@ConditionalOnResource : 指定的文件存在时起效
@ConditionalOnJndi : 指定的JNDI存在时起效
@ConditionalOnJava : 指定的Java版本存在时起效
@ConditionalOnWebApplication : Web应用环境下起效
@ConditionalOnNotWebApplication : 非Web应用环境下起效
@AutoConfigureAfter:在指定的配置类初始化后再加载
@AutoConfigureBefore:在指定的配置类初始化前加载
@AutoConfigureOrder:数越小越先初始化
2.4.二、condition示例讲解
1)@ConditionalOnBean
/@ConditionalOnMissingBean
当容器中存在/不存在某个bean时,加上此注解的bean被自动注入
package org.springframework.boot.autoconfigure.condition;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] type() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
}
2)@ConditionalOnJava
根据当前使用的JDK版本,判断是否自动注入
3)@
ConditionalOnProperty来控制Configuration是否生效
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用
String prefix() default "";//property名称的前缀,无关紧要
String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用
String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
boolean matchIfMissing() default false;//缺乏该property时是否能够加载。若是为true,没有该property也会正常加载;反之报错
boolean relaxedNames() default true;//是否能够松散匹配,至今不知道怎么使用的
}
经过其两个属性
name以及
havingValue来实现的,其中
name用来从
application.properties中读取某个属性值。
若是该值为空,则返回false;
若是值不为空,则将该值与havingValue指定的值进行比较,若是同样则返回true;不然返回false。
若是返回值为false,则该configuration不生效;为true则生效。
@Configuration
//在application.properties配置"mf.assert",对应的值为true
@ConditionalOnProperty(prefix="mf",name = "assert", havingValue = "true")
public class AssertConfig {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
2.4.2.三、示例:(参考网络)
最近碰到个这样的需求,须要同一套代码适配个版本数据库(数据库不一样,且部分表的字段及关联关系可能会不一样),即这套代码配置不一样的数据库都能跑。项目采用的框架为SpringBoot+Mybatis。通过一番思考,思路以下:
(1)在业务层(service)和数据访问层(Mapper)之间添加一层适配层,用来屏蔽数据库的差别
(2)适配层中代码均采用接口加实现类的方式,不一样的数据库用的实现类不一样
(3)业务层(service)中所有采用面向接口编程
(4)项目启动后只实例化和数据库相匹配的适配层实现类
实现上面的一个关键点是对bean的实例化添加一个条件判断来控制。其实SpringBoot里面新增了不少条件注解,能实现这个功能。可是都有些局限性,最终是采用自定义条件注解的方案。
2.4.2.3.1)、经过SpringBoot自带的注解ConditionalOnProperty实现
这个注解不作过多的解释,只说经过这个注解怎么实现咱们的功能。
假设咱们application.properties中配置一个配置项为
#bean实例化条件配置项
conditionKey: 1.0
那么只须要加上@ConditionalOnProperty的name和havingValue就能实现,只有配置文件中name对应的配置项的值和havingValue内容一致才实例化这个对象。
针对咱们上面配置的application.properties的内容,@ConditionalOnProperty的使用案例以下面代码所示
ManageImpl1.java代码以下:(MyManage接口、ManageImpl2省略)
package com.dxz.palmpay.condition;
import javax.annotation.PostConstruct;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
//仅当conditionKey==1.0的时候实例化这个类
@ConditionalOnProperty(name = "conditionKey", havingValue = "1.0")
@Component
public class ManageImpl1 implements MyManage {
@Override
public void sayHello() {
System.out.println("我是实现类01");
}
//为了效果,建立后打印一些信息
@PostConstruct
public void init() {
this.sayHello();
}
}
这个注解的局限性:这个注解的havingValue里面只能配置一个值。
因为项目个性化需求,但愿这个havingValue能够配置多个值,name对应的配置项的Value只要知足havingValue里面多个值的就表示匹配正确。即,havingValue里面能够配置多个值,name对应配置项的值来和havingValue匹配时,采用逻辑或匹配,知足一个值就算匹配正确。
2.4.2.3.2)、自定义条件注解
(1)思路
注解里面有2个属性,具体以下
- name:String类型,用来接受application.properties的配置项的key
- havingValue:String数组类型,用来和name对应key的Value进行匹配
(2)定义注解
package com.dxz.palmpay.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(CustomOnPropertyCondition.class)
public @interface CustomConditionalOnProperty {
String name() default "";
//havingValue数组,支持or匹配
String[] havingValue() default {};
}
(3)定义注解的匹配规则
package com.dxz.palmpay.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
/**
* 自定义条件注解的验证规则
*/
public class CustomOnPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String, Object> annotationAttributes = annotatedTypeMetadata
.getAnnotationAttributes(CustomConditionalOnProperty.class.getName());
String propertyName = (String) annotationAttributes.get("name");
String[] values = (String[]) annotationAttributes.get("havingValue");
if (0 == values.length) {
return false;
}
String propertyValue = conditionContext.getEnvironment().getProperty(propertyName);
// 有一个匹配上就ok
for (String havingValue : values) {
if (propertyValue.equalsIgnoreCase(havingValue)) {
return true;
}
}
return false;
}
}
@Component
@CustomConditionalOnProperty(name = "db.version", havingValue = {"3"})
public class ManageImpl3 implements MyManage {
@Component
@CustomConditionalOnProperty(name = "db.version", havingValue = {"1","2","4"})
public class ManageImpl4 implements MyManage {
自定义Condition注解,主要就2步
(1)定义一个条件注解
(2)定义一个条件的校验规则
参考:https://blog.csdn.net/lqzkcx3/article/details/82807888
参考:https://www.cnblogs.com/zeng1994/p/8c10310d8a042d56eddd40635afb6e93.html