1. 程式人生 > 實用技巧 >Spring EnableXX註解原理及應用

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