Spring EnableXX注解原理及应用
Spring提供了一系列以Enable开头的注解,这些注解本质上是激活Spring的某些管理功能。例如@EnableWebMvc注解引入了MVC框架在Spring应用中需要用到的所有bean,@EnableAsync注解可以使Bean在spring应用中支持异步功能,@EnableTransactionManagement开启事务支持。打开这些注解的源码不难发现这些@EnableXX注解的定义都包含一个@Import注解,通过导入一些配置类来完成特定的功能。
@Import注解导入配置方式的三种类型
第一类 配置类
例如,@EnableScheduling中直接导入配置类SchedulingConfiguration,这个类注解了@Configuration,且注册了一个scheduledAnnotationProcessor的Bean。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
我们可以仿照着写一个类似的demo,将自定义的注解加在配置类上即可加载bean到spring容器。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ConfigurationDemo.class)
public @interface EnableConfigDemo {
String value() default "";
}
@Configuration
public class ConfigurationDemo {
@Bean
public TestBean01 getTestBean01(){
return new TestBean01();
}
}
public class TestBean01 {
public void sayHello(){
System.out.println("hello ,I am TestBean01");
}
}
第二类 ImportSelector的实现类
@Import导入ImportSelector的实现类时,Spring会把selectImport方法的返回值对应的Bean注入到Spring容器(其核心原理是spring的BeanFactoryPostProcessor),例如@EnableAsync、@EnableTransactionManagement
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
private String determineTransactionAspectClass() {
return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
}
}
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;
}
}
}
自己动手写一个demo
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SelectorDemo.class)
public @interface EnableSelectorDemo {
String dataSourcetype() default "druid";
}
public class SelectorDemo implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableSelectorDemo.class.getName(), false);
AnnotationAttributes annotationAttributes1 = AnnotationAttributes.fromMap(annotationAttributes);
String dataSourcetype = annotationAttributes1.getString("dataSourcetype");
if("druid".equals(dataSourcetype)){
return new String[]{"com.example.beans.DruidDataSource"};
}else{
return new String[]{"com.example.beans.HpDataSource"};
}
}
}
第三类 动态注册Bean
@Import导入ImportBeanDefinitionRegistrar的实现类,通过重写方法registerBeanDefinitions()注入bean(其核心原理也是spring的BeanFactoryPostProcessor,是spring的重要扩展接口),例如@EnableAspectJAutoProxy
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);
}
}
}
}
手写一个demo
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RegistrarDemo.class)
public @interface EnableRegistrar {
String[] scanPackage() default "";
}
//模仿spring整合mybaits
public class RegistrarDemo implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableRegistrar.class.getName(), false));
String[] scanPackages = annotationAttributes.getStringArray("scanPackage");
List<String> classNames = new ArrayList<String>();
for (String scanPackage : scanPackages) {
classNames.addAll(scanBasePackage(scanPackage));
}
try {
for (String className : classNames) {
Class<?> aClass = Class.forName(className);
if(aClass.isAnnotationPresent(MapperDemo.class)){
BeanDefinitionBuilder bdb1 = BeanDefinitionBuilder.rootBeanDefinition(aClass);
BeanDefinition beanDefinition1 = bdb1.getBeanDefinition();
registry.registerBeanDefinition(aClass.getName(), beanDefinition1);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private List<String> scanBasePackage(String basePackName){
List<String> classNames = new ArrayList<String>();
String path = basePackName.replace(".","/");
System.out.println(path);
URL url = this.getClass().getClassLoader().getResource(path);
System.out.println(url);
File dir = new File(url.getFile());
File[] files = dir.listFiles();
for (File file: files) {
if(file.isDirectory()){
scanBasePackage(basePackName +"."+file.getName());
}else if(file.isFile()){
classNames.add(basePackName +"." + file.getName().replace(".class",""));
System.out.println("扫描到的类有" + basePackName +"." + file.getName().replace(".class",""));
}
}
return classNames;
}
}
springboot中的自动装配机制
springboot中的自动装配机制是基于@EnableAutoConfiguration这个注解里完成的,这个@EnableAutoConfiguration注解可以显式地调用,否则它会在@SpringBootApplication注解中隐式地被调用,@EnableAutoConfiguration注解中使用了AutoConfigurationImportSelector作为ImportSelector。核心源码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
//读取META-INF/spring.factories下的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
这也是我们封装自己的springboot-starter是需要在META-INF/spring.factories配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx的原因