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的原因