【源码解析】自动装配的实现原理

311 阅读7分钟

一、什么是自动装配?

  1. 所谓自动装配就是指启动spring应用时不需要像之前一样配置很多xml,而是直接通过SpringApplication来进行配置即可
  2. springboot会使用默认配置来填充之前需要手动配置的内容,同时支持我们通过properties文件修改这种默认配置,例如端口号、默认的内置容器等

二、自动装配的原理

  1. 自动装配是基于EnableAutoConfiguration来实现的
  2. 我们以Spring如何装配tomcat为例来解析

1、EnableAutoConfiguration注解

  1. 它试图去猜测和配置你可能需要的bean,它是基于我们classpath来生效的,比如你的classpath中包含tomcat-embedded.jar,那么spring可能认为你需要tomcat;除非你自定义了自己的ServletWebServerFactory
  2. 该注解对其子文件下的所有类生效
  3. 自动装配的类一般包含Configuration、Condition注解、ConditionOnClass注解等
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

1.1 Import注解

  1. 只能用在类上
  2. 用与将bean导入到spring的容器中,也可以导入三方jar包中类

1.1.1 Import注解的使用

1.1.1.1 使用import直接导入class类
  1. 使用Import注解的类必须也是spring中的bean,否则Import注解无法生效,例如使用@Component注解和@Service注解
@Component
@Import(ImportService.class)
public class DemoService {
}

public class ImportService {
    public void sayHello() {
        System.out.println("hello world!");
    }
}

public static void main(String[] args) {
	ConfigurableApplicationContext context =
			SpringApplication.run(Application.class, args);

    // 这里能获取到ImportService,并打印出hello world
	ImportService bean = context.getBean(ImportService.class);
	bean.sayHello();
}
1.1.1.2 使用ImportSelector类来导入
  1. 定义一个类继承ImportSelector,并且实现selectImports方法
  2. 该方法返回的数组就是需要加载到spring容器中的对象
public class DemoImporter implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String[] imports = {"com.zxk.springboot.demo.service.ImportService"};
        return imports;
    }
}

public static void main(String[] args) {
	ConfigurableApplicationContext context =
			SpringApplication.run(Application.class, args);

    // 这里能获取到ImportService,并打印出hello world
	ImportService bean = context.getBean(ImportService.class);
	bean.sayHello();
}
1.1.1.3 使用ImportBeanDefinitionRegistrar方法
  1. 用法类似于ImportSelector,继承ImportBeanDefinitionRegistrar,并实现registerBeanDefinitions方法
  2. 通过registerBeanDefinitions这个方法往spring容器中注入bean

1.1.2 AutoConfigurationImportSelector

  1. 通过AutoConfigurationImportSelector的select的selectImport方法将META-INF/spring.factories文件中的类加载到spring容器中
  2. 这里的annotationMetadata表示的是Import注解标注的那个注解的抽象,这里表示EnableAutoConfiguration
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 获取要加载的类
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
1.1.2.1 getAutoConfigurationEntry
  1. 加载spring.factories文件中的类集合configurations
  2. 获取EnableAutoConfiguration中的属性值,进行一些exclude处理(代码省略)
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	return new AutoConfigurationEntry(configurations, exclusions);
}
1.1.2.2 getCandidateConfigurations
  1. 从spring.factories文件获取候选的待加载的类
  2. 如果我们有自定义的需要自动装配的内容,可以在spring.factories文件中配置
// SpringFactoriesLoader
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	Enumeration<URL> urls = (classLoader != null ?
			classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
			ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
	result = new LinkedMultiValueMap<>();
	while (urls.hasMoreElements()) {
		URL url = urls.nextElement();
		UrlResource resource = new UrlResource(url);
		Properties properties = PropertiesLoaderUtils.loadProperties(resource);
		for (Map.Entry<?, ?> entry : properties.entrySet()) {
			String factoryTypeName = ((String) entry.getKey()).trim();
			for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
				result.add(factoryTypeName, factoryImplementationName.trim());
			}
		}
	}
	return result;
}
1.1.2.3 spring.factories

下面是摘自spring-boot-autoconfigure保重的spring.factories文件

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

1.2 AutoConfigurationPackage注解

  1. 用来配置自动扫描范围的注解,比如basePackages和basePackageClasses
  2. 可以配置扫描的basePackages,表示该包下面的所有类都会被springboot扫描到
  3. 可以配置扫描的基类basePackageClasses,表示该类对应包名下的所有对象都会被扫描
  4. 对于springboot没有配置basePackages和basePackageClasses这种情况,他会使用使用这个注解的类作为basePackageClasses,==这也就是为什么启动类同级目录下的所有对象都会被扫到的原因==
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
}

1.2.1 AutoConfigurationPackages.Registrar

  1. ImportBeanDefinitionRegistrar接口的作用是用来维护component扫描的basePackages
  2. 这里Registrar的作用是将启动类的包名作为basePackages,当进行spring扫描时,它会作为ComponentScan注解的一个扫描基包
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
	}

	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new PackageImports(metadata));
	}

}

1.2.2 PackageImports 获取自动扫描的包名

  1. 如果basePackages和basePackageClasses没设置的话就会使用启动类包名
  2. AnnotationMetadata是一个特定类所有注解的抽象,它并不要求类已经被加载
private static final class PackageImports {

	private final List<String> packageNames;

