SpringBoot自动装配原理及自定义start开发
Posted 愉悦滴帮主)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot自动装配原理及自定义start开发相关的知识,希望对你有一定的参考价值。
SpringBoot自动装配原理及自定义start开发
前言:
大部分互联网公司都是使用SpringBoot,或SpringCloud作为开发框架。在我们开发中,很可能遇到要开发公共组件提供给其他人调用的场景。随着项目版本的迭代,很可能把公共组件打成jar包。我们在自己的业务类中通过使用@Autowired注解来调用jar包中的公共类,公共方法等。但是如果公共给组件要作升级,也就是二次开发,导致以前定义的一些类名,方法名等发生改变,那么也要更改对应业务代码所依赖jar包的相关代码。这就会造成对jar包的强依赖性。以上问题就会对公共组件的使用是否便捷,维护起来是否方便都有很高的要求。我们期望我们开发的公共组件的jar包,能够直接依赖到我们业务项目中的pom文件里面,而公共组件的版本迭代不影响我们的业务代码。这就运用到了SpringBoot的自动装配。
在我们用Spring、SpringMVC(也就是Web框架)的时候通常都会配置web.xml、applicationContext.xml等配置文件,用Mybaties的时候通常也会配置SqlSessionFactory。但是SpringBoot并没有Web框架,和Mybaties等等这些功能,那么SpringBoot又是如何简化掉这些配置文件的呢?这就需要了解下面要讲的SpringBoot的自动装配。
SpringBoot可以省略版本号的原因:
为什么我们用SpringBoot时,通过在pom文件中加上下面的依赖(parent)就可以省略其他依赖的版本号呢?
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- <version>2.5.1</version>-->
</dependency>
我们点开 <artifactId>spring-boot-starter-parent</artifactId>这个依赖,发现如下依赖,不难理解就是管理依赖的意思。
//定义了核心依赖包的版本号
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.1</version>
</parent>
我们继续点开 <artifactId>spring-boot-dependencies</artifactId>,发现如下的pom文件,其中<dependencyManagement>标签就是用来管理依赖的作用。其实SpringBoot的父级工程已经帮我们把很大一部分的依赖都依赖好了,例如apache的activeMq,redis等等。所以我们再用的时候只需要依赖最外层的包就行。我们在开发中通常会遇到在导入pom依赖的时候经常因为各种依赖包的版本不匹配,依赖包冲突这样的问题。SpringBoot这么作的好处就是帮助我们进行版本以及依赖包的管理,防止版本号不匹配,依赖冲突这样问题的产生。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-amqp</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-blueprint</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>${activemq.version}</version>
</dependency>
</dependencyManagement>
//省略部分代码,关键是dependencyManagement标签
application.yml(.yaml、.properties)文件如何引用的
我们知道SpringBoot默认使用的是Tomcat提供Web服务,但是如果我们想到替换成 jetty,undertow。就可以通过配置application.yml(.yaml、.properties)文件来进行配置覆盖。
那么SpringBoot如何把 .yml文件依赖进来呢?我们通过点开<artifactId>spring-boot-starter-parent</artifactId>这个依赖就会发现spring-boot-starter-parent这个依赖通过<resource>这个标签将.yml文件注入进来了。如下:
//加载资源文件
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
SpringBoot自动装配之@SpringBootApplication注解
@SpringBootApplication注解通常作为启动类的注解,大家都不陌生。而SpringBoot自动装配主要基于@SpringBootApplication注解。
这是一个启动类:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
我们点开@SpringBootApplication注解。发现如下代码:
@SpringBootApplication注解是一个复合注解
者四个注解属于Java的元注解。元注解:修饰自定义注解的注解
@Target({ElementType.TYPE}) //定义注解的作用范围 (类、方法、属性)
@Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期。(注解保留多久,是在编译期起作用或者运行期起作用)
@Documented //Javadoc 可以配合@param,@see(超链接)注解使用
@Inherited //被它修饰的自定义注解可被子类继承
@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 {
.....省略
}
显然上面的四个注解与SpringBoot的自动装配关系不大。
自动装配之@SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
我们发现@SpringBootConfiguration注解中只有一个默认属性,我们点开SpringBootConfiguration 注解上的@Configuration注解发现有同样的默认属性。也就是说@SpringBootConfiguration注解相当于@Configuration注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
boolean proxyBeanMethods() default true这个属性是默认使用CGLIB动态代理。如果@Configuration这个属性值为false那么就与@Component注解起到一样的作用。
@Configuration修饰的类相当于一个配置类(可以理解为 .xml文件)。@Configuration注解的作用就是保证bean的唯一性,而@Component注解不能保证。也就是说在用@Configuration注解修饰的类中获取到的bean是唯一的。
注意:@Configuration注解的原因我们能够在启动类里面能够定义bean,而不用去单独写一个配置类。
自动装配之@EnableAutoConfiguration
我们点开@EnableAutoConfiguration注解发现,有两个注解对自动装配起到作用,分别是:@AutoConfigurationPackage、@Import
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class}) //导入参数类到IOC容器
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
1. @Import({AutoConfigurationImportSelector.class})注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
参数如果是ImportSelect的实现类,注册selectImports返回的数组到IOC容器(类的全路径),也就是注册类。批量注册
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
点开AutoConfigurationImportSelector
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//获取注解属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//从META-INF/spring.factories加载EnableAutoConfiguration类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = this.removeDuplicates(configurations);
//排除掉一些类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//检查
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
//发布事件,监听
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> 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;
}
点开loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
发现路径:META-INF/spring.factories
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
//发现路径:META-INF/spring.factories
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
....后面省略
}
我们找到autoconfigure这个包,打开spring.factories文件,如下图:
我们在spring.factories选择org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration打开发现DispatcherServletRegistrationConfiguration 上面有一个@bean标签。所以SpringBoot就是通过这种形式把DispatcherServletRegistrationConfiguration加载到IOC容器里面。
@Conditional({DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
@Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration {
protected DispatcherServletRegistrationConfiguration() {
}
@Bean(
name = {"dispatcherServletRegistration"}
)
@ConditionalOnBean(
value = {DispatcherServlet.class},
name = {"dispatcherServlet"}
)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
2. @AutoConfigurationPackage注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) //保存扫描路径,提供给Spring-data-jpa查询 @Entity
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@Import({Registrar.class}) //保存扫描路径,提供给Spring-data-jpa查询 @Entity
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
总结:@Import是SpringBoot自动装配的核心注解。
工作流程
- 参数如果是ImportBeanDefinitionRegistrar的实现类,支持手工注册bean
- 参数如果是ImportSelect的实现类,注册selectImports返回的数组到IOC容器(类的全路径),也就是注册类。批量注册
- 参数类如果是普通类,将该类实例化交给IOC容器管理(除开上面两种都是普通类)
以上是关于SpringBoot自动装配原理及自定义start开发的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot——SpringBoot四大核心之自动装配(源码解析)
SpringBoot——SpringBoot四大核心之自动装配(源码解析)