springboot启动main方法中都会加
@SpringBootApplication
这个注解,就把这个注解作为入口,研究下springboot自动装配的原理。
springboot使用的版本号是2.2.1.RELEASE
文章目录
- @SpringBootApplication
- @EnableAutoConfiguration
- @AutoConfigurationPackage
- @Import
- @Import的使用
- @Import(AutoConfigurationPackages.Registrar.class)
- 新建demo类
- 原理分析
- @Import(AutoConfigurationImportSelector.class)
- 新建demo类
- 原理分析
- 深入研究源码原理
- 扩展资料
@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 {
//...
}
元注解
的用法自行百度,网上教程很多。我们重点关注@EnableAutoConfiguration
这个注解。首先看下@SpringBootConfiguration
:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
//...
}
@SpringBootConfiguration
其实就是一个@Configuration
配置注解。
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//...
}
这里有两个注解@AutoConfigurationPackage
和@Import
。
@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
这里也用到了@Import
注解,下面统一讲解下@Import
注解。
@Import
@Import的使用
先理解@Import的使用,之后的内容方便理解。
@Import(AutoConfigurationPackages.Registrar.class)
public abstract class AutoConfigurationPackages {
//...
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
}
下面举个demo来讲解下这个Registrar
的源码到底干了什么。
新建demo类
先模仿springboot新建几个demo类,同时附上对应于springboot中的类:
- LoggerService -> 一个普通的bean
public class LoggerService {
//...
}
- LoggerMyRegistrar -> AutoConfigurationPackages.Registrar
public class LoggerMyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Class beanClass = LoggerService.class;
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(beanClass);
String beanName = StringUtils.uncapitalize(beanClass.getSimpleName());
registry.registerBeanDefinition(beanName, rootBeanDefinition);
}
}
- EnableMyConfig -> AutoConfigurationPackage
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Import({LoggerMyRegistrar.class})
public @interface EnableMyConfig {
Class<?>[] exclude() default {};
}
- EnableDemoMain -> 启动类
@SpringBootApplication
@EnableMyConfig
public class EnableDemoMain {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(EnableDemoMain.class,args);
System.out.println(context.getBean(LoggerService.class));
}
}
原理分析
LoggerService
类上没有加任何的注解,但在main方法输出中,却能得到LoggerService
这个Bean
,原因就是LoggerMyRegistrar
在起作用,这个类相对于springboot中的AutoConfigurationPackages.Registrar.class
。具体流程如下:
- 启动类上加了
@EnableMyConfig
注解,EnableMyConfig
中@Import(LoggerMyRegistrar.class)
引入LoggerMyResitrar
这个类。 -
LoggerMyResitrar
实现ImportBeanDefinitionRegistrar
接口中的registerBeanDefinitions
方法。
该方法的第一个参数是元数据
,可参考这篇博客,详细了解下spring元数据,嫌麻烦的可以打个断点调试下看看该参数的具体值是多少。第二个参数是bean注册中心,详细可参考这篇博客,bean注册中心。 -
registry.registerBeanDefinition(beanName, beanDefinition)
最终通过这个方法注入需要的bean。 - 通过调试可知,源码中
register(registry, new PackageImport(metadata).getPackageName())
是把启动类所在的包下面的类都给注入。
@Import(AutoConfigurationImportSelector.class)
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//...
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
下面举个demo来讲解下这个ImportSelector
的源码到底干了什么。
新建demo类
先模仿springboot新建几个demo类,同时附上对应于springboot中的类:
- CacheService -> 一个普通的bean
public class CacheService {
//...
}
- CacheImportSelector -> AutoConfigurationImportSelector
public class CacheImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//可以加自定义判断逻辑
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(EnableMyConfig.class.getName());
return new String[]{CacheService.class.getName()};
}
}
- EnableMyConfig -> EnableAutoConfiguration
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Import({CacheImportSelector.class})
public @interface EnableMyConfig {
Class<?>[] exclude() default {};
}
- EnableDemoMain -> 启动类
@SpringBootApplication
@EnableMyConfig
public class EnableDemoMain {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(EnableDemoMain.class,args);
System.out.println(context.getBean(CacheService.class));
}
}
原理分析
CacheService
类上没有加任何的注解,但在main方法输出中,却能得到CacheService
这个Bean
,原因就是CacheImportSelector
在起作用,这个类相对于springboot中的AutoConfigurationImportSelector
。具体流程如下:
- 启动类上加了
@EnableMyConfig
注解,EnableMyConfig
中@Import(CacheImportSelector.class)
引入CacheImportSelector
这个类。 CacheImportSelector
实现ImportSelector
接口中的selectImports
方法。
该方法的第一个参数是元数据
,可参考这篇博客,详细了解下spring元数据,嫌麻烦的可以打个断点调试下看看该参数的具体值是多少。- 方法返回需要注入的bean类全路径字符串数组。
深入研究源码原理
AutoConfigurationImportSelector
实现的接口DeferredImportSelector
public interface DeferredImportSelector extends ImportSelector {
//...
}
- 最终实现的还是
ImportSelector
接口。 AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader)
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
//...
- 可以查看下
META-INF/spring-autoconfigure-metadata.properties
配置文件:
//...
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.ConditionalOnClass=
com.couchbase.client.java.Cluster,com.couchbase.client.java.CouchbaseBucket
//...
- 文件内容很长,我随便选了一行,key的构成:
自动配置的类全名.条件=值
,其中条件ConditionalOnClass
这个类应该很熟悉,不懂的可以百度springboot条件注解
。可以查看下org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration
源码,对照下配置文件中的内容方便理解:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ CouchbaseBucket.class, Cluster.class })
@Conditional(CouchbaseAutoConfiguration.CouchbaseCondition.class)
@EnableConfigurationProperties(CouchbaseProperties.class)
public class CouchbaseAutoConfiguration {
//...
}
CouchbaseAutoConfiguration
源码中用了@ConditionalOnClass({ CouchbaseBucket.class, Cluster.class })
和配置文件中的内容一对比,刚好匹配。
这段代码的意思是:注入配置文件META-INF/spring-autoconfigure-metadata.properties
中满足条件的Bean。当然自己可以对该文件进行扩展,在项目resources
文件夹下新建META-INF/spring-autoconfigure-metadata.properties
配置文件,添加自己的内容,就可以扩展。getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata)
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
- 以上代码可以打断点调试,看看那些参数值分别是多少。重点看
getCandidateConfigurations(annotationMetadata, attributes)
:
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
//...
}
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class)
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//...
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//...
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
//...
return result;
}
catch (IOException ex) {
//...
}
}
}
- 里面用到了
META-INF/spring.factories
这个配置文件,这个配置文件在spring-boot-autoconfigure-2.2.1.RELEASE.jar/META-INF/spring.factories
,文件中找到org.springframework.boot.autoconfigure.EnableAutoConfiguration
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...
- 看到这,可能依然不是很明白。
总结:通过META-INF/spring.factories
文件里的配置,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
,找到对应的值,把这些类注入。 当然META-INF/spring.factories
该文件也是可以扩展的。SPI
也是使用了类似的原理,数据库链接串就是用到了SPI
,不懂的可自行百度。
扩展资料
- spring元数据
- spring的bean注册中心
- @Import注解的使用