SpringBoot自动装配原理

464 阅读5分钟

1. 简介

不知道大家第一次搭SpringBoot环境的时候,有没有觉得非常简单。无须各种的配置文件,无须各种繁杂的pom坐标, 一个main方法,就能run起来了。与其他框架整合也贼方便,使用EnableXXXXX注解就可以搞起来了!

2. 原理

2.1 总体流程图

在看源码之前,我们不妨先想一下,如果是我们自己实现这个功能,要怎么实现呢?

  • 从META-INF.spring.factories读取key为EnableAutoConfiguration的value值,放入一个数据结构中
  • 从这个数据结构里按需加载这些类
  • 将这些类注册为一个一个的Bean
  • 最终由Spring管理使用

2.2 源码阅读

2.2.1 入口

首先从@SpringBootApplication注解开始看。 启动注解

可以看到@EnableAutoConfiguration这个注解,从名字可以猜测,这个注解就是开启自动装配的注解。

进入这个注解可以看到@Import了AutoConfigurationImportSelector这个类。

所以这个类就是我们源码阅读的入口类。

2.2.2 DeferredImportSelector

从代码中可以看到该类实现了DeferredImportSelector,该类可以和@Conditional组合使用。 可以根据Conditional的条件来选择是否开启某功能或加载某个Bean。

比如:看如下的AopAutoConfiguration类,是SpringBoot里AOP的Bean,该类里边有这么一个静态类:

	@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 {

		}

	}

表示当系统里有Advice类时,才会开启动态代理配置,而且默认是CGLIB的代理。随便创建一个SpringBoot的启动项目,在配置文件中加入"debug = true"这一行,启动项目。看到有如下输出:

AopAutoConfiguration.ClassProxyingConfiguration matched:
      - @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
      - @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)

说明系统中没有Advice类,无须动态代理。

DeferredImportSelector该类还有另外两个功能,一个是分组,一个是排序。具体应用是这样,有的时候我们可能会自己定制一个Bean,我们希望自己定义的Bean优先加载,但是由于这是一个Bean,无论如何都是由Spring IOC容器管理的,所以可以让自己定义的Bean实现DeferredImportSelector,将我们自己的类进行分组,区别于Spring的Bean,并把我们自定义的类先行注册、加载,这样Spring再进行管理Bean的时候,发现系统中已经有我们自己定义的Bean了,就不会对Bean进行注册加载了。

2.2.3 ConfigurationClassParser类

入口虽然是AutoConfigurationImportSelector类,但是具体的调用链,请看下图: 自动装配调用链

ConfigurationClassParser.getImports()方法里,可以看到会先调用系统中所有DeferredImportSelector实现类的process方法,然后再调用selectImports方法。

		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

2.2.4 AutoConfigurationImportSelector

2.2.4.1 process()方法

		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			//重点方法				
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

2.2.4.2 getAutoConfigurationEntry()方法

该方法的作用是获取所有有效的自动装配类。

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		//
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		//从所有的jar包中和类路径中读取META-INF/spring.factories文件,获取所有自动装配类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		//删除重复的自动装配类
		configurations = removeDuplicates(configurations);
		//获取显示的过滤条件
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		//检查过滤条件
		checkExcludedClasses(configurations, exclusions);
		//根据过滤条件删除类
		configurations.removeAll(exclusions);
		//从pom文件中读取有效的配置类,并删除无效的配置类
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

下面分析重点方法

2.2.4.3 getCandidateConfigurations()

从META-INF/spring.factories文件中读取key为EnableAutoConfiguration的所有配置类。

	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;
	}

2.2.4.4 selectImports()方法

这个方法就是将自动配置类进行排序,根据@Order、@AutoConfigureBefore、@AutoConfigureAfter这个顺序进行排序。

至此所有流程分析完毕,让我们总结下总体流程。 自动装配全流程

3. 自定义Starter

3.1 简介

SpringBoot 最强大的功能就是把我们常用的场景抽取成了一个个starter(场景启动器),我们通过引入springboot 为我提供的这些场景启动器,我们再进行少量的配置就能使用相应的功能。即使是这样,springboot也不能囊括我们所有 的使用场景,往往我们需要自定义starter,来简化我们对springboot的使用。比如笔者就在项目中写过akka的starter。

3.2 如何自定义

  1. 创建父项目:springboot_custome_starter
  2. 创建子项目:zyz-spring-boot-starter
  3. 创建子项目:zyz-spring-boot-starter-autoconfigurer
  4. zyz-spring-boot-starter项目依赖zyz-spring-boot-starter-autoconfigurer
  5. 在zyz-spring-boot-starter-autoconfigurer里创建具体逻辑
  6. 将zyz-spring-boot-starter-autoconfigurer项目打成jar包,并上传至本地Maven私有库
  7. 新建测试项目运行

3.3 代码实现

3.3.1 新建父工程

springboot_custome_starter

<?xml version="1.0" encoding="UTF-8"?>
<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>
    <packaging>pom</packaging>
    <modules>
        <module>zyz-spring-boot-starter</module>
        <module>zyz-spring-boot-starter-autoconfigurer</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>org.example</groupId>
    <artifactId>springboot_custome_starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

</project>

3.3.2 新建子项目zyz-spring-boot-starter

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>springboot_custome_starter</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>zyz-spring-boot-starter</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>zyz-spring-boot-starter-autoconfigurer</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

3.3.3 新建子项目zyz-spring-boot-starter-autoconfigurer

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>springboot_custome_starter</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>zyz-spring-boot-starter-autoconfigurer</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>
package com.starter.zyz;

import org.springframework.beans.factory.annotation.Autowired;
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;

@Configuration
@ConditionalOnProperty(value = "zyz.hello.name")
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfitguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public IndexController indexController(){
        return new IndexController(helloProperties);
    }

}

package com.starter.zyz;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("zyz.hello")
public class HelloProperties {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package com.starter.zyz;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    HelloProperties helloProperties;

    public IndexController(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    @RequestMapping("/")
    public String index(){
        return helloProperties.getName()+"欢迎你";
    }
}

resources目录中新建META-INF目录,并在该目录下新建文件spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.starter.zyz.HelloAutoConfitguration

3.3.4 上传至本地私有库

分别将两个子项目打成Jar包,并把zyz-spring-boot-starter项目上传至本地Maven仓库

install:install-file -Dfile=<Jar包的地址> 
                     -DgroupId=<Jar包的GroupId> 
                     -DartifactId=<Jar包的引用名称> 
                     -Dversion=<Jar包的版本> 
                     -Dpackaging=<Jar的打包方式>

3.3.5 新建测试项目运行

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.5.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.example</groupId>
			<artifactId>zyz-spring-boot-starter</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

启动类

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

启动项目,在浏览器里输入:http://localhost:8080

输出为:

"zyz"欢迎你

成功