【Spring Boot】五、注解(自动配置)原理浅析

1概述

Spring关于自动配置的源码在spring-boot-autoconfigure-2.0.2.RELEASE.jar内:

spring.factories文件中声明了有哪些自动配置:

三种查看自动配置的方式:

  1. 运行jar添加--debug
  2. application.properties设置:debug=true
  3. 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判断过程:
  1. GenericWebApplicationContext是否被加载
  2. 容器里是否有session的scope-
  3. 当前容器的Environment是否为ConfigurableWebEnvironment
  4. 当前容器的ResourceLoader是否为WebApplicationContext
  • Reactive判断过程:
  1. 当前容器的Environment是否为ConfigurableReactiveWebEnvironment
  2. 当前容器的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;
    }
}

说明:

  1. 通过@EnableConfigurationProperties声明属性注入对象
  2. @ConditionalOnClass声明CharacterEncodingFilter需要在类路径下
  3. 当设置spring.http.encoding=enabled的情况下,默认为true,即满足条件
  4. 通过@Bean注入CharacterEncodingFilter,@ConditionalOnMissingBean声明如果容器内没有则创建新的Bean
  5. 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);
    }
}

结果:

 

 


爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqchaozai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值