Spring-Enable模块驱动

本文深入探讨Spring框架中的@Enable模块驱动机制,介绍其工作原理,包括如何通过注解驱动和接口编程两种方式激活模块,以及@Import注解在过程中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.理解Enable模块驱动

  从Spring 3.1开始,Spring Framework开始支持“@Enable模块驱动”,所谓“模块”是指具备相同领域的功能组件集合,组合所形成的一个独立单元,比如Web MVC模块,AspectJ代理模块、Caching(缓存)模块等。
在Spring框架中,有着许多模块化的Annotation,这些注解均已@Enable为前缀。

框架实现@Enable模块注解激活模块
Spring Framework@EnableWebMvcWeb MVC模块
@EnableTransactionManagement事物管理模块
@EnableCachingCaching模块
@EnableMbeanExportJMX模块
@EnableAsync异步处理模块
@EnableWebFluxWebFlux模块
@EnableAspectJautoProxyAspectJ代理模块
Spring Boot@EnableAutoConfiguration自动装配模块
@EnableManagementContextActuator管理模块
@EnableConfigurationProperties配置属性绑定模块

  “Enable模块驱动”的意义能够简化配置步骤,实现“按需装配”,同时屏蔽组件集合装配的细节。Srping实现Enable模块驱动的方法大致分为两类,分别“注解驱动”和“接口编程”,无论哪种方式,均依赖于@Import注解。@Import注解用于导入一个和多个ConfigurationClass,将其注册为SpringBean。

2.基于“注解驱动”实现@Enable模块

  对于已“注解驱动”实现的方式,我们可以先参考Spring中已有的实现,录入@EnableWebMvc。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

  很明显,EnableWebMvc注解Impot了DelegatingWebMvcConfiguration类,
而DelegatingWebMvcConfiguration是一个@Configuration类。

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport{
    ...
}

  通过模仿EnableWebMvc,我们可以自己写一个简单的@Enable“注解驱动”的实现。先写一个@Configuration类

@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String helloWorld() {
        return "Hello World";
    }
}

  定义对应的@Enable注解@EnableHelloWorld

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

  最后标注@EnableHelloWorld到对应引导类上,并运行观察结果

@EnableHelloWorld
public class EnableHelloWorldBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(EnableHelloWorldBootstrap.class);
        context.refresh();
        String helloWorld = context.getBean("helloWorld", String.class);
        System.out.println(helloWorld);
    }
}
//输出:Hello World

3.基于“接口编程”实现@Enable模块

  相对于“注解驱动”,“接口编程”的实现方式更为复杂。同样的我们先参考Spring现有实现@EnableCaching。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching{
    ...
}

  看上去和之前的@EnableWebMvc没啥区别,我们再看看Import的CachingConfigurationSelector.class类

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    ...
    @Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}
    ...
}

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
    ...
    @Override
	public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
		Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
		Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");

		AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
		if (attributes == null) {
			throw new IllegalArgumentException(String.format(
					"@%s is not present on importing class '%s' as expected",
					annType.getSimpleName(), importingClassMetadata.getClassName()));
		}

		AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
		String[] imports = selectImports(adviceMode);
		if (imports == null) {
			throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
		}
		return imports;
	}

	@Nullable
	protected abstract String[] selectImports(AdviceMode adviceMode);
}

  我们可以看到,CachingConfigurationSelector以及其父类AdviceModeImportSelector都没有标注注解@Configuration。这个@Import的性质有关。@Imprt注解除了传一个@Configuration类外,同时也支持传ImportSelector或者ImportBeanDefinitionRegistrar的实现类。而AdviceModeImportSelector,正是实现了ImportSelector接口。相较于“注解驱动”,实现接口的方式弹性更大,同时也更为复杂。

  • 基于ImportSelector接口的自定义实现

先定义接口,组件以及注解

public interface Server {

    void start();

    enum ServerType {

        HTTP,
        FTP
    }
}

##################################################################

@Component
public class HttpServer implements Server{

    @Override
    public void start() {
        System.out.println("Http");
    }
}

##################################################################

public class FtpServer implements Server {

    @Override
    public void start() {
        System.out.println("FtpServer");
    }
}

##################################################################
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Import(ServerImportSelector.class)
public @interface EnableServer {

    Server.ServerType type() default Server.ServerType.HTTP;
}

再定义关键的ImportSelector实现类

