浅析 @Enable* 注解是怎样实现的

Spring 的项目中,我们经常会使用 @Enable 开头的注解到配置类中,添加了这种注解之后,便会开启一些功能特性。常用的注解如 @EnableWebMvc、@EnableTransactionManagement、@EnableAsync、@EnableScheduling 等等。集成到 Spring 的第三方的框架中,我们也经常会看到这样的注解,如阿里巴巴开源的缓存框架 jetcache,便需要使用注解 @EnableCreateCacheAnnotation 开启缓存能力。

@Enable* 注解实现分析

以 @EnableScheduling 注解为例,其源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

除了最常用的 Java 提供的元注解,这个 @EnableScheduling 使用了 @Import 注解,很明显 @Enable* 就是利用 @Import 注解实现的。查看 @Import 源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class<?>[] value();

}

@Import 可以标注在类型上,只有一个类型为 Class 的 value 属性,根据注释我们可以看到,value 属性值可以是 @Configuration 配置类、ImportSelector、ImportBeanDefinitionRegistrar 或者常规组件类。Spring 读取到这几个类型时的处理如下:

  • @Configuration 配置类:Spring 对配置类进行解析。
  • ImportSelector:实例化该接口实现类,提供元数据给接口中的方法,将返回的类型作为配置类进行解析。
  • ImportBeanDefinitionRegistrar:实例化该类型,提供元数据给接口中的方法,由用户自己控制注入哪些 bean。
  • 常规类:将该类作为bean进行注入。

value 值为配置类的时候 Spring 会对配置类进行解析,值为常规组件类的时候 Spring 会把组件类作为 Spring 的 bean 进行注入,这两个相对比较容易理解。下面对剩下的两个类型进行分析。

ImportSelector 定义

ImportSelector 接口定义如下:

public interface ImportSelector {

    /**
     * 选择并返回哪些类应该被导入,导入的类将作为配置类再次进行解析
     * AnnotationMetadata 是 @Configuration 配置类的元数据
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

    /**
     * 排除导入的类
     * @since 5.2.4
     */
    @Nullable
    default Predicate<String> getExclusionFilter() {
    	return null;
    }

}

ImportSelector#selectImports 方法中的参数 AnnotationMetadata 是使用 @Import 注解的配置类的元数据,返回值将作为配置类再次进行解析。如果返回值是实现 BeanFactoryPostProcessor 或者 BeanPostProcessor 接口的组件,我们将有机会在 Spring 应用上下文或 bean 的生命周期中做一些其他的操作。如果返回值是配置类,我们还能根据 @Conditional 注解根据条件注入一些 bean 。

示例如下:

public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"info.pigg.dict.entity.PiggPet","info.pigg.dict.entity.PiggUser"};
    }
}

ImportSelector#getExclusionFilter 方法则用于从导入的类中排除我们不想导入的类。

ImportBeanDefinitionRegistrar 定义

ImportBeanDefinitionRegistrar 定义如下:

public interface ImportBeanDefinitionRegistrar {
	// 注册 BeanDefinition
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {
		registerBeanDefinitions(importingClassMetadata, registry);
	}
	// 注册 BeanDefinition
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}
}

相比 ImportSelector 只能获取配置类的元数据,ImportBeanDefinitionRegistrar 接口提供了更为灵活的方法,还能收到参数 BeanDefinitionRegistry、BeanNameGenerator 。 BeanDefinitionRegistry 可以方便我们获取已注册的 BeanDefinition,注册自己定义的 BeanDefinition。BeanNameGenerator 则用于生成 bean 的名称。

@Import 实现分析

通过上面的描述,我们知道 @Enable* 注解是通过 @Import 实现的,而 @Import 的处理在 Spring 中的配置类解析过程中。解析 @Import 注解的核心代码位置为 ConfigurationClassParser#processImports,源码如下:

/**
 * 处理 @Import 注解
 *
 * @param configClass             标注或元标注 @Import 的配置类
 * @param currentSourceClass      配置类对应的 SourceClass
 * @param importCandidates        @Import 注解 value 属性值集合或 ImportSelector.selectImports 返回值
 * @param exclusionFilter         不要处理配置类的过滤器
 * @param checkForCircularImports 是否检查循环 @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));
	} else {
		this.importStack.push(configClass);
		try {
			for (SourceClass candidate : importCandidates) {
				// @Import value 属性值是 ImportSelector.class
				if (candidate.isAssignable(ImportSelector.class)) {
					// Candidate class is an ImportSelector -> delegate to it to determine imports
					Class<?> candidateClass = candidate.loadClass();
					ImportSelector selector = ParserStrategyUtils
							.instantiateClass(candidateClass, ImportSelector.class,
									this.environment, this.resourceLoader, this.registry);
					Predicate<String> selectorFilter = selector.getExclusionFilter();
					if (selectorFilter != null) {
						exclusionFilter = exclusionFilter.or(selectorFilter);
					}
					if (selector instanceof DeferredImportSelector) {
						// @Import value 属性值是 DeferredImportSelector.class
						this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
					} else {
						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames,
								exclusionFilter);
						// 递归处理 ImportSelector 返回的配置类
						processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter,
								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 =
							ParserStrategyUtils
									.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
											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), exclusionFilter);
				}
			}
		} 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();
		}
	}
}

processImports 方法整体结构较为清晰,对 @Import 注解的 value 属性值进行循环处理:

  • 首先判断是否为 ImportSelector 类型,如果是则调用 ImportSelector#selectImports 方法,并将获取到的类型作为配置类,进行递归解析 @Import。
  • 否则如果类型为 ImportBeanDefinitionRegistrar,则对其实例化,并添加到 importBeanDefinitionRegistrars 中,后面将统一进行处理。
  • 否则将导入的类作为 bean 进行注册,然后作为配置类递归进行处理。

实现自己的 @Enable* 注解

下面尝试实现一个 @EnableHelloWorld ,配置类上使用了该注解后将自动注入一个 HelloWorld 的 bean。代码如下:

public class HelloWorld {
    public void sayHello() {
        System.out.println("hello,world");
    }
}


public class HelloWorldSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] { HelloWorld.class.getName() };
    }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(HelloWorldSelector.class)
public @interface EnableHelloWorld {
}

@EnableHelloWorld
@Configuration
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("com.zzuhkp");
        context.refresh();
        context.getBean(HelloWorld.class).sayHello();
    }
}

程序执行后成功打印出“hello,world”,表明自定义 @Enable* 成功生效。

@Enable* 注解通过 @Import 注解实现。@Import 注解在配置类解析过程中进行处理。通过 @Import 可以方便的注入自定义的 bean 。

 

 

posted @   残城碎梦  阅读(209)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示