Spring的ImportSelector实现类动态开关闭功能,配合上注解@import使用,BeanPostProcessor就是通过ImportSelector注册进去的,而不是@Component.经典场景AOP,加上自己的自定义注解,就可以来完成是否要实现代理功能,具体看下面文章说明。
接上篇的Import注解的三种方式,ImportSelector这篇主要做一个简单的案例,模拟aop是如何通过加注解就可以代理,不加就不返回代理对象。
1、首先写两个简单的Dao和DaoImpl
public interface UserDao {
public void query();
}
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("UserDao ···");
}
}
我现在想让userDaoImpl返回的时候是一个代理类,那我可以在bean初始化前做干预,也就是实现BeanPostProcessor的前置方法来完成代理。
注意: —> 但是我们这里不能加@Component,因为我们要完成动态的开关实现代理功能,所以我们要交给ImportSelector去完成,那我们来看没有加@Component的代码。>
2、MyBeanPostProcessor实现后置处理器完成代理,
//@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(beanName.equals("userDaoImpl")) {
bean = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{UserDao.class}, new MyInvocationHandler(bean));
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
public class MyInvocationHandler implements InvocationHandler {
Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是代理方法");
return method.invoke(target,args);
}
}
上面说了,因为我们是没有加@Component注解的,所以我们需要交由以下的实现类去完成
3、MyImportSelector将我们的MyBeanPostProcessor注入Spring容器
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanPostProcessor.class.getName()}; //动态注入UserDaoImpl
}
}
现在我们的MyBeanPostProcessor这个已经通过ImportSelector 的实现类里的selectImports方法注入容器了,但是不能成功,因为我们还需要通过Import注解去扫描,所以接下来我们需要加个Import注解。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
但是在此之前,我们先来介绍一下Import在Spring源码是如何实现的。
我们看源码可以这个流程
refresh() -> invokeBeanFactoryPostProcessors(beanFactory) -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); ->
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
->postProcessor.postProcessBeanDefinitionRegistry(registry);进入ConfigurationClasspostProcessor->processConfigBeanDefinitions(registry);->找到parse->parse->doProcessConfigurationClass->
这里它会先去扫描@Component注解
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
然后再去扫描@Import注解
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
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 (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(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));
}
}
}
发现你实现了ImportSelector接口后然后从bd中拿到类名。继而通过反射去实现一个对象。
我们会发现他是现扫描@component后处理db,在去扫描Import注解。
所以我们重点看这行。
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
这里就是ImportSelector的实现类的selectImports方法,从而得到new String里面的Class
//此行主要是递归,因为可能你的类里面还实现了@Import注解
processImports(configClass, currentSourceClass, importSourceClasses, false);
而再次进来就不是一个Import注解的了,就会调用最后一个方法
processConfigurationClass(candidate.asConfigClass(configClass));
this.configurationClasses.put(configClass, configClass);
就会放入BeanDefinition里去,就完成了任务了。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4、现在我们的只需要将我们的MyImportSelector交给Import就可以了,再此自己实现一个注解
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)
public @interface MyImport {
}
5、我们将注解放再AppConfig上就可以扫描到了Import了。
@ComponentScan("com.spring02")
@MyImport//实现了动态的注入bean,而我们的component是固定的
public class Appconfig {
}
6、现在我们来看看实现类
public class Test1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new
AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(Appconfig.class);
annotationConfigApplicationContext.refresh();
// annotationConfigApplicationContext.getBean(UserDaoImpl.class).query();
UserDao userDao = (UserDao) annotationConfigApplicationContext.getBean("userDaoImpl");
userDao.query();
}
}
打印以下结果----有@MyImport注解的
我是代理方法
UserDao ···
成功将没有配@Component的MyBeanPostProcessor给注入成功,现在我们取消我们的@MyImport注解再来试试。
@ComponentScan("com.spring02")
//@MyImport//实现了动态的注入bean,而我们的component是固定的
public class Appconfig {
}
打印以下结果----没有@MyImport注解的
UserDao ···
我们会发现,有加注解的,也就是有引用@ImportSelector实现类的就可以完成我们的代理功能,而没有加注解的就不能完成我们的代理功能,我们的案例到此也就结束了。
总结
ImportSelector实现类,配合@Import注解可以帮我们完成对功能动态开启与关闭的功能,是Spring的一个很重要的扩展点之一。