欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-02-16 10:50:46
...

本文介绍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的方式确认我们的猜想。下图是调试过程中的调用关系图。

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

我们只分析下最后一个方法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 其次,直接引入配置类灵活性最低。