Spring组合注解与@Enable*注解
这篇文章主要谈论Spring中的注解,在近端发型的新版本的都是以注解为主,代替了xml配置,这也简化了开发的代码量。
以及开启某项功能的支持用的@Enable*等,是如何实现的。
组合注解
如果一个类或者方法前使用多个注解,也会有些繁琐,Spring设计原则中就是要消除代码
元注解就是可以注解到别的注解上的注解,被注解的注解称为组合注解,Spring很多注解可以作为元注解,而且有很多的组合注解,如@Configuration就是一个组合@Component注解,
代码示例
首先写的小例子字演示一下,通过常用的组合Configuration和ComponentScan元注解为@MyConfiguration
自定义注解
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 组合Configuration和ComponentScan元注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@ComponentScan
public @interface MyConfiguration {
// 覆盖value参数
String[] value() default {};
}
配置类
/**
* 使用新的注释代替原有 Configuration 和ComponentScan
*/
@MyConfiguration("con.ch3.annotation")
public class DemoConfig {
}
服务类
@Service
public class DemoService {
public void outputResult() {
System.out.println("通过组合注释配置获得Bean");
}
}
方法调用入口
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @description:
* @author: Shenshuaihu
* @version: 1.0
* @data: 2019-06-03 22:17
*/
public class AnnotationMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(DemoConfig.class);
DemoService service = context.getBean(DemoService.class);
service.outputResult();
context.close();
}
}
打印结果
onnected to the target VM, address: '127.0.0.1:58117', transport: 'socket'
六月 10, 2019 10:36:25 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@64616ca2: startup date [Mon Jun 10 10:36:25 CST 2019]; root of context hierarchy
六月 10, 2019 10:36:26 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@64616ca2: startup date [Mon Jun 10 10:36:25 CST 2019]; root of context hierarchy
通过组合注释配置获得Bean
Disconnected from the target VM, address: '127.0.0.1:58117', transport: 'socket'
Process finished with exit code 0
这样便可以用自定义的注解代替类或者方法比较繁杂的注解,一般公司的研发框架回大量使用这样的组合注解
@Enable*
接下来再介绍另外一种开启某方法的注解,@EnableAsync ,@EnableScheduling等等,分为三类不同的方法,开启特定的功能
第一类直接导入配置类
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.SchedulingConfiguration;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}
package org.springframework.scheduling.annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.scheduling.config.TaskManagementConfigUtils;
/**
* {@code @Configuration} class that registers a {@link
* ScheduledAnnotationBeanPostProcessor} bean capable of processing Spring's @{@link
* Scheduled} annotation.
*
* <p>This configuration class is automatically imported when using the @{@link
* EnableScheduling} annotation. See {@code @EnableScheduling} Javadoc for complete usage
* details.
*
* @author Chris Beams
* @since 3.1
* @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor
*/
@Configuration
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
该注释首先导入了 SchedulingConfiguration,开启了 Configuration,注册了 ScheduledAnnotationBeanPostProcessor Bean
第二类依据条件选择配置类
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.AsyncConfigurationSelector;
/**
* 第二类:依据条件选择配置类
* AsyncConfigurationSelector 通过条件来选择需要导入的配置类
* 继承 AdviceModeImportSelector 又实现了ImportSelector接口
* 接口重写selectImports方法 进行事先条件判断
* PROXY 或者 ASPECTJ 选择不同的配置类
*
* @author ShenShuaihu
* @version 1.0
* @date 23:26
*/
@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 2147483647;
}
AsyncConfigurationSelector类
package org.springframework.scheduling.annotation;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AdviceModeImportSelector;
/**
* Selects which implementation of {@link AbstractAsyncConfiguration} should be used based
* on the value of {@link EnableAsync#mode} on the importing {@code @Configuration} class.
*
* @author Chris Beams
* @since 3.1
* @see EnableAsync
* @see ProxyAsyncConfiguration
*/
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
/**
* {@inheritDoc}
* @return {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration} for
* {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()}, respectively
*/
@Override
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;
}
}
}
ImportSelector 接口
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotationMetadata;
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
接口实现的方法
@Override
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annoType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
Assert.notNull(attributes, String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName());
String[] imports = selectImports(adviceMode);
Assert.notNull(imports, String.format("Unknown AdviceMode: '%s'", adviceMode));
return imports;
}
// selectImports 方法
@Override
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 又实现了ImportSelector接口,接口重写selectImports方法 进行事先条件判断 PROXY 或者 ASPECTJ 选择不同的配置类
第三类动态注册Bean
import org.springframework.context.annotation.Import;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
}
AspectJAutoProxyRegistrar类
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* Registers an {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
* AnnotationAwareAspectJAutoProxyCreator} against the current {@link BeanDefinitionRegistry}
* as appropriate based on a given @{@link EnableAspectJAutoProxy} annotation.
*
* @author Chris Beams
* @since 3.1
* @see EnableAspectJAutoProxy
*/
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}
registerOrEscalateApcAsRequired方法
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
} else {
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", -2147483648);
beanDefinition.setRole(2);
registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
return beanDefinition;
}
}
AspectJAutoProxyRegistrar 事先实现了ImportBeanDefinitionRegistrar 接口
作用是在运行时自动添加Bean到已有的配置类,通过重写 registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2)
AnnotationMetadata 获取当前配置类上的注解,BeanDefinitionRegistry 用来注册Bean
更多参考:
Springboot @Enable*注解的工作原理 https://www.jianshu.com/p/1241c1079dd6
网友评论