SpringBoot是对Spring的一种扩大,其中比拟重要的扩大性能就是主动拆卸:通过注解对罕用的配置做默认配置,简化xml配置内容。本文会对Spring的主动配置的原理和局部源码进行解析,本文次要参考了Spring的官网文档。
主动拆卸的组件
SpringBoot主动拆卸通过多局部组件协调实现,这些组件次要有上面几种,这几种组件之间协调工作,最终实现了SpringBoot的主动拆卸。
- @EnableAutoConfiguration:用于依据用户所援用的jar包主动拆卸Spring容器,比方用户在ClassPath中蕴含了HSQLDB,然而没有手动配置数据库连贯,那么Spring会主动应用HSQLDB作为数据源。
- @Condition:不同状况下依照条件进行拆卸,Spring的JdbcTemplate是不是在Classpath外面?如果是,并且DataSource也存在,就主动配置一个JdbcTemplate的Bean
- @ComponentScan:扫描指定包上面的@Component注解的组件。
@EnableAutoConfiguration注解
Spring的主动拆卸倒退大抵能够分为三个阶段:
- 全手工配置的XML文件阶段,用户须要的Bean全副须要在XML文件中申明,用户手工治理全副的Bean。
- 半手工配置的注解阶段,用户能够装置需要Enable对应的功能模块,如增加@EnableWebMvc能够启用MVC性能。
- 全自动配置的SpringBoot,用户只须要引入对应的starter包,Spring会通过factories机制主动拆卸须要的模块。
全手工配置的XML文件示意图:
半自动注解配置示意图:
全自动注解配置示意图:
Spring启用全自动配置性能的注解就是@EnableAutoConfiguration,利用增加了@EnableAutoConfiguration注解之后,会读取所有jar包上面的spring.factories文件,获取文件中配置的主动拆卸模块,而后去拆卸对应的模块。
@EnableAutoConfiguration的性能可总结为:使Spring启用factories机制导入各个starter模块的配置。
原理剖析
通过下面的剖析咱们晓得Spring的@EnableAutoConfiguration次要性能是使Spring启用factories机制导入各个starter模块的配置。上面咱们会对@EnableAutoConfiguration的源码进行简略剖析。
@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 {};}
@EnableAutoConfiguration注解的定义有两局部比拟重要的内容:
@AutoConfigurationPackage:将增加该注解的类所在的package作为主动配置package进行治理。
@Import({AutoConfigurationImportSelector.class}):用于导入factories文件中的AutoConfiguration。
@Import({AutoConfigurationImportSelector.class})
首先咱们须要晓得@Import注解的作用,从字面意思就可以看进去,@Import用于把一个Bean注入到Spring的容器中,@Import能够导入三种类型的Bean:
- 导入一般的Bean,通常是@Configuration注解的Bean,也能够是任意的@Component组件类型的类。
- 导入实现了ImportSelector接口的Bean,ImportSelector接口能够依据注解信息导入须要的Bean。
- 导入实现了ImportBeanDefinitionRegistrar注解的Bean, ImportBeanDefinitionRegistrar接口能够间接向容器中注入指定的Bean。
@Import({AutoConfigurationImportSelector.class})
中的AutoConfigurationImportSelector实现了ImportSelector接口,会依照注解内容去装载须要的Bean。
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { // 获取须要主动拆卸的AutoConfiguration列表 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); // 获取主动拆卸类的类名称列表 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } } // 获取须要主动拆卸的AutoConfiguration列表 protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { // 获取注解中的属性 AnnotationAttributes attributes = this.getAttributes(annotationMetadata); // 获取所有META-INF/spring.factories中的AutoConfiguration类 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); // 删除反复的类 configurations = this.removeDuplicates(configurations); // 获取注解中Execlud的类 Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); // 移除所有被Exclude的类 configurations.removeAll(exclusions); // 应用META-INF/spring.factories中配置的过滤器 configurations = this.getConfigurationClassFilter().filter(configurations); // 播送相干的事件 this.fireAutoConfigurationImportEvents(configurations, exclusions); // 返回符合条件的配置类。 return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }
@AutoConfigurationPackage
@AutoConfigurationPackage用于将增加该注解的类所在的package作为主动配置package进行治理,听起来是不是和@ComponentScan性能有所反复?咱们来剖析一下其具体实现,能够看到这个注解仍旧是通过@Import注解向容器中注册Bean。
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import({Registrar.class})public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class<?>[] basePackageClasses() default {};}
@AutoConfigurationPackage
注解导入了Registrar.class,其本质是一个ImportBeanDefinitionRegistrar,会把以后注解类所在的包注入到Spring容器中。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); } public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata)); }}
@ComponentScan并不会把类所在的包注入到容器中,@ComponentScan只注入指定的包。类所在的包通过@AutoConfigurationPackage注入。
@Conditional注解
@Conditional注解的组件只有在满足特定条件的状况下才会被注册到容器中,@Conditional注解的定义如下所示,能够看到这个注解只有一个内容:Condition,所以这个注解的重点就是Condition接口。
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Conditional { /** * All {@link Condition} classes that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value();}
Condition接口的定义如下所示,该接口只蕴含一个办法,输出以后的上下文信息和注解的参数,判断注解的Bean是否能够注册到Spring容器中。其中上下文信息蕴含了:Bean定义管理器(BeanDefinitionRegistry)/BeanFactory/上下文环境Environment/资源加载器ResourceLoader/类加载器ClassLoader。
@FunctionalInterfacepublic interface Condition { /** * Determine if the condition matches. * @param context the condition context * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class} * or {@link org.springframework.core.type.MethodMetadata method} being checked * @return {@code true} if the condition matches and the component can be registered, * or {@code false} to veto the annotated component's registration */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}
@Conditional判断机会
@Conditionl注解作用于Spring读取Bean定义的阶段,这个阶段大多数Bean尚未实例化,多数实例化的Bean属于Spring的非凡Bean,不能条件管制是否加载。
Spring中的Bean有很多起源,如扫描包下的Component、@Bean注解的办法、@Import、用户手工注册Bean等办法注册Bean,这些所有起源的Bean定义都能够应用@Conditional进行解决吗?答案是不是所有Bean定义起源都会应用@Conditional注解进行过滤,只有扫描包或者@Configuration注解类中的的Bean会应用@Conditionl注解进行判断。
扫描包注册Bean,咱们晓得基于注解的Spring容器会通过扫描包的模式去获取门路上面的Component,这部分Bean定义是最早被加载到Spring容器中的。Spring通过
AnnotatedBeanDefinitionReader
读取并注册@Component对应的Bean定义,其中@Conditional判断的局部逻辑如下:private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); // 此处判断Bean是否应该被注册 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } // 省略其它注册逻辑 }
@Bean办法注册,在@Configuration中的@Bean和不在@Configuration中的@Bean的模式不同,我在其它文章中有介绍,然而获取Bean定义的逻辑是统一的,@Bean注解的办法是通过
ConfigurationClassBeanDefinitionReader
去读取的,其中也蕴含了对@Conditional注解的判断,其中局部要害判断代码如下所示。private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); String methodName = metadata.getMethodName(); // Do we need to mark the bean as skipped by its condition? if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; } if (configClass.skippedBeanMethods.contains(methodName)) { return; }
@Configuration类中的@Bean注解是在
ConfigurationClassPostProcessor
中进行解析和注册的,这是一个BeanDefinitionRegistryPostProcessor
,执行机会显然晚于扫描包的机会。
从@Conditional的判断原理能够看出,Spring该当只容许Bean定义一个个进行注册,并且要严格保障读取程序,不容许Bean定义的批量注册。
@Conditional扩大
为了简化用户的应用,Spring提供了几种常见的@Conditional的实现,咱们下文中会介绍常见的几种实现。
- @ConditionalOnBean注解,当特定的Bean注解存在的时候注册以后Bean,应用这个注解的时候要注解Bean的先后顺序,因为@ConditionalOnBean会去以后容器中查找是否有满足条件的Bean,后注册的Bean会受先注册Bean的影响。
- @ConditionalOnClass注解,以后类门路中蕴含指定类的时候注册以后Bean。
- @ConditionalOnMissingBean注解,当指定类型的Bean不存在的时候注册以后Bean。
- @ConditionalOnMissingClass注解,以后类门路中不蕴含指定类的时候注册以后Bean。
- @ConditionalOnProperty注解,指定属性满足特定值是失效。
值得一提的是,@Profile注解自身也是应用@Conditional注解进行Bean的条件注册的。
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
@ComponentScan组件
有些状况下,咱们的组件不都定义在带有@EnableAutoConfiguration注解的类对应的包上面,这个时候@AutoConfigurationPackage扫描的类就不能满足用户的需要了。Spring提供了@ComponentScan组件让用户增加指定包上面的Spring组件到Spring容器中。
Filter选项
扫描包过程中,Spring容许用户依照条件过滤所须要的Bean,@SpringBootApplication中自身蕴含了@ComponentScan
注解,并为注解配置了两个Filter:TypeExcludeFilter和AutoConfigurationExcludeFilter注解。这两个注解容许用户实现特定的类,从而实现对Bean定义的过滤。
@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 { // 省略属性}
@Import原理
通过下面的内容,咱们晓得@Import在Spring的主动拆卸中有很重要的作用,用于主动拆卸过程中导入指定的配置类。接下来咱们剖析一下@Import注解的源码及其作用机制。
@Import的解析仍旧是在要害类ConfigurationClassPostProcessor
中进行的,ConfigurationClassPostProcessor
蕴含了@Bean、@Import等注解的解析。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } // 其它逻辑 }
本文最先公布至微信公众号,版权所有,禁止转载!