大家好,又见面了,我是你们的朋友全栈君。
写在前面:对于一个java程序员来说,相信绝大多数都有这样的面试经历,面试官问:你知道什么是aop吗?谈谈你是怎么理解aop的?等等诸如此类关于aop的问题。当然对于一些小白可能会一脸懵逼;对于一些工作一两年的,可能知道,哦!aop就是面向切面变成,打印日志啊,什么什么的,要是有点学习深度的呢可能会说aop底层实现利用了jdk动态代理,cglib啊什么的。很多时候可能面试就到此打住了,当然,然后也就没有然后了(客气点的来句:回头有消息我会通知你的!)。 今天,通过这篇文章,我想带大家先了解下什么是spring后置处理器,然后利用spring的后置处理器我们自己来手写一个springAop,来完成和springAop一样的功能!让你可以对你的面试官说:你精通AOP!
在开始之前呢,我想先引入一个概念:spring扩展点和后置处理器
我们知道springIoc可以对我们应用程序中的java对象做一个集中化的管理,从而使我们从繁琐的new Object();中解脱出来。 其核心思想呢就是先创造出一个bean工厂,也就是我们的beanFactory,通过beanFactory来生产出我们应用程序中所需要的java对象,也就是我们的java bean。
今天呢我跟大家介绍的后置处理器呢,有三个 BeanFactoryPostProcessor : 可以插手beanFactory的生命周期 BeanPostProcessor :可以插手bean的生命周期 ImportSelector :借助@Import注解,可以动态实现将一个类是否交由spring管理,常用作开关操作
1,BeanFactoryPostProcessor(BeanDefinitionRegistryPostProcessor 有兴趣的可以自行查阅spring源码)
可以看到,该接口只定义了一个方法,具体实现的执行时机呢,我们可以通过spring源码得知: 在我们AnnotationConfigApplicationContext.refresh();的时候,从下源码可知,在我们beanFactory被创建出来后,相关准备工作做完后,会去执行invokeBeanFactoryPostProcessors(beanFactory);也就是去执行我们的BeanFactoryPostProcessor
从上面两处代码可以看出,spring在执行 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); 的时候,会传入一个List beanFactoryPostProcessors;然后循环去执行list里面所有实现了 BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor的对象的相关方法(spring处理逻辑比较严谨,我这只是大致描述,如想深入了解细节可以参考spring源码)。
2,BeanPostProcessor:可以看出,该接口定义了两个方法,分别在bean实例化之后放到我们的容器之前和之后去执行,方法的返回值为一个object,这个object呢就是我们存在于容器的对象了(所以这个位置我们是不是可以对我们的bean做一个动态的修改,替换等等操作,所以这也是我们spring的扩展点之一,后面结合我么自己手写aop来详细讲解这个扩展点的应用)
3, ImportSelector 在讲ImportSelector之前呢,我想先讲一下@Import这个注解。在spring处理我们的java类的时候,会分成四种情况去处理 1)普通类:就是我们家里@Component,@Service,@Repository等等的类 2)处理我们的import进来的类: 这里呢,又分为三种情况: a)import一个普通类:@Import(A.class) b)import一个Registrar:比如我们的aop @Import(AspectJAutoProxyRegistrar.class) c)import一个ImportSelector:具体妙用见下文
至于spring在什么时候处理的呢,我大致叙述一下,有兴趣的可以自己去研究下spring源码: 对于普通类,spring在doScan的时候,就将扫描出来的java类转换成我们的BeanDefinition,然后放入一个BeanDefinitionMap中去 对于@import的三种情况,处理就在我们的ConfigurationClassPostProcessor(该类是我们BeanDefinitionRegistryPostProcessor后置处理器的一个实现,同时这也是我们spring内部自己维护的唯一实现类(排除内部类)),具体处理import的核心代码如下,if-else 很容易可以看出spring对于我们import三种类型的处理。
代码语言:javascript复制/**
* 处理我们的@Import注解,注意我们的@Import注解传入的参数,可能有三种类型
* 1,传入一个class类,直接解析
* 2,传入一个registrar,需要解析这个registrar
* 3,传入一个ImporterSelector,这时候会去解析ImporterSelector的实现方法中返回的数组的class
*
*/
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//处理我们的ImportSelector
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);
//注意可能我们ImportSelector传入的类上还有可能会Import,所以这里,spring采用了
//一个递归调用,解析所有的import
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
//处理我们的Registrar
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);
//添加的一个和Importselector方式不同的map中,sprig对两种方式传入的类注册方式不同
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
//最后如果是普通类,传入importStack后交由processConfigurationClass进行注册处理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class ["
configClass.getMetadata().getClassName() "]", ex);
}
finally {
this.importStack.pop();
}
}
}
4,实现思路:在我们开发过程中,无非就是明确需求,列出需要解决的问题,然后针对问题,找出解决方案就OK!
同样根据如上原理,下面我们便可以来模拟我们的springAop,如果有点基础的可能应该会知道,spring是基于我们的动态代理实现的(先不考虑是cglib还是jdk动态代理),结合我们aop使用(没用过的好去百度了),那么我们就需要解决如下几个问题: a)我们知道开启和关闭aop需要注解@EnableAspectJAutoProxy,如何实现,结合上文,我们可以使用@import(ImportSelector.class) b)如何确定代理关系,即哪些是我们需要代理的目标对象和其中的目标方法,以及哪些方法是要增强到目标对象的目标方法上去的? c)如何实现目标对象的替换,就是我们在getBean的时候,如何根据目标对象来获取到我们增强后的代理对象?
如上问题都解决了,那么也就实现了我们的AOP.
5,根据如上思路,构建工程如下:
具体工程说明如下: annotation:放我们所有的自定义注解 holder:自定义数据结构,具体类后面说 processor:放我们所有的后置处理器及代理相关 selector:放我们的ImportSelector的实现 util:工具类
要模拟aop,那么我们就要结合我们怎么去使用aop: 对于AOP,我们知道有一个开关注解类 @EnableAspectJAutoProxy(同样我们定义个@EnableAop), 注解@Aspect,@Before,@After。。。(注意这些都不是spring的注解,是Aspectj的注解,只是我们spring直接引用了而已,同样我们也对于新建自定义注解@AopJ,@BeforeBaomw,@AfterBaomw,@AfterBaomw。。。)
针对问题2,由于BeanFactoryPostProcessor的所有实现会在beanFactory完成对由于bean的扫描后,在实例化之前执行,所以我们可以新建一类,实现这个接口,然后实现方法里面主要完成对有BeanDefinition的扫描,找出我们所有的通知类,然后循环里面的方法,找到所有的通知方法,然后根据注解判断切入类型(也就是前置,后置还是环绕),最后解析注解的内容,扫描出所有的目标类,放入我们定义好的容器中。 具体实现如下: (1)定义holder,用于描述通知信息
代码语言:javascript复制/**
* 描述:
* 自定义数据结构
*
* @author baomw
* @create 2018-11-19 下午 4:56
*/
public class ProxyBeanHolder {
//通知类名称
private volatile String className;
//通知方法名称
private volatile String methodName;
//注解类名称
private volatile String annotationName;
...
< get and setter>
}
(2)定义数据工具类,具体作用见注释
代码语言:javascript复制 /**
* 描述:
*
* @author baomw
* @create 2018-11-19 下午 1:48
*/
public class ConfigurationUtil {
/**
* aop标识注解类
*/
public static final String AOP_POINTCUT_ANNOTATION
= "com.baomw.annotation.AopJ";
/**
* 前置通知注解类
*/
public static final String BEFORE
= "com.baomw.annotation.BeforeBaomw";
/**
* 后置通知注解类
*/
public static final String AFTER
= "com.baomw.annotation.AfterBaomw";
/**
* 环绕通知注解类
*/
public static final String AROUND
= "com.baomw.annotation.AroundBaomw";
/**
* 存放需代理的全部目标对象类
*/
public static volatile Map<String,List<ProxyBeanHolder>> classzzProxyBeanHolder = new ConcurrentHashMap<>();
}
(3)定义我们的注册类,用于注册我们的目标对象和通知对象之间的关系,其核心代码如下,首先实现BeanFactoryPostProcessor ,保证其实在对所有的bean完成扫描后,在bean的实例化之前执行,然后再其中按上述思路,scan出所有的目标对象,然后建立起目标对象和通知对象的关联关系,然后放入我们的Map中
代码语言:javascript复制/**
* 描述:
*
* @author baomw
* @create 2018-11-19 下午 1:59
*/
@Component
public class RegisterBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
/**
* 存放需要代理的相关信息类
*/
public static volatile List<ProxyBeanHolder> roxyBeanHolderList = new Vector<>();
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//获取所有的bdName
String[] beanDefinitionNames = configurableListableBeanFactory.getBeanDefinitionNames();
for (String beanDefinitionName:beanDefinitionNames){
BeanDefinition beanDefinition
= configurableListableBeanFactory.getBeanDefinition(beanDefinitionName);
//判断bd是否是一个注解bd
if (beanDefinition instanceof AnnotatedBeanDefinition) {
//取得bd上的所有注解
AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata();
Set<String> Annotations = metadata.getAnnotationTypes();
//循环所有注解,找到aop切面注解类
for (String annotation:Annotations)
if (annotation.equals(ConfigurationUtil.AOP_POINTCUT_ANNOTATION))
doScan((GenericBeanDefinition)beanDefinition);
}
}
}
如此问题二就得到了完美 的解决
针对问题3,我们可以利用BeanPostProcessor,在bean实例化之后,在放入容器之前,进行一个条件过滤,如果当前对象是我们的目标对象(即在我们定义好的Map中),则对对象进行代理,将目标对象替换成代理对象返回即可 (注:spring实现aop采用cglib和jdk动态代理两种方式,@EnableAspectJAutoProxy(proxyTargetClass=true)可以加开关控制,如果不加,目标对象如果有实现接口,则使用jdk动态代理,如果没有就采用cglib(因为我们知道cglib是基于继承的))
我们这里实现,都简单粗暴一点,统一采用cglib代理,这样就可以完成对任意对象的代理了。
具体实现如下:
代码语言:javascript复制/**
* 描述:
* aop实现核心处理类
*
* @author baomw
* @create 2018-11-18 下午 11:24
*/
public class RealizedAopBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
String targetClass = bean.getClass().getName();
Object object = bean;
if (ConfigurationUtil.classzzProxyBeanHolder.containsKey(targetClass)){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(new CustomizedProxyInterceptor(ConfigurationUtil.classzzProxyBeanHolder.get(targetClass)));
object = enhancer.create();
}
return object;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
如此我们第三个问题也就顺利解决了最后,还剩下我们的问题1,这时候就可以引出我们的@import(ImportSelector.class)了 ImportSelector 接口有一个实现方法,返回一个字符串类型的数组,里面可以放类名,在@import(ImportSelector.class)的时候,spring会把我们返回方法里面的类全部注册到BeanDefinitionMap中,继而将对象注册到Spring容器中
代码语言:javascript复制/**
* 描述:
* 自定义aop实现,提交给spring处理的类
*
* @author baomw
* @create 2018-11-18 下午 11:29
*/
public class CustomizedAopImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{RealizedAopBeanPostProcessor.class.getName(),RegisterBeanFactoryPostProcessor.class.getName()};
}
}
/**
* 描述:
* aop开关注解
*
* @author baomw
* @create 2018-11-18 下午 11:21
*/
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomizedAopImportSelector.class)
public @interface EnableAop {
}
很明显,如果我的Appconfig上加了@EnableAop注解,则会将我们的后置处理器的实现类交给了spring管理,spring才能去扫描得到这个类,才能去执行我们的自定义的后置处理器里面的方法,才能实现我们的aop的代理,因此,我们的开关也就顺利完成了。下面编写测试方法 定义我们的通知,对我们com.baomw.dao下面的所有类的所有方法进行代理
可以看到,已经对我们dao下面的所有方法完成了代理,由此,我们便已经完成了我们的SpringAOP了,只不过我这aop是一个山寨版的,功能比较简单,但是具体的实现原理是跟springaop的实现大相庭径的,spring处理的逻辑更缜密严谨(毕竟是大师和小菜鸡的区别,你们懂的!)
下面我们不妨来看看spring是怎么来实现aop的: (1)这里和我们一样,也就是我们的开关接口,不过他@Import进来的是一个registrar,前面我已经提到过,sping是可以对我们import进来的registrar进行扫描注册的
(2)下面为我们aop实现BeanPostProcesser实现的postProcessBeforeInstantiation方法,可以看到,他也是判断bean是否在 Set targetSourcedBeans里面,如果在呢,就取到targetSource,然后createProxy,创建我们的代理对象。最后将我们的代理对象返回出去。 可见,其实现跟我们自己实现aop的思路一模一样,只是spring处理的更严谨而已
代码语言:javascript复制@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
到这里,你是不是对springaop有了一个更深层次的了解呢!希望对大家有所启发,谢谢!
【源码地址:】https://github.com/baomw/spring-customized.git
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/169624.html原文链接:https://javaforall.cn