Spring定义Bean类(二) Enable*注解装配
本文介绍Spring中以
Enable*
开头的注解是如何使用的,以及解析其原理,主要有以下内容
- 使用方式
- 实现原理
- 扩展
- 总结
使用方式
Spring中可以通过使用Enable*
注解,开启一项功能,比如@EnableScheduling
、 @EnableAsync
、@EnableCache
、 @EnableWebMvc
等,这样使用的好处不言而喻,可以大大减少相关配置的引入,简化了使用的难度,使代码易读。通过观察@Enable*
注解可以发现他们都有一个公共的注解@Import
注解,这个注解是用来自动引入一些配置的Bean,这样就可以方便的引入@Enable*
功能相关的Bean。
通过@Import
导入类的方式有三种,分别是:
- 直接引入配置类
- 引入
org.springframework.context.annotation.ImportSelector
接口的实现 - 引入
org.springframework.context.annotation.ImportBeanDefinitionRegistrar
接口的实现
下面分别说明这三种方式的使用。
1. 直接引入配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
@EnableScheduling
注解的 @Import
注解直接引入了配置类 @SchedulingConfiguration
,这个配置类将会作为Bean被Spring容器管理。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
2. 引入 ImportSelector
接口的实现
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
EnableAsync
注解的@Import
注解引入了 ImportSelector
接口的实现类AsyncConfigurationSelector
,将会调用 ImportSelector#selectImports
方法,选择性的返回一个数组,作为Spring容器管理的Bean。
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
@Override
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] { ProxyAsyncConfiguration.class.getName() };
case ASPECTJ:
return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
default:
return null;
}
}
}
AsyncConfigurationSelector
继承自 AdviceModeImportSelector
, 而 AdviceModeImportSelector
实现了 ImportSelector
。
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {}
3. 引入 ImportBeanDefinitionRegistrar
接口的实现
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
EnableAspectJAutoProxy
注解的@Import
注解引入了 ImportBeanDefinitionRegistrar
接口的实现类AspectJAutoProxyRegistrar
,将会调用 ImportBeanDefinitionRegistrar#registerBeanDefinitions
方法,通过在代码中注册Bean,成为Spring容器管理的Bean。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
以上三种方式都可以作为@Import
注解的 value
值,他们有不同的用途
- 直接引入配置类,可以直接将类作为配置类,具有和注解
@Configuration
相似的作用,可以在配置类中,定义@Bean
注解,定义新的Bean。 - 引入
ImportSelector
接口的实现,此类可以返回一个字符串数组,表示需要被Spring管理的Bean类,可以通过逻辑判断,返回哪些类。 - 引入
ImportBeanDefinitionRegistrar
接口的实现,这种方式可以手动注册BeanDefinition
,灵活定义Bean的名称,以及BeanDefinition
的定义。
实现原理
谈到@Enable*
的原理,我们必须要看@Import
注解的使用,@Import
引入类的三种方式,是实现@Enable*
的重点内容,我们首先查看 @Import
注解在哪里使用,进一步通过Debug的方式确认我们的猜想。下图是调试过程中的调用关系图。
我们只分析下最后一个方法org.springframework.context.annotation.ConfigurationClassParser#processImports
这个方法实现了上一节中使用的三种方式。源码如下:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
首先看下这个方法的参数意义:
- 参数一,
ConfigurationClass configClass
表示@Enable*
注解的Bean。 - 参数二,
SourceClass currentSourceClass
表示被ConfigurationClassParser
包装的Bean类 - 参数三,
Collection<SourceClass> importCandidates
表示@Import
注解的value
值集合 - 参数四,
boolean checkForCircularImports
表示是否循环检查Import类
看完参数后,最重要的代码莫过于14行for
循环的内容,有三个判断,以此完成以下判断:
- 首先判断的是
@Import
引入的是否为ImportSelector
的实现类 - 其次判断是否为
ImportBeanDefinitionRegistrar
的实现类 - 若以上判断都为
false
,则直接注册为Bean类
以上就是@Import
的三种值的实现方式,也可以从以上内容,看出三种方式的优先级。
扩展
了解了实现原理后,我们来扩展线自己的@Enable*
注解
1. 扩展直接引入配置类
编写@EnableHelloWorld
注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
实现HelloWorldConfiguration
普通配置类
public class HelloWorldConfiguration {
@Bean
public String helloWorld() { // 方法名即 Bean 名称
return "Hello,World 2018";
}
}
在启动类中使用@Enable
注解,就可以获取到helloWorld
的Bean。
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// helloWorld Bean 是否存在
String helloWorld = context.getBean("helloWorld", String.class);
System.out.println("helloWorld Bean : " + helloWorld);
// 关闭上下文
context.close();
}
}
2. 扩展引入 ImportSelector
接口的实现
使用同样的@EnableHelloWorld
注解,只是变更下@Import
注解的内容
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
实现ImportSelector
接口,即类HelloWorldImportSelector
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
使用上小结同样的启动类,可获取同样的结果。
3. 扩展引入 ImportBeanDefinitionRegistrar
接口的实现
使用同样的@EnableHelloWorld
注解,再次变更下@Import
注解的内容
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldImportBeanDefinitionRegistrar.class)
public @interface EnableHelloWorld {
}
实现ImportBeanDefinitionRegistrar
接口,即类HelloWorldImportBeanDefinitionRegistrar
public class HelloWorldImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HelloWorldConfiguration.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
registry.registerBeanDefinition(HelloWorldConfiguration.class.getName(), beanDefinition);
}
}
使用上小结同样的启动类,可获取同样的结果。
以此完成三种方式的扩展,实现相同功能的@EnableHelloWorld
功能
总结
-
@Enable*
注解主要通过@Import
注解实现 -
@Import
注解引入的类有三种方式- 直接引入配置类
- 引入
ImportSelector
接口的实现 - 引入
ImportBeanDefinitionRegistrar
接口的实现
-
@Import
实现的三种方式,原理在中org.springframework.context.annotation.ConfigurationClassParser#processImports
判断 - 通过实现原理可以看出三种方式的优先级,首先判断
ImportSelector
方式,其次判断ImportBeanDefinitionRegistrar
方式,最后判断直接引入配置类方式。 - 针对以上三种方式,可以实现相同的功能,但是相对来说,
@ImportBeanDefinitionRegistrar
最灵活,ImportSelector
其次,直接引入配置类灵活性最低。
上一篇: JZOJ5625. 【NOI2018模拟4.3】Max
下一篇: 在WPF中使用字体图标