SpringBoot基础自动装配原理
Posted 烟锁迷城
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot基础自动装配原理相关的知识,希望对你有一定的参考价值。
目录
3、实现ImportBeanDefinitionRegistrar接口
SpringBoot是为了简化开发而出现的技术。
1、SpringBoot项目构建
1.1、官网生成
https://start.spring.io/
1.2、IDE在线模板生成
在IDEA等编译工具中,也有对应的生成方法。
2、常见配置
2.1、入口类和相关注解
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);
}
}
在SpringBoot的启动类SpringApplication中的run方法里,可以看到,整个run方法返回的类就是ConfigurableApplicationContext,这个类继承了ApplicationContext。
在run方法内,refreshContext(ConfigurableApplicationContext context)方法完成对context的内部构建,启动时,它会执行,最终还是调用refresh()方法,这一点上和Spring框架IOC的初始化没有区别。
@SpringBootApplication注解内容如下,IOC在初始化时一定会加载这个注解。
//使用范围,类
@Target({ElementType.TYPE})
//作用域,运行时生效
@Retention(RetentionPolicy.RUNTIME)
//该注解会被API抽取
@Documented
//可以被继承
@Inherited
//以上四个注解为元注解
//相当于@Configuration
@SpringBootConfiguration
//自动装配
@EnableAutoConfiguration
//读取指定文件
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
可以看到,这是一个集合注解,其中包含很多注解内容
- 使用范围,类,@Target({ElementType.TYPE})
- 作用域,运行时生效,@Retention(RetentionPolicy.RUNTIME)
- 该注解会被API抽取,@Documented
- 可以被继承,@Inherited
- @SpringBootConfiguration,其内部代码显示其实这就是一个@Configuration注解
- @EnableAutoConfiguration,自动装配
- @ComponentScan,扫描文件,目前的默认配置是扫描@SpringBootApplication注解标注下同级的包以及子包
2.2、Banner
在resources文件夹下增加文件“banner.txt”,就可以将启动图案替换为该文件中的图案。
2.3、常规配置
在SpringBoot中提供两种配置文件,applicationContext.properties和applicationContext.yml。这两种文件的作用是一样的,但是语法格式会有所不同。
- server.port=8081,将访问端口改为8081
- server.servlet.context-path=/spring,设置访问路径,/spring
- 如果想要自定义配置属性,可以根据对应语法进行书写,然后使用@Value注解进行获取,如:@Value("${自定义数据}")
- 自定义配置属性也可以被加载到一个Bean中,然后托管给IOC。使用注解@ConfigurationProperties,此时自定义属性必须有一个前缀,该注解会将所有带有这个前缀的配置加载进来。@ConfigurationProperties(prefix = "前缀"),前缀后的数据名称需要和bean的数据名称保持一致,不然无法找到
2.4、日志
SpringBoot默认支持Logback,依赖已经被包含进去。
生效需要在配置文件中设置日志文件路径和日志输出级别
logging.file.path=d;/log
logging.level.org.springframework.web=DEBUG
或者直接使用logback.xml进行配置
2.5、profile环境切换
在SpringBoot中进行环境切换是比较简单的,只需要预先配置好对应环境的文件,比如生产环境配置:application-dev.properties,测试环境配置:application-dev.properties,然后在主配置中增加配置:
spring.profiles.active=dev
这样就可以决定引用哪一个配置文件。
2.6、静态资源
在创建SpringBoot项目时,resources文件夹下的static文件夹存放静态文件,template文件夹下存放动态文件。
3、核心原理
3.1、自动装配
@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 {};
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
除了四个自定义注解必须的元注解,就是@Import注解起到特殊的作用。
@Import注解主要起到注入外部Class的作用,在Spring中,能将Bean导入IOC的方法有很多,比如:
- 基于xml配置文件Bean标签
- 基于xml配置文件@Component
- 基于jiava配置类@Configuration和@Bean
- 基于java配置类,@ComponentScan+@Component
- FactoryBean接口getObject
- @Import注解
@Import注解的使用有三种。
1、搭配@Configuration注解使用
@Configuration
@Import({User.class})
public class JavaConfig {
}
这种写法可以直接将类注入到IOC容器中,但需要注意的是,这种写法是写死在程序中的。
2、实现ImportSelector接口
可以将一个类实现ImportSelector接口的selectImports方法,在这个方法内返回类的名字。使用@Import注解在自定义注解上,引入继承ImportSelector的类,即可将需要的类加入IOC中。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{User.class.getName()};
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyImportSelector.class})
public @interface EnableMyDefine {
}
@EnableMyDefine
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
}
}
这种写法就更加灵活
3、实现ImportBeanDefinitionRegistrar接口
可以实现接口ImportBeanDefinitionRegistrar的方法registerBeanDefinitions,在这个方法中,有一个IOC的注册器,只要在其中完成注册,就可以实现加载,其他文件代码和第二种差别不大。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
RootBeanDefinition user = new RootBeanDefinition(User.class);
registry.registerBeanDefinition("user", user);
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MyImportBeanDefinitionRegistrar.class})
public @interface EnableMyDefine {
}
@EnableMyDefine
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
}
}
了解到这些,就可以看这几个@Import注解的作用了
在注解@EnableAutoConfiguration中,可以看到对于@Import注解中引入的AutoConfigurationImportSelector根据命名来看就是实现了ImportSelector接口,那么可以看一下代码
@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 {};
}
直接看selectImport方法,在正常流程下,会执行getAutoConfigurationEntry中。List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);这句代码将加载特定的配置文件spring.factories。路径为:/org/springframework/boot/spring-boot-autoconfigure/2.5.5/spring-boot-autoconfigure-2.5.5
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
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;
}
这个位于org.springframework对应jar的配置文件spring.factories决定到底加载那些类。
后面的代码主要是为了对这些类做出一些过滤,最后把需要装配的类返回去。
阶段总结:实现ImportSelector接口的类可以根据springboot的jar包自动装配配置文件对指定的类进行加载,最后注入到IOC中,但是因为读取配置文件的原因,只能将第一方的类加载进去,第三方的无法被感知到,所以第三方jar也会带有一个一样的spring.factories文件来告诉IOC需要加载的类的路径。
那么既然自动装配装配了这么多类,有一些类其实并没有被依赖,那么没有被依赖的类是怎么过滤掉的呢?这就需要条件进行判断了。
在spring.factories同级目录下有一个文件spring-autoconfigure-metadata.properties,其内容大致为:
可以看到,里面有一些带有condition的配置,这是条件加载,只有在满足某些条件时才会将其装配到IOC中。
于是整个自动装配原理为:
- 在SpringBoot项目启动时,会加载@SpringBootApplication注解。
- 在@SpringBootApplication注解内,有@EnableAutoConfiguration注解
- 在@EnableAutoConfiguration注解中,有@Import注解
- @Import注解实现了ImportSelector接口的selectImports方法
- 加载META-INF/spring-autoconfigure-metadata.properties文件,根据其条件过滤需要加载的文件内容。
- 加载META-INF/spring.factories文件,根据配置类路径加载需要装配的类。
- 根据名称,配置文件等进行加载过滤,返回需要装配的类名,完成自动装配
以上是关于SpringBoot基础自动装配原理的主要内容,如果未能解决你的问题,请参考以下文章