Spring定义Bean类(二) Enable*注解装配

本文介绍Spring中以Enable* 开头的注解是如何使用的,以及解析其原理,主要有以下内容

  • 使用方式
  • 实现原理
  • 扩展
  • 总结

使用方式

Spring中可以通过使用Enable* 注解,开启一项功能,比如@EnableScheduling@EnableAsync@EnableCache@EnableWebMvc 等,这样使用的好处不言而喻,可以大大减少相关配置的引入,简化了使用的难度,使代码易读。通过观察@Enable* 注解可以发现他们都有一个公共的注解@Import 注解,这个注解是用来自动引入一些配置的Bean,这样就可以方便的引入@Enable* 功能相关的Bean。

通过@Import 导入类的方式有三种,分别是:

  1. 直接引入配置类
  2. 引入 org.springframework.context.annotation.ImportSelector 接口的实现
  3. 引入 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 值,他们有不同的用途

  1. 直接引入配置类,可以直接将类作为配置类,具有和注解@Configuration 相似的作用,可以在配置类中,定义 @Bean 注解,定义新的Bean。
  2. 引入 ImportSelector 接口的实现,此类可以返回一个字符串数组,表示需要被Spring管理的Bean类,可以通过逻辑判断,返回哪些类。
  3. 引入 ImportBeanDefinitionRegistrar 接口的实现,这种方式可以手动注册BeanDefinition ,灵活定义Bean的名称,以及BeanDefinition 的定义。

实现原理

谈到@Enable* 的原理,我们必须要看@Import 注解的使用,@Import 引入类的三种方式,是实现@Enable* 的重点内容,我们首先查看 @Import 注解在哪里使用,进一步通过Debug的方式确认我们的猜想。下图是调试过程中的调用关系图。

image

我们只分析下最后一个方法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();
			}
		}
	}

首先看下这个方法的参数意义:

  1. 参数一,ConfigurationClass configClass 表示@Enable* 注解的Bean。
  2. 参数二,SourceClass currentSourceClass 表示被ConfigurationClassParser 包装的Bean类
  3. 参数三,Collection<SourceClass> importCandidates 表示@Import 注解的value 值集合
  4. 参数四,boolean checkForCircularImports 表示是否循环检查Import

看完参数后,最重要的代码莫过于14行for 循环的内容,有三个判断,以此完成以下判断:

  1. 首先判断的是@Import 引入的是否为ImportSelector 的实现类
  2. 其次判断是否为ImportBeanDefinitionRegistrar 的实现类
  3. 若以上判断都为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 功能

总结

  1. @Enable* 注解主要通过@Import 注解实现
  2. @Import 注解引入的类有三种方式
    1. 直接引入配置类
    2. 引入 ImportSelector 接口的实现
    3. 引入 ImportBeanDefinitionRegistrar 接口的实现
  3. @Import 实现的三种方式,原理在中org.springframework.context.annotation.ConfigurationClassParser#processImports判断
  4. 通过实现原理可以看出三种方式的优先级,首先判断ImportSelector 方式,其次判断ImportBeanDefinitionRegistrar 方式,最后判断直接引入配置类方式。
  5. 针对以上三种方式,可以实现相同的功能,但是相对来说,@ImportBeanDefinitionRegistrar 最灵活,ImportSelector 其次,直接引入配置类灵活性最低。
posted @ 2022-09-28 21:06  Little_Monster-lhq  阅读(71)  评论(0编辑  收藏  举报