SpringBoot自动注入原理初解与实现
前言:一直对SpringBoot的自动注入感兴趣,特意查了一下并进行了实现.
参考:https://blog.****.net/zxc123e/article/details/80222967(绝大部分是照抄了此博文,某些地方进行了改动)
https://www.cnblogs.com/duanxz/p/4520571.html
https://blog.****.net/gottst0113/article/details/80978966
https://www.cnblogs.com/duanxz/p/7493276.html
目录
3.1.5 HelloAutoConfiguration.java
3.2.3 application.properties配置
第一章 实现自动装配的注解
首先Spring Boot项目中都会如下启动类:
@SpringBootApplication
public class TestAutoConfigApplication {
public static void main(String[] args) {
SpringApplication.run(TestAutoConfigApplication.class, args);
}
}
从上面代码可以看出,注解@SpringBootApplication
和SpringApplication.run()
方法是最为重要的部分。这里主要来看看@SpringBootApplication
注解部分。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:
@Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration,相当于把该类作为spring的xml配置文件中的<beans>
)
@EnableAutoConfiguration(实现自动装配的注解)
@ComponentScan(将标识了需要装配的类自动装配到spring的bean容器中)
如果在启动类使用这个三个注解,整个SpringBoot应用依然可以与之前的启动类功能一样。但每次写这3个比较啰嗦,所以写一个@SpringBootApplication方便点。
第二章 EnableAutoConfiguration讲解
既然我们要讲自动装配,是要把EnableAutoConfiguration说一下.
打开其源代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
//@Import注解是为了把某个类注入到IOC容器中,这样就可以进行自动装配啥的,也可以通过getBean获取.
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
可以看到其将AutoConfigurationImportSelector.class导入了容器,借助AutoConfigurationImportSelector
,@EnableAutoConfiguration
可以帮助SpringBoot应用将所有符合条件的@Configuration
配置都加载到当前SpringBoot创建并使用的IoC容器(注意是Configuration配置)。
打开AutoConfigurationImportSelector的源码,
可以看到通过 SpringFactoriesLoader.loadFactoryNames()
把 XXX.jar/META-INF/spring.factories中每一个xxxAutoConfiguration文件都加载到容器中.
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.1 SpringFactoriesLoader
SpringFactoriesLoader属于Spring框架私有的一种扩展方案(类似于Java的SPI方案java.util.ServiceLoader),其主要功能就是从指定的配置文件META-INF/spring-factories
加载配置,而spring-factories
是一个典型的java properties文件,只不过Key和Value都是Java类型的完整类名,比如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
在@EnableAutoConfiguration场景中,SpringFactoriesLoader更多提供了一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfig.EnableAutoConfiguration作为查找的Key,获得对应的一组@Configuration类。
SpringFactoriesLoader是一个被final修饰的类,类中定义的静态属性定义了其加载资源的路径public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories",此外还有三个静态方法:
loadFactories:加载指定的factoryClass并进行实例化。
loadFactoryNames:加载指定的factoryClass的名称集合。
instantiateFactory:对指定的factoryClass进行实例化。
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
loadFactories方法首先获取类加载器,然后调用loadFactoryNames方法获取所有的指定资源的名称集合、接着调用instantiateFactory方法实例化这些资源类并将其添加到result集合中。最后调用AnnotationAwareOrderComparator.sort方法进行集合的排序。
第三章 实例
3.1 自动配置类
新建maven项目,pom.xml的配置如下.
3.1.1 pom.xml的配置
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>NewTestAutoConfig</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.3.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
3.1.2 项目目录结构如下
3.1.3 Hello.java
package com.example.newtestautoconfig;
public class Hello {
private String msg;
public String sayHello() {
return "hello " + msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3.1.4 HelloProperties.java
package com.example.newtestautoconfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
private static final String MSG = "world";
private String msg = MSG ;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3.1.5 HelloAutoConfiguration.java
@Configuration
//EnableConfigurationProperties注解。该注解是用来开启对@ConfigurationProperties注解
//配置Bean的支持,提供一种方便的方式来将带有@ConfigurationProperties注解的类
//注入为Spring容器的Bean,也就是@EnableConfigurationProperties注解
//告诉Spring Boot 能支持@ConfigurationProperties
//更多信息 查看https://www.cnblogs.com/duanxz/p/4520571.html
@EnableConfigurationProperties(HelloProperties.class)
//判断这个类是否在classpath中存在,如果存在,才会实例化一个Bean
@ConditionalOnClass(Hello.class)
// matchIfMissing没有prefix所表示的前缀也会生效,value=enabled表示注入生效.
//更多详见 https://blog.****.net/gottst0113/article/details/80978966
@ConditionalOnProperty(prefix="hello", value="enabled", matchIfMissing = true)
public class HelloAutoConfiguration {
@Autowired
private HelloProperties helloProperties;
@Bean
@ConditionalOnMissingBean(Hello.class)//容器中如果没有Hello这个类,那么自动配置这个Hello
public Hello hello() {
Hello hello = new Hello();
hello.setMsg(helloProperties.getMsg());
return hello;
}
}
3.1.6 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.newtestautoconfig.HelloAutoConfiguration
3.1.7 项目打包
最后使用maven install命令将项目打包到本地
3.2 应用类
新建SpringBoot项目
3.2.1 pom.xml
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>NewTestAutoConfig</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.3.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
3.2.2 主类
@SpringBootApplication
@RestController
public class TestAutoConfigApplication {
@Autowired
private Hello hello;
@RequestMapping("/")
public String index() {
return hello.sayHello();
}
public static void main(String[] args) {
SpringApplication.run(TestAutoConfigApplication.class, args);
}
}
3.2.3 application.properties配置
hello.msg=zhangsan
3.3 进行测试
成功!