	PackageImports(AnnotationMetadata metadata) {
		AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
		List<String> packageNames = new ArrayList<>();
		for (String basePackage : attributes.getStringArray("basePackages")) {
			packageNames.add(basePackage);
		}
		for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
			packageNames.add(basePackageClass.getPackage().getName());
		}
		if (packageNames.isEmpty()) {
		    // metadata.getClassName会获取到当前启动类的全路径
			packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
		}
		this.packageNames = Collections.unmodifiableList(packageNames);
	}
}

2、自动装配

  1. EnableAutoConfiguration的作用是将需要加载的类以及basePackage找出来
  2. 真正的自动装配需要依赖于Condition、ConfigurationProperties等注解

2.1 Condition注解

  1. Condition注解是条件注解得一个元注解
  2. 它是在bean定义之前的检查
  3. 可以使用ConfigurationCondition进行更细粒度的控制,比如在不同阶段检查
  4. matches方法用来判断当前被注释的bean是否满足要求,比如要求必须有ClassA才能注册当前bean,那么这个macthes方法就是用来判断是否存在ClassA的
public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

2.2 Conditional注解

  1. 只有当所有value中的Condition都匹配了之后才会注册被注解修饰的类
  2. value是个数组,所以也就是说一个类的注册可以添加多个限定条件
public @interface Conditional {
	Class<? extends Condition>[] value();
}

2.3 ConditionalOnClass注解

  1. 该注解作用是注册某个类之前需要检查该注解中配置的类是否在类路径下存在
  2. 它是基于Conditional+OnClassCondition来实现的,其他的ConditionOnBean也是基于类似的方式实现
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    // 类路径下必须存在的class
	Class<?>[] value() default {};

    // 类路径下必须存在勒路径的名称
	String[] name() default {};
}

2.3.1 OnClassCondition

  1. 它是继承自Condition注解,当然它的直接父类不是Condition,而是springboot封装的SpringBootCondition
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	ClassLoader classLoader = context.getClassLoader();
	ConditionMessage matchMessage = ConditionMessage.empty();
	List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
	List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
	List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
	if (!present.isEmpty()) {
		return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
				.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
	}
	matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
			.didNotFind("unwanted class", "unwanted classes")
			.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
	return ConditionOutcome.match(matchMessage);
}

2.4 tomcat属性自动装配

  1. tomcat属性自动装配其实基于Condition和EnableConfigurationProperties注解来实现的
  2. tomcat的属性装配是在factory的bean初始化之前来进行的,基于BeanPostProcessor来实现
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
	public static class TomcatWebServerFactoryCustomizerConfiguration {
		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}

	}
}

2.4.1 TomcatWebServerFactoryCustomizer

  1. 它是用来将属性参数配置到ConfigurableTomcatWebServerFactory中的对象
  2. 在factory的bean初始化之前利用BeanPostProcessor来进行属性的注入
  3. ServerProperties是用来存储用户自定义的属性,配置在application.properties中
public class TomcatWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {

	private final Environment environment;

	private final ServerProperties serverProperties;

	public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
		this.environment = environment;
		this.serverProperties = serverProperties;
	}

	@Override
	public void customize(ConfigurableTomcatWebServerFactory factory) {
		ServerProperties properties = this.serverProperties;
		customizeStaticResources(factory);
	}

2.4.2 配置factory的属性

  1. 利用spring的BeanPostProcessor扩展来进行属性的注入
  2. 找到所有的WebServerFactoryCustomizer来进行装配,处理上面的TomcatWebServerFactoryCustomizer,其实还有一个ServletWebServerFactoryCustomizer,它用来装配基本属性,例如port、address等
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
	private List<WebServerFactoryCustomizer<?>> customizers;

	@SuppressWarnings("unchecked")
	private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
	    // 找到所有的WebServerFactoryCustomizer来进行属性的注入
	    // 调用customize进行装配
		LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
				.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
				.invoke((customizer) -> customizer.customize(webServerFactory));
	}

	private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
		if (this.customizers == null) {
			// Look up does not include the parent context
			this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
			this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
			this.customizers = Collections.unmodifiableList(this.customizers);
		}
		return this.customizers;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
	    // 去容器中找到所有的
		return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
	}

}

2.5 EnableConfigurationProperties

  1. 自动将application.properties中的属性注入该注解的对象中,比如上面的ServerProperties
  2. 利用EnableConfigurationPropertiesRegistrar将value的class注册到BeanDefinitionRegistry
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
	String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";

	Class<?>[] value() default {};

}

2.5.1 ConfigurationProperties

  1. prefix表示properties中的前缀,可以用value,也可以用prefix
  2. 当你想绑定外部的配置时(例如*.properties),可以将这个注解加到对应的bean或者方法上
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {

	@AliasFor("prefix")
	String value() default "";

	@AliasFor("value")
	String prefix() default "";

	boolean ignoreInvalidFields() default false;

	boolean ignoreUnknownFields() default true;

}

2.5.2 ServerProperties

  1. tomcat服务属性参数的加载,它会将以server开头的属性加载到这个对象中,例如server.port加载到port属性中
  2. 至此以tomcat属性的自动装配为例,已经将自动装配的原理说清楚了
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind.
	 */
	private InetAddress address;
}

三、Tomcat的启动

  1. 内置服务器的启动嵌入在spring的初始化过程
  2. 通过重写ApplicationContext的onRefresh方法来嵌入服务器启动
  3. Tomcat的配置的自动装配
  4. 具体的启动过程详见SpringBootApplication的解析