自动装配原理
文章目录
1.@EnableAutoConfiguration
Spring中将bean注入到IOC容器中的行为叫做装配,SpringBoot的自动装配是围绕着@EnableAutoConfiguration注解来实现的
①首先从主启动类开始
@SpringBootApplication
public class MainApplication{
public static void main(Object[] args){
SpringBootApplication.run(MainApplication.class,args);
}
}
②进入到SpringBootApplication注解内,发现用到了@SpringBootConfiguration、@EnableAutoConfiguration注解。
@SpringBootConfiguration表明这是一个配置类,因为@SpringBootConfiguration = @Configuration
@EnableAutoConfiguration表明开启自动装配,主要分为自动装配自定义组件和场景组件
@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 {
//@AliasFor 代表等价的意思 即@SpringBootApplication(exclude={}) == @EnableAutoConfiguration(exclude={})
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
③⭐@EnableAutoConfiguration注解
包含了两个重要注解,一个是@AutoConfigurationPackage,一个是@Import(⭐关于@Import注解请看上一章)
-
@AutoConfigurationPackage:内部是@Import(AutoConfigurationPackages.Registrar.class),主要的作用是
默认
将主启动类所在包下所有用@Component、@Service、@Controller、@Repository标注的bean注入到IOC容器中,即自动装配自定义组件
。(也可以在主启动类上用@ComponentScan来指定还要扫描哪里的包) -
@Import(AutoConfigurationImportSelector.class):AutoConfigurationImportSelector这个类会
自动装配场景组件
@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 {};
}
④⭐AutoConfigurationImportSelector类
- 加载classpath/META-INF/spring-autoconfigure-metadata.properties中的数据,这个文件存放的是key-value格式的“
spring-boot所有默认支持的待自动装配类的过滤规则
”,key格式为:全类名.ConditionalOnClass - 加载classpath/META-INF/spring.factories中的数据,这个文件存放的是key-value格式的“
spring-boot所有默认支持的待自动装配类
”。使用的是java SPI机制(服务提供发现机制),利用SpringFactoriesLoader工具类,但是不会像ServiceLoader工具类那样一次性加载全部类,会根据key进行加载 - 去掉重复的配置信息,从spring.factories文件里获得key对应的value即所有自动装配引导类,并去掉一些重复的(因为有可能用户自定义引入了一些重复的类)
- 排除需要排除的类,具体操作是通过@SpringBootApplication 注解中的 exclude、excludeName。或者通过application.yaml中的 spring.autoconfigure.exclude配置
- 根据第1步的过滤规则,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
- 最后将过滤后的待自动装配类全部装配进IOC容器
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
//重写ImportSelector接口的selectImports方法,将会把return的东西注入到容器中
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
// 加载META-INF/spring-autoconfigure-metadata.properties 下的元数据信息
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取候选加载的配置信息 META-INF/spring.factories
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去掉重复的配置信息
configurations = this.removeDuplicates(configurations);
// 排序
configurations = this.sort(configurations, autoConfigurationMetadata);
// 获取 注解中配置的 exclusion 信息
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查
this.checkExcludedClasses(configurations, exclusions);
// 移除需要排除的信息
configurations.removeAll(exclusions);
// 过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
configurations = this.filter(configurations, autoConfigurationMetadata);
// 广播事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回要被加载的类数组
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
}
2.@EnableConfigurationProperties
从上一步@EnableAutoConfiguration注解得知,会从spring.facorties中自动装配一些类,那么这些类中的一些属性又是怎么赋值上去的呢?主要就是使用@EnableConfigurationProperties注解的自动赋值。以RedisAutoConfiguration为例
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {}
@EnableConfigurationProperties:发现它内部导入了一个EnableConfigurationPropertiesImportSelector这个类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
Class<?>[] value() default {};
}
EnableConfigurationPropertiesImportSelector:可以看到实现了ImportSelector接口,重写selectImports方法,将会将一些类装配到IOC容器中,那么是哪些类呢?根据代码可以看到,会去取@EnableConfigurationProperties注解的value值,此例中是RedisProperties,所以会把RedisProperties这个类装配进IOC容器
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
private static final String[] IMPORTS = {
ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}
/**
* {@link ImportBeanDefinitionRegistrar} for configuration properties support.
*/
public static class ConfigurationPropertiesBeanRegistrar
implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
getTypes(metadata).forEach((type) -> register(registry,
(ConfigurableListableBeanFactory) registry, type));
}
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
EnableConfigurationProperties.class.getName(), false);
return collectClasses((attributes != null) ? attributes.get("value")
: Collections.emptyList());
}
private List<Class<?>> collectClasses(List<?> values) {
return values.stream().flatMap((value) -> Arrays.stream((Object[]) value))
.map((o) -> (Class<?>) o).filter((type) -> void.class != type)
.collect(Collectors.toList());
}
private void register(BeanDefinitionRegistry registry,
ConfigurableListableBeanFactory beanFactory, Class<?> type) {
String name = getName(type);
if (!containsBeanDefinition(beanFactory, name)) {
registerBeanDefinition(registry, name, type);
}
}
private String getName(Class<?> type) {
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type,
ConfigurationProperties.class);
String prefix = (annotation != null) ? annotation.prefix() : "";
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName()
: type.getName());
}
private boolean containsBeanDefinition(
ConfigurableListableBeanFactory beanFactory, String name) {
if (beanFactory.containsBeanDefinition(name)) {
return true;
}
BeanFactory parent = beanFactory.getParentBeanFactory();
if (parent instanceof ConfigurableListableBeanFactory) {
return containsBeanDefinition((ConfigurableListableBeanFactory) parent,
name);
}
return false;
}
private void registerBeanDefinition(BeanDefinitionRegistry registry, String name,
Class<?> type) {
assertHasAnnotation(type);
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
registry.registerBeanDefinition(name, definition);
}
private void assertHasAnnotation(Class<?> type) {
Assert.notNull(
AnnotationUtils.findAnnotation(type, ConfigurationProperties.class),
() -> "No " + ConfigurationProperties.class.getSimpleName()
+ " annotation found on '" + type.getName() + "'.");
}
}
}
RedisProperties:这个类用到了@ConfigurationProperties(prefix = “spring.redis”),这个注解会到我们的配置文件中找对应prefix的配置然后赋值。这个RedisProperties类在之后的其它相关redis配置类构造时会用到
@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 password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Whether to enable SSL support.
*/
private boolean ssl;
/**
* Connection timeout.
*/
private Duration timeout;
private Sentinel sentinel;
private Cluster cluster;
private final Jedis jedis = new Jedis();
private final Lettuce lettuce = new Lettuce();
}
补充:classpath: java/ resources/ jar/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J36GbPJv-1686813419729)(C:\Users\10059\AppData\Roaming\Typora\typora-user-images\image-20220726103520641.png)]
.
*/
private Duration timeout;
private Sentinel sentinel;
private Cluster cluster;
private final Jedis jedis = new Jedis();
private final Lettuce lettuce = new Lettuce();
}
---
补充:classpath: java/ resources/ jar/
[外链图片转存中...(img-J36GbPJv-1686813419729)]