springboot启动main方法中都会加
@SpringBootApplication
这个注解,就把这个注解作为入口,研究下springboot自动装配的原理。
springboot使用的版本号是2.2.1.RELEASE
文章目录
@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的使用,之后的内容方便理解。
https://www.cnblogs.com/zhoading/p/12194960.html
@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
配置文件:
文件内容很长,我随便选了一行,key的构成://... org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.ConditionalOnClass= com.couchbase.client.java.Cluster,com.couchbase.client.java.CouchbaseBucket //...
自动配置的类全名.条件=值
,其中条件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元数据
https://blog.csdn.net/f641385712/article/details/88765470
-
spring的bean注册中心
https://blog.csdn.net/f641385712/article/details/89518940
- @Import注解的使用
https://www.cnblogs.com/zhoading/p/12194960.html