在Spring Boot
项目中,我们只需要引入spring-boot-start-web
就能使用web
开发相关的功能,而不需要添加任何配置。其实Spring Boot
帮我们做好了很多的自动配置,在应用启动的时候依托于@EnableAutoConfiguration
注解来激活自动配置的模块。本文中的@Enable
和@Conditional
我并没有细说,这两部分内容在我之前的文章中有详细的阐述。如果读者并不这两部分内容熟悉的话,请先读我的另外的两篇文章。之前的文章中探究了@Enable***
用来激活指定的配置模块和使用@Conditional
实现条件配置,在这个基础上再来看这个这个自动配置就非常简单了,我们一起去看一看。
演示环境
- IntelliJ IDEA 2018.2.1 (Community Edition)
- Maven 3.5.4
- Spring Boot 2.1.1.RELEASE
走进源码
这里我们就以Web
模块为例,来说明一下这个自动装配的怎么实现的。正常情况下我们的启动类上都会标注@SpringBootApplication
,在这个@SpringBootApplication
注解里面我们会发现@EnableAutoConfiguration
用来激活我们的自动配置模块。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
而这个@EnableAutoConfiguration
注解里面使用了***ImportSelector
,用来实现对自动配置类的筛选。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
而这个AutoConfigurationImportSelector
这个类,又借助了工厂加载机制,根据@EnableAutoConfiguration
注解的全类名去加载META-INF/spring.factories
文件中配置好的自动配置的模块。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
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;
}
// 省略其它方法.......
}
spring-boot-autoconfigure-2.1.1.RELEASE
包下的META-INF/spring.factories
文件:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
上面的代码,一些不是我们叙述重点的都被我省略了。
然后去加载这个WebMvcAutoConfiguration
的配置:
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
首先这个WebMvcAutoConfiguration
类是一个被@Configuration
的配置类;然后它被标注了很多@ConditionalOn***
的条件配置的条件,只有都满足条件了才会被容器装载;接着还定义了一些加载的顺序,以及在哪些配置的类之后加载。到这儿我们的web
模块就被装载完成了。
实现自动配置
根据上面我们分析好的流程,我们可以自定义实现我们的自动配置。流程如下:
- 定义一个
***AutoConfiguration
的一个自动配置的类。 - 使用
@Enable***
,用来激活我们自定义的某些配置模块。 - 使用
@ConditionalOn***
,来根据自定义条件灵活的加载我们的 配置类。 - 利用工厂加载机制,将我们的配置类配到
META-INF/spring.factories
文件中去。 - 使用
@EnableAutoConfiguration
激活自动配置,验证我们的实现。
1、定义一个HelloAutoConfiguration
我们首先编写HelloAutoConfiguration
的自动配置类:
/**
* Hello模块自动装配
*
* @author Jerome Zhu
*/
@Configuration // 模式注解
@EnableHello // Enable Hello模块装配 ↑
@ConditionalOnSystemProperty(key = "user.name", value = "user") // 条件装配 ↑
public class HelloAutoConfiguration {
@Bean
public String sayHello() {
return "hello jerome, this is auto-configure.";
}
}
这里使用@Configuration
表明这是一个配置类,使用我们@EnableHello
用来激活我们自定义的Hello
模块。使用@ConditionalOnSystemProperty
用来判断系统参数是否满足我们制定的参数。
我的当前电脑的
user.name
的值为user
。
2、使用@Enable***
,激活模块配置
之前文章中编写好的@EnableHello
注解:
/**
* 激活Hello模块配置
*
* @author Jerome Zhu
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloConfiguration.class) // 模块装配:注解驱动实现
public @interface EnableHello {
}
以及HelloConfiguration
配置类:
/**
* Hello模块的配置
*
* @author Jerome Zhu
*/
@Configuration
public class HelloConfiguration {
@Bean
public String hello() { // method key is bean key
System.out.println("Bean : hello is loading.");
return "hello word !";
}
}
这里就激活了我们的自定义的Hello
模块,会向容器中添加一个名为hello
的javaBean
。
3、使用@ConditionalOn***
,判断自定义条件
之前文章中编写好的@ConditionalOnSystemProperty
注解:
/**
* 根据系统环境条件判断是否装配Bean
*
* @author Jerome Zhu
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
/**
* system property key
*/
String key();
/**
* system property key
*/
String value();
}
以及OnSystemPropertyCondition
类:
/**
* {@link Condition} 实现匹配系统配置的条件
*
* @author Jerome Zhu
*/
public class OnSystemPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
String propertyKey = String.valueOf(attributes.get("key"));
String propertyValue = String.valueOf(attributes.get("value"));
// 获取系统变量 propertyKey 对应的值
String sysPropertyValue = System.getProperty(propertyKey);
return propertyValue.equals(sysPropertyValue);
}
}
这里的条件配置就是判断当前系统变量中的属性,是否与我们限制的值相同,相同则向容器中注册。
4、添加自动配置类到META-INF/spring.factories
文件中。
在我们的项目的resources
文件夹下,新建META-INF
文件夹,在该文件夹下新建spring.factories
并添加一下配置:
# 添加自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xin.jerome.autoconfigure.configuration.HelloAutoConfiguration
5、验证自定义自动配置
我们编写一个启动类EnableHelloAutoConfigurationBootstrap
来验证我们的配置是否生效。
/**
* 使用spring boot 的自动装配
* {@link EnableAutoConfiguration} -> MATA-INF/spring.factories 取到相应的配置
*
* @author Jerome Zhu
*/
@EnableAutoConfiguration
public class EnableHelloAutoConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloAutoConfigurationBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
String helloBean = context.getBean("hello", String.class);
System.out.println("hello Bean: " + helloBean);
String sayHelloBean = context.getBean("sayHello", String.class);
System.out.println("sayHello Bean: " + sayHelloBean);
context.close();
}
}
首先使用@EnableAutoConfiguration
激活自动配置模块,因为我们的@Enable ->HelloConfiguration
注册了一个名为hello
的javaBean
,我们的HelloAutoConfiguration
配置注册了一个名为sayHello
的javaBean
。我们根据上下文去获取我们的javaBean
,并输出其内容。
当我们运行过后,控制台会有如下输出:
hello Bean: hello word !
sayHello Bean: hello jerome, this is auto-configure.
说明我们的自定义自动装配模块成功了。
总结
其实自动装配模块的有这么几个要点,一是使用@Enable***
模块用来激活我们的某一个功能模块;二是通过@Conditional***
根据条件灵活的绝对是否要注册该组件,三是添加我们配置好的自动配置类到spring.factories
文件中,让Spring Boot
提供的自动配置模块去读取并加载它。
具体的代码可以参考GitHub:spring-boot-demo-autoconfigure小节。