2.自动装配原理

文章详细解析了SpringBoot的自动装配机制,从@SpringBootApplication注解开始,介绍了@EnableAutoConfiguration如何启用自动配置,@EnableAutoConfiguration中的@AutoConfigurationPackage和@Import的作用,以及AutoConfigurationImportSelector类在自动装配场景组件中的工作流程。此外,还讨论了@EnableConfigurationProperties注解如何用于配置属性的绑定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自动装配原理

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注解请看上一章)

  1. @AutoConfigurationPackage:内部是@Import(AutoConfigurationPackages.Registrar.class),主要的作用是默认将主启动类所在包下所有用@Component、@Service、@Controller、@Repository标注的bean注入到IOC容器中,即自动装配自定义组件。(也可以在主启动类上用@ComponentScan来指定还要扫描哪里的包)

  2. @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类

  1. 加载classpath/META-INF/spring-autoconfigure-metadata.properties中的数据,这个文件存放的是key-value格式的“spring-boot所有默认支持的待自动装配类的过滤规则”,key格式为:全类名.ConditionalOnClass
  2. 加载classpath/META-INF/spring.factories中的数据,这个文件存放的是key-value格式的“spring-boot所有默认支持的待自动装配类”。使用的是java SPI机制(服务提供发现机制),利用SpringFactoriesLoader工具类,但是不会像ServiceLoader工具类那样一次性加载全部类,会根据key进行加载
  3. 去掉重复的配置信息,从spring.factories文件里获得key对应的value即所有自动装配引导类,并去掉一些重复的(因为有可能用户自定义引入了一些重复的类)
  4. 排除需要排除的类,具体操作是通过@SpringBootApplication 注解中的 exclude、excludeName。或者通过application.yaml中的 spring.autoconfigure.exclude配置
  5. 根据第1步的过滤规则,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
  6. 最后将过滤后的待自动装配类全部装配进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)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值