Spring的ImportSelector配合BeanPostProcessor手动自己实现经典场景AOP开关闭代理功能

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的一个很重要的扩展点之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值