public class ServerImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String importClassName = "";
        Map<String, Object> attribute = importingClassMetadata.getAnnotationAttributes(EnableServer.class.getName());
        Server.ServerType type = (Server.ServerType) attribute.get("type");
        switch (type) {
            case FTP:
                importClassName = FtpServer.class.getName();
                break;
            case HTTP:
                importClassName = HttpServer.class.getName();
                break;
            default:
                break;
        }
        return new String[]{importClassName};
    }
}

最后引导类及运行输出

@EnableServer(type = Server.ServerType.FTP)
public class EnableServerBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(EnableServerBootstrap.class);
        context.refresh();
        Server bean = context.getBean(Server.class);
        bean.start();
        context.close();
    }
}

//输出:FtpServer

  • 基于ImportBeanDefinitionRegistrar接口的自定义实现

沿用之前的Server例子,实现ImportBeanDefinitionRegistrar接口

public class ServerImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ServerImportSelector selector = new ServerImportSelector();
        String[] classNames = selector.selectImports(importingClassMetadata);
        Stream.of(classNames).map(BeanDefinitionBuilder::genericBeanDefinition)
                .map(BeanDefinitionBuilder::getBeanDefinition)
                .forEach(definition -> BeanDefinitionReaderUtils.registerWithGeneratedName(definition, registry));
    }
}

修改EnableServer,替换@Import

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

    Server.ServerType type() default Server.ServerType.HTTP;
}

4.@Enable模块驱动原理

  在全文的讨论中,我们可以很明显的看到,@Enable模块驱动最重要的就是@Import注解的作用。

  从context:annotation-config/标签入手,该标签作用是启用注解。通过Spring Framework “可扩展XML编写”的特性,可知道context:annotation-config/所对应的BeanDefinitionParser实现为AnnotationConfigBeanDefinitionParser。

public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {

	@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		Object source = parserContext.extractSource(element);

		// Obtain bean definitions for all relevant BeanPostProcessors.
		Set<BeanDefinitionHolder> processorDefinitions =
				AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);
				...
		return null;
	}
}

  这里Spring并未直接进行解析,而是通过AnnotationConfigUtils.registerAnnotationConfigProcessors注册了@Configuration的处理实现,ConfigurationClassPostProcessor。当然,还有其他一些BeanFactoryPostProcessor,不过这里我们重点关注ConfigurationClassPostProcessor

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {
		...
		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		...
		return beanDefs;
	}

   在所有bean的配置加载完毕,任何一个bean创建之前调用,Spring会依次调用所有注册到Spring容器中的BeanFactoryPostProcessor类postProcessBeanFactory方法。ConfigurationClassPostProcessor的postProcessBeanFactor中,就包含了对@Import注解的解析。
   同样的,处理解析xml标签外,在AnnotationConfigApplicationContext的构造函数中,同样会调用AnnotationConfigUtils.registerAnnotationConfigProcessors注册。

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    ...
	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
	...
}

###############################################################################

public class AnnotatedBeanDefinitionReader {
    ...
	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	    ...
		this.registry = registry;
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
	...
}

  接着我们重点关注ConfigurationClassPostProcessor#postProcessBeanFactor方法

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		int factoryId = System.identityHashCode(beanFactory);
		...
		if (!this.registriesPostProcessed.contains(factoryId)) {
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}
	    ...
	}
	
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			    ...
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

	    ...
		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			parser.parse(candidates);
			parser.validate();
		...
		}
		while (!candidates.isEmpty());
		...
	}

  执行期间,最重要的组件莫过于ConfigurationClasParser,它对已注册的Spring BeanDefinition进行注解的解析。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			...
		}
		this.deferredImportSelectorHandler.process();
	}

protected final void parse(Class<?> clazz, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(clazz, beanName));
	}

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

  两个parse的重载方法分别采用CGLIB实现的AnnotationMetadataReadingVisitor和Java反射实现的StandardAnnotationMetadata;

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
	    ...
		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}
	
	@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
		throws IOException {
	    ...
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true);
        ...
		return null;
	}
	
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 (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(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));
					}
				}
			}
			...
			finally {
				this.importStack.pop();
			}
		}
	}

  在processImports中,若@Import的赋值是否为ImportSelector或者ImportBeanDefinitionRegistrar实现,那么会执行ImportSelector和ImportBeanDefinitionRegistrar的处理。同时这里也是一个递归的调用,实现多层次@Import元标注的ConfigurationClass的解析。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值