1概述
Spring关于自动配置的源码在spring-boot-autoconfigure-2.0.2.RELEASE.jar内:
spring.factories文件中声明了有哪些自动配置:
三种查看自动配置的方式:
- 运行jar添加--debug
- application.properties设置:debug=true
- eclipse的Run Configurations中VM arguments添加-Ddebug
添加后可在启动日志中查看到,Positive-启用的自动配置,Negative-未启用的自动配置:
2运作原理
从@SpringBootApplication说起,它是一个组合注解,核心功能有@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,getCandidateConfigurations中通过SpringFactoriesLoader的loadFactoryNames扫描META-INF/spring.factories中的自动配置类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final String[] NO_IMPORTS = new String[0];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
public Class<? extends Group> getImportGroup() {
return AutoConfigurationImportSelector.AutoConfigurationGroup.class;
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass() == AutoConfigurationImportSelector.class
? ((Boolean) this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class,
Boolean.valueOf(true))).booleanValue()
: true;
}
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = this.getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> {
return "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with "
+ ClassUtils.getShortName(name) + "?";
});
return attributes;
}
protected Class<?> getAnnotationClass() {
return EnableAutoConfiguration.class;
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(),
this.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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
......略......
}
抽象类:SpringFactoriesLoader
public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "\'factoryClass\' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
ArrayList result = new ArrayList(factoryNames.size());
Iterator arg4 = factoryNames.iterator();
while (arg4.hasNext()) {
String factoryName = (String) arg4.next();
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List) loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = (MultiValueMap) cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration ex = classLoader != null ? classLoader.getResources("META-INF/spring.factories")
: ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result1 = new LinkedMultiValueMap();
while (ex.hasMoreElements()) {
URL url = (URL) ex.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator arg5 = properties.entrySet().iterator();
while (arg5.hasNext()) {
Entry entry = (Entry) arg5.next();
List factoryClassNames = Arrays
.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result1.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result1);
return result1;
} catch (IOException arg8) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]",
arg8);
}
}
}
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
Class ex = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(ex)) {
throw new IllegalArgumentException(
"Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
} else {
return ReflectionUtils.accessibleConstructor(ex, new Class[0]).newInstance(new Object[0]);
}
} catch (Throwable arg3) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), arg3);
}
}
}
3核心注解
随便选择一个自动配置类打开,如AopAutoConfiguration:
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = { "auto" }, havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = {
"proxy-target-class" }, havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = {
"proxy-target-class" }, havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
}
可以看到包含很多条件注解@Conditional*,org.springframework.boot.autoconfigure.condition包下定义了许多:
这些注解都是组合了@Conditional元注解,只是使用了不同的条件。
已OnWebApplicationCondition做示例:
class OnWebApplicationCondition extends SpringBootCondition {
private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
ConditionOutcome outcome = this.isWebApplication(context, metadata, required);
return required && !outcome.isMatch() ? ConditionOutcome.noMatch(outcome.getConditionMessage())
: (!required && outcome.isMatch() ? ConditionOutcome.noMatch(outcome.getConditionMessage())
: ConditionOutcome.match(outcome.getConditionMessage()));
}
private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
boolean required) {
Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class,
new Object[] { required ? "(required)" : "" });
Type type = this.deduceType(metadata);
if (Type.SERVLET == type) {
return this.isServletWebApplication(context);
} else if (Type.REACTIVE == type) {
return this.isReactiveWebApplication(context);
} else {
ConditionOutcome servletOutcome = this.isServletWebApplication(context);
if (servletOutcome.isMatch() && required) {
return new ConditionOutcome(servletOutcome.isMatch(), message.because(servletOutcome.getMessage()));
} else {
ConditionOutcome reactiveOutcome = this.isReactiveWebApplication(context);
if (reactiveOutcome.isMatch() && required) {
return new ConditionOutcome(reactiveOutcome.isMatch(),
message.because(reactiveOutcome.getMessage()));
} else {
boolean finalOutcome = required ? servletOutcome.isMatch() && reactiveOutcome.isMatch()
: servletOutcome.isMatch() || reactiveOutcome.isMatch();
return new ConditionOutcome(finalOutcome, message.because(servletOutcome.getMessage()).append("and")
.append(reactiveOutcome.getMessage()));
}
}
}
}
private ConditionOutcome isServletWebApplication(ConditionContext context) {
Builder message = ConditionMessage.forCondition("", new Object[0]);
if (!ClassUtils.isPresent("org.springframework.web.context.support.GenericWebApplicationContext",
context.getClassLoader())) {
return ConditionOutcome.noMatch(message.didNotFind("web application classes").atAll());
} else {
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("\'session\' scope"));
}
}
return context.getEnvironment() instanceof ConfigurableWebEnvironment
? ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"))
: (context.getResourceLoader() instanceof WebApplicationContext
? ConditionOutcome.match(message.foundExactly("WebApplicationContext"))
: ConditionOutcome.noMatch(message.because("not a servlet web application")));
}
}
private ConditionOutcome isReactiveWebApplication(ConditionContext context) {
Builder message = ConditionMessage.forCondition("", new Object[0]);
return context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment
? ConditionOutcome.match(message.foundExactly("ConfigurableReactiveWebEnvironment"))
: (context.getResourceLoader() instanceof ReactiveWebApplicationContext
? ConditionOutcome.match(message.foundExactly("ReactiveWebApplicationContext"))
: ConditionOutcome.noMatch(message.because("not a reactive web application")));
}
private Type deduceType(AnnotatedTypeMetadata metadata) {
Map attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
return attributes != null ? (Type) attributes.get("type") : Type.ANY;
}
}
说明:
- 发现源码中不包含matches方法,那它如何实现条件判断?查看父类:
public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(this.getClass());
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome ex = this.getMatchOutcome(context, metadata);
this.logOutcome(classOrMethodName, ex);
this.recordEvaluation(context, classOrMethodName, ex);
return ex.isMatch();
} catch (NoClassDefFoundError arg4) {
throw new IllegalStateException(
"Could not evaluate condition on " + classOrMethodName + " due to " + arg4.getMessage()
+ " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)",
arg4);
} catch (RuntimeException arg5) {
throw new IllegalStateException("Error processing condition on " + this.getName(metadata), arg5);
}
}
......略......
}
- 父类重写matches方法,且声明方法为final类型,子类无法覆盖,那子类如何实现条件判断逻辑?通过返回ConditionOutcome对象
- 返回过程getMatchOutcome-->isWebApplication,此时按类型分别判断isServletWebApplication和isReactiveWebApplication,springboot2.*版本新加入Reactive模式。
- Servlet判断过程:
- GenericWebApplicationContext是否被加载
- 容器里是否有session的scope-
- 当前容器的Environment是否为ConfigurableWebEnvironment
- 当前容器的ResourceLoader是否为WebApplicationContext
- Reactive判断过程:
- 当前容器的Environment是否为ConfigurableReactiveWebEnvironment
- 当前容器的ResourceLoader是否为ReactiveWebApplicationContext
4实例分析
Spring boot编码设置:
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
#spring.http.encoding.enabled=true
自动配置类-HttpEncodingAutoConfiguration:
@Configuration
@EnableConfigurationProperties({ HttpEncodingProperties.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ CharacterEncodingFilter.class })
@ConditionalOnProperty(prefix = "spring.http.encoding", value = { "enabled" }, matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpEncodingProperties properties;
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
OrderedCharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties
.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.REQUEST));
filter.setForceResponseEncoding(this.properties
.shouldForce(org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type.RESPONSE));
return filter;
}
@Bean
public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
}
private static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final HttpEncodingProperties properties;
LocaleCharsetMappingsCustomizer(HttpEncodingProperties properties) {
this.properties = properties;
}
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
public int getOrder() {
return 0;
}
}
}
属性配置类-HttpEncodingProperties:
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET;
private Charset charset;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
public HttpEncodingProperties() {
this.charset = DEFAULT_CHARSET;
}
public Charset getCharset() {
return this.charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public boolean isForce() {
return Boolean.TRUE.equals(this.force);
}
public void setForce(boolean force) {
this.force = Boolean.valueOf(force);
}
public boolean isForceRequest() {
return Boolean.TRUE.equals(this.forceRequest);
}
public void setForceRequest(boolean forceRequest) {
this.forceRequest = Boolean.valueOf(forceRequest);
}
public boolean isForceResponse() {
return Boolean.TRUE.equals(this.forceResponse);
}
public void setForceResponse(boolean forceResponse) {
this.forceResponse = Boolean.valueOf(forceResponse);
}
public Map<Locale, Charset> getMapping() {
return this.mapping;
}
public void setMapping(Map<Locale, Charset> mapping) {
this.mapping = mapping;
}
public boolean shouldForce(HttpEncodingProperties.Type type) {
Boolean force = type != HttpEncodingProperties.Type.REQUEST ? this.forceResponse : this.forceRequest;
if (force == null) {
force = this.force;
}
if (force == null) {
force = Boolean.valueOf(type == HttpEncodingProperties.Type.REQUEST);
}
return force.booleanValue();
}
static {
DEFAULT_CHARSET = StandardCharsets.UTF_8;
}
public static enum Type {
REQUEST, RESPONSE;
}
}
说明:
- 通过@EnableConfigurationProperties声明属性注入对象
- @ConditionalOnClass声明CharacterEncodingFilter需要在类路径下
- 当设置spring.http.encoding=enabled的情况下,默认为true,即满足条件
- 通过@Bean注入CharacterEncodingFilter,@ConditionalOnMissingBean声明如果容器内没有则创建新的Bean
- HttpEncodingProperties中声明spring.http.encoding为前缀,默认字符集配置为UTF-8。
5实战
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study.spring</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-starter-hello</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
配置类:
package com.study.spring.spring_boot_starter_hello;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 通过hello.msg设置,默认world
* @author chaozai
* @date 2018年9月9日
*
*/
@ConfigurationProperties(prefix = "hello")
public class HelloServiceProperties {
private static final String MSG = "world";
private String msg = MSG;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
服务类:
package com.study.spring.spring_boot_starter_hello;
/**
* 默认服务
* @author chaozai
* @date 2018年9月9日
*
*/
public class HelloService {
private String msg;
public String sayHello(){
return "Hello "+msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
自动配置类:
package com.study.spring.spring_boot_starter_hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 自动配置类
* @author chaozai
* @date 2018年9月9日
*
*/
@Configuration
@EnableConfigurationProperties(HelloServiceProperties.class)
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix="hello",value="enable",matchIfMissing=true)
public class HelloServiceAutoConfiguration {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean
public HelloService helloService(){
HelloService service = new HelloService();
service.setMsg(helloServiceProperties.getMsg());
return service;
}
}
src\main\resources\META-INF\spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.study.spring.spring_boot_starter_hello.HelloServiceAutoConfiguration
验证:
<dependency>
<groupId>com.study.spring</groupId>
<artifactId>spring-boot-starter-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
package com.study.spring.springboot2.autoconfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.study.spring.spring_boot_starter_hello.HelloService;
/**
* 验证自定义starter-pom
* @author chaozai
* @date 2018年9月9日
*
*/
@SpringBootApplication
@RestController
public class HelloStarterApp {
@Autowired
HelloService service;
@GetMapping("/")
public String hello(){
return service.sayHello();
}
public static void main(String[] args) {
SpringApplication.run(HelloStarterApp.class, args);
}
}
结果:
爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!