SpringBoot 自动装配原理
1. 元注解和组合注解
元注解: 主要作用就是为其他注解提供解释说明
@Target(ElementType.TYPE) 表明该注解用于类、接口、注解、枚举上
@Retention(RetentionPolicy.RUNTIME) 表明该注解将会被保留在Class文件上,且在JVM中也保留(运行时可获得)
@Documented 将注解包含在Javadoc中(javadoc中默认不保留注解)
@Inherited 表示父类注解将会被子类继承得到
组合注解: 字面意思是多个注解组合形成,被注解的注解就是组合注解
2. 自动装配原理
2.1 Springboot的主启动类
@SpringBootApplication
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
只要标注了@SpringbootApplication
的类就是启动类,启动类通过SpringApplication.run方法找到主启动类中的 @SpringbootApplication
注解即可实现自动装配,因此@SpringbootApplication
即为启动类的核心
2.2 @SpringbootApplication注解
按住Ctrl
点进@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 {
可以看到, @SpringbootApplication
是一个组合注解,除了元注解外,由三个注解组成
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
2.2.1 @ComponentScan
这个注解就是扫描包,并放进spring容器中。
2.2.2 @SpringBootConfiguration
该注解也是一个组合注解,且组成十分简单,就是对@Configuration
注解的一个封装,表示被注解类是一个 配置类,该注解表明主启动类其实也是一个配置类
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
2.2.3 @EnableAutoConfiguration
这个注解是实现自动装配的核心注解,也是一个组合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
2.2.3.1 @AutoConfigurationPackage
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
@AutoConfigurationPackage
里面就是一个@Import
注解。@Import
的作用就是给容器中注册一个组件,所以它其实就是注册了Registrar
这个组件,
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
其中: AnnotationMetadata metadata 即注解元信息,通过 metadata 拿到了主启动类的信息,打个断点看下
其实就是将 启动类所在包及子包里面所有组件扫描加载到Spring容器。
2.3.3.2 @Import(AutoConfigurationImportSelector.class)
这个注解导入了一个 AutoConfigurationImportSelector组件,这个组件继承了 DeferredImportSelector延迟导入选择器,并重写了其 selectImports方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 首先判断是否禁用了自动装配的功能
if (!isEnabled(annotationMetadata)) {
// 若开启了,则返回一个空数组
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
通过断点看到这个方法将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。
这些自动配置类都是 xxAutoConfiguration命名方式。
具体如何自动导入的: 可以看到,这个方法首先判断 @EnableAutoConfiguration
这个注解有没有开启,
isEnabled
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
然后通过 getAutoConfigurationEntry方法获取所有自动装配类的条目,获取到所有自动配置类的条目后,再进行排除项和重复项的移除
查看该方法源码
protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
官方注释
Return the AutoConfigurationImportSelector.AutoConfigurationEntry based on the AnnotationMetadata of the importing @Configuration class.
Params:
annotationMetadata – the annotation metadata of the configuration class
Returns:
the auto-configurations that should be imported
可以看到主要是通过 getCandidateConfigurations()
方法加载配置类的,再继续看其源码
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;
}
getCandidateConfigurations()
通过SpringFactoriesLoader
的loadFactoryNames()
方法获取到一个所有自动配置类类名的List集合,
loadFactoryNames()
调用 loadSpringFactories方法从 FACTORIES_RESOURCE_LOCATION 即META-INF/spring.factories这个目录中获取配置信息,封装为Properties
对象,但是,springboot并不会直接加载所有配置,而是需要满足一定条件才会加载,@ConditionOnxxx
注解判断是否开启自动装配
注意:loadSpringFactories并不是直接去加载资源的,它首先从缓存 cache中读取,若没有,再去加载
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
找一个 spring.factories
看下,在META-INF目录下
以 RedisAutoConfiguration为例
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@EnableConfigurationProperties
注解中引入的类就封装了对应的自动装配类的可配置属性定义了我们可以再配置文件中的定义的属性
RedisProperties部分源码
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
/**
* Database index used by the connection factory.
*/
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Whether to enable SSL support.
*/
private boolean ssl;
2.3 自动装配原理总结
Springboot自动装配流程图:
springboot是通过启动类的SpringBootApplication注解进行开始解析的,他会根据EnableAutoConfiguration
开启自动化配置,里面有个核心方法ImportSelect选择性的导入,通过loadFanctoryNames
方法根据classpash路径以MATA-INF/spring.factorces
下面以什么什么EnableAutoConfiguration
开头的key去加载里面所有对应的自动化配置,他并不是直接将所有自动化配置全部导入,在他每个自动化配置里面都有条件判断注解,先判断是否引入相互的jar包,再判断容器是否有bean再进行注入到bean容器