理解自动配置Bean(auto-configuration)
自动配置类是标准的@Configuration类,附加用来限制是否生效的条件注解(@Conditional)。如@ConditionalOnClass
@ConditionalOnMissingBean
注解. 这个可以确认在相关的类存在或未申明某Bean时才生效这个自动配置类。
自动配置类放置在类路径classpash下的META-INF/spring.factories文件里。SpringBoot自带的spring.factories在spring-boot-autoconfiguration模块jar里,默认的配置内容有(当前版本:2.30):
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
....
查找自己的自动配置候选类
在自己的Maven工程里,新建一份spring.factories文件放到recourses/META-INF/目录下:
app
--src
--main
--resources
-- META-INF
--spring.factories
--test
配置内容格式为,属性键值对,Key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx.emoAutoConfiguration
自动配置只能用这种方式存储并加载,需要确保这个路径不会被自动扫描,同时不能配置类似@ComponentScan这样的注解,但是可以配置@Import来导入额外的配置类。
可以使用@AutoConfigureAfter,@AutoConfigureBefore注解来应用配置类在特定的顺序下。例如如果是Web应用,你一般需要再WebMvcAutoConfiguration生效只会才加载你自己的配置类。同时你还可以使用@AutoConfigureOrder来指定你的配置类的生效顺序。
使用条件注解(@Conditional)
Class Conditions
@ConditionalOnClass
通过查找Name是否存在,如果存在配置类会被注册为Bean,同时生效配置类中的@Bean.
@Configuration(proxyBeanMethods = true)
static class OnClassCondition {
// Auto-configured beans
@Bean
public List<String> list() {
return Lists.newArrayList("A", "B");
}
@Bean("userService")
public UserService userService1() {
return new UserService();
}
@Bean
@ConditionalOnMissingBean(name = "userService2")
public UserService userService2() {
return new UserService();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.test.EmbeddedAcmeService")
static class EmbeddedConfiguration {
@Bean
public String hello() {
return "Hello";
}
@Bean
@ConditionalOnMissingBean
public UserService userService() {
return new UserService();
}
}
}
@Test
public void testOnClassCondition() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OnClassCondition.class));
contextRunner.withUserConfiguration(OnClassCondition.class).run((context) -> {
System.out.println(Arrays.toString(context.getBeanDefinitionNames()));
assertThat(context).getBean(OnClassCondition.class).isNotNull();
List<String> list = (List<String>) context.getBean("list");
assertThat(list).hasSize(2);
assertThat(context).getBean(UserService.class).isNull();
assertThat(context).getBean("hello").isNull();
});
}
@ConditionalOnMissingClass
如果Name指定的类不存在,则配置类生效,会被注册为Bean,同时生效配置类里的@Bean
@Configuration(proxyBeanMethods = false)
static class OnClassMissingCondition {
// Auto-configured beans
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.test.EmbeddedAcmeService")
static class EmbeddedConfiguration {
@Bean
public String hello() {
return "world";
}
@Bean
@ConditionalOnMissingBean
public UserService userService() {
return new UserService();
}
}
}
@Test
public void testOnClassMissingCondition() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OnClassMissingCondition.class));
contextRunner.withUserConfiguration(OnClassMissingCondition.class).run((context) -> {
System.out.println(Arrays.toString(context.getBeanDefinitionNames()));
assertThat(context).getBean(OnClassMissingCondition.class).isNotNull();
assertThat(context).getBean(OnClassMissingCondition.EmbeddedConfiguration.class).isNotNull();
assertThat(context).getBean(UserService.class).isNotNull();
assertThat(context).getBean("hello").hasToString("world");
});
}
Bean Conditions
@ConditionalOnMissingBean
@ConditionalOnBean
Bean存在时则生效配置类
Property Conditions
@ConditionalOnProperty
属性文件里配置了某属性=true,才生效。matchIfMission=true,表示如没有这个属性,默认生效。
如:spring.example.enble=true
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.example", name = "enable", matchIfMissing = true)
static class OnPropertyCondition {
@Bean
public UserService userService() {
return new UserService();
}
}
@Test
public void testOnPropertyCondition() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OnPropertyCondition.class));
contextRunner
.withUserConfiguration(OnPropertyCondition.class)
.withPropertyValues("spring.example.enable=false")
.run((context) -> {
System.out.println(Arrays.toString(context.getBeanDefinitionNames()));
assertThat(context).getBean(OnPropertyCondition.class).isNull();
assertThat(context).getBean(UserService.class).isNull();
});
}
SpringBoot内置AutoConfiguration示例
AopAutoConfiguration
ConditionalOnProperty+ConditionalOnClass
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for Spring's AOP support. Equivalent to enabling
* {@link EnableAspectJAutoProxy @EnableAspectJAutoProxy} in your configuration.
* <p>
* The configuration will not be activated if {@literal spring.aop.auto=false}. The
* {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be
* overridden by specifying {@literal spring.aop.proxy-target-class=false}.
*
* @author Dave Syer
* @author Josh Long
* @since 1.0.0
* @see EnableAspectJAutoProxy
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
ClassProxyingConfiguration(BeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
}
PropertyPlaceholderAutoConfiguration
AutoConfigureOrder+ConditionalOnMissingBean
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
DispatcherServletAutoConfiguration
ConditionalOnClass+EnableConfigurationProperties
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
/*
* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/*
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
}