Springboot启动类注解
Springboot启动类注解
有三个重要的注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
一、@SpringBootConfiguration
这个注解是springboot的配置类,其作用和@configuration注解是基本上是一样的
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
可以看到其实就是直接对configuration注解进行了一层套娃式的封装
当我们在项目中使用了 @Indexed 之后,编译打包的时候会在项目中自动生成METAINT/spring.components文件。根据该文件进行扫描注入,可以提高效率,相当于为为扫描的类创建了索引文件,只需要一次io就能完成全部类的扫描。
当Spring应用上下文执行ComponentScan扫描时,META-INT/spring.components将会被CandidateComponentsIndexLoader 读取并加载,转换为CandidateComponentsIndex对象,这样的话@ComponentScan不在扫描指定的package,而是读取CandidateComponentsIndex对象,从而达到提升性能的目的。
简单点说,使用了@Indexed注解后,原来@ComponentScan可能需要扫描非常多的类,现在只需要在启动后对spring.components进行一次io,将类路径写入文件,后续将只需要读取文件即可获得所有类路径
proxyBeanMethods属性是是否为被注解的类修饰的属性每次都去ioc容器里获取它的代理类,默认为true开启,设置为false关闭后,每次获取这个类的实例会创建一个新的对象且不由IOC容器进行管理
二、@EnableAutoConfiguration注解
这个是springboot开启自动配置的核心所在
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
@Import,当注入的类实现了ImportSelector接口,就不会将该类型注入到容器中,而是会注入selectImports方法返回的信息对应的对象(动态使用)
@Import有三种用法:
- .class数组方式;
不额外实现其他接口,简单将class数组中的每个类注册到IOC容器中 - 实现ImportSelector(Spring Boot底层采用比较得多的方式);
实现ImportSelector接口,重写selectImports()方法,返回的是要导入到容器中的类的全类名数组。然后会将返回的全类名数组对应的类全部加载到spring容器中 - ImportBeanDefinitionRegistrar方式
实现ImportBeanDefinitionRegistrar接口
手动将指定的bean注册到IOC容器中
@AutoConfigurationPackage注解的作用是扫描启动类所在的包及其子包下所有的组件,并将其添加到IOC容器中进行管理
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
这里最核心的就是这个Registrar类,正是通过它去将启动类所在的包及其子包下的所有组件注册到IOC容器
Registrar实现了ImportBeanDefinitionRegistrar接口
这个接口提供了两个重载的默认方法,Registrar类重写了一个默认方法
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
这个方法提供了两个参数,第一个参数metadata包含了注解标注的类对象
第二个参数registry提供了spring的IOC容器对象
通过idea去动态debug执行new AutoConfigurationPackages.PackageImports(metadata)
可以看到这里是获取到了启动类所在包的全限定名
继续执行AutoConfigurationPackages类中提供的register方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
} else {
registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
}
}
这里的BEAN是AutoConfigurationPackages的全限定类名,判断IOC容器中是否注册了AutoConfigurationPackages这个bean
我们这里是debug进来第一次执行这段代码的,所以IOC容器中并没有这个bean,直接走else代码块new 一个BasePackagesBeanDefinition对象,然后将包名赋值给他的basePackages属性。
basePackages是个set集合,但是容器当中始终只有一个BasePackagesBeanDefinition对象,也就是只要代码当中添加@AutoConfigurationPackage注解,就会将注解所在的包名添加到basePackages集合当中。
然后调用BeanDefinitionRegistry对象的registerBeanDefinition方法,将这个BasePackagesBeanDefinition对象注册到IOC容器中交给spring管理
if (this.hasBeanCreationStarted()) {
synchronized(this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
this.removeManualSingletonName(beanName);
}
}
beanDefinitionMap就是IOC容器所管理的bean集合,k是bean的名称,v是bean的实例对象
对IOC容器中的beanDefinitionMap加上synchronized锁,虽然这个beanDefinitionMap对象是ConCurrentHashMap的实例对象,这个map的实现类是能够保证并发线程安全的,但是它只保证了数据的最终一致性,get方法是可能拿到脏数据,而spring的IOC容器必须要能够保证强一致性,所以为了防止其他地方读取beanDefinitionMap拿到旧的副本,这里还是要上锁保证强一致性
执行完这串代码我们的BasePackagesBeanDefinition对象就被加载到IOC容器中了,同时将我们的启动类所在的包添加到BasePackagesBeanDefinition对象的的basepackges属性中,后续spring将会根据这个属性将这个包内所有的组件注册到IOC容器中
OK AutoConfigurationPackage注解的作用和原理我们就研究到这里了
@EnableAutoConfiguration注解中还包含了@Import({AutoConfigurationImportSelector.class})
引入了AutoConfigurationImportSelector这个类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered
AutoConfigurationImportSelector实现的接口很丰富,但是我们这里只需要去注意DeferredImportSelector这个接口的实现就行了,其他Aware接口都是spring提供可以获取到BeanFactory和ResourceLoader等等的,有需要了解的可以自行去查阅其作用
这个类才是springboot实现自动装配的核心所在,下面我们开始分析这个类,首先分析一个类肯定是要从抽象到具体,所以我们需要搞清楚它实现的接口提供的作用
public interface DeferredImportSelector extends ImportSelector
可以看到这个接口是ImportSelector的子接口
实现DeferredImportSelector可以提高应用程序的启动速度和效率,因为只在需要时加载额外的配置。
@Nullable
default Class<? extends DeferredImportSelector.Group> getImportGroup() {
return null;
}
public interface Group {
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
Iterable<DeferredImportSelector.Group.Entry> selectImports();
public static class Entry {
private final AnnotationMetadata metadata;
private final String importClassName;
public Entry(AnnotationMetadata metadata, String importClassName) {
this.metadata = metadata;
this.importClassName = importClassName;
}
public AnnotationMetadata getMetadata() {
return this.metadata;
}
public String getImportClassName() {
return this.importClassName;
}
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
} else if (other != null && this.getClass() == other.getClass()) {
DeferredImportSelector.Group.Entry entry = (DeferredImportSelector.Group.Entry)other;
return this.metadata.equals(entry.metadata) && this.importClassName.equals(entry.importClassName);
} else {
return false;
}
}
public int hashCode() {
return this.metadata.hashCode() * 31 + this.importClassName.hashCode();
}
public String toString() {
return this.importClassName;
}
}
}
DeferredImportSelector内部自己定义了一个Group接口,还有一个默认方法用来获取Group接口的实现类的实例对象
同时它是ImportSelector的子接口,AutoConfigurationImportSelector重写了selectImports方法用来加载需要自动装配的组件,但是通过debug我们发现springboot在启动流程中并不会执行整个方法,而是执行了DeferredImportSelector接口提供的getImportGroup方法,所以我们直接跳过对selectImports方法的分析
@Import(class<?> extends DeferredImportSelector)将会走到这个流程中
class ConfigurationClassParser{
private class DeferredImportSelectorGroupingHandler
{
public void register(ConfigurationClassParser.DeferredImportSelectorHolder deferredImport) {
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
ConfigurationClassParser.DeferredImportSelectorGrouping grouping = (ConfigurationClassParser.DeferredImportSelectorGrouping)this.groupings.computeIfAbsent(group != null ? group : deferredImport, (key) -> {
return new ConfigurationClassParser.DeferredImportSelectorGrouping(this.createGroup(group));
});
grouping.add(deferredImport);
this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getConfigurationClass());
}
}
}
自动装配最终是在AutoConfigurationImportSelector的静态内部类AutoConfigurationGroup中完成的
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
这一行代码
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
在这里面读取了自动装配的信息
step into进去看看
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
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());
}
这里factoryTypeName是org.springframework.boot.autoconfigure.EnableAutoConfiguration,看到这里就很眼熟了吧,自动装配的配置类名
然后继续step into
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();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
这里实际上是在读取springbootframework的jar包下的META-INF/spring.factories文件,并将它们放到cache里面缓存,下次就不需要重新读取了,直接从map中拿就行了
通过后续debug也可以发现springbootframework的jar包下的META-INF/spring.factories需要被多次读取,所以这里使用了一个map进行缓存
最终configurations被读取出来
可以看到这里面有我们自己引入的mybatis和pagehelper druid等等
而且spring-boot-starter-autoconfigure包为我们整合了很多常用的组件,
只要我们引入了相应的jar包,springboot就会自动读取相应的配置信息,当然对于我们自定义的jar包,我们就需要自己写好spring.factories文件和自动装配的类,毕竟为了防止过于臃肿,springboot自动装配的这个包只是包括了一些常用的组件的自动装配实现
三、ComponentScan注解
在springbootApplication中,componentscan注解的作用主要是扫描@AutoConfigurationPackage中设置的basepackges属性指向的包及其子包
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
这个注解是可以重复使用在一个类上的
@Repeatable(ComponentScans.class)
ComponentScans只有一个属性value,类型为ComponentScan数组,注解默认情况下都是不可在同一个作用对象上重复使用的
所以即使你是用了springbootapplication注解,你依然可以在启动类上使用ComponentScan注解去对springboot的扫描进行一些手动配置
配置缺省的情况下将按照AutoConfigurationPackage添加的basepackges属性进行扫描
总结
因为spring默认情况下只能扫描到我们项目启动类下的组件,但是对于项目引入的jar包文件我们就必须手动扫描,springboot通过读取jar包文件下的META-INF/spring.factories文件,将其中指定的需要装配的类读取到IOC容器中,从而使得非spring官方的组件也能够不修改原有代码的情况下,只需要增加一部分自动装配的文件配置和自动装配类,即可完成与springboot的整合,充分体现了Java中SPI的设计理念