Spring Aop底层原理详解

共 21337字,需浏览 43分钟

 ·

2021-04-17 19:14

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

写在前面:对于一个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三种类型的处理。

/**
  * 处理我们的@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,用于描述通知信息

/**
 * 描述:
 *  自定义数据结构
 *
 * @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)定义数据工具类,具体作用见注释

 /**
     * 描述:
     *
     * @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中

/**
 * 描述:
 *
 * @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代理,这样就可以完成对任意对象的代理了。


具体实现如下:

/**
 * 描述:
 * 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容器中

/**
 * 描述:
 * 自定义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处理的更严谨而已

@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有了一个更深层次的了解呢!希望对大家有所启发,谢谢!

————————————————

版权声明:本文为CSDN博主「baomw」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:

https://blog.csdn.net/baomw/article/details/84262006





锋哥最新SpringCloud分布式电商秒杀课程发布

👇👇👇

👆长按上方微信二维码 2 秒





感谢点赞支持下哈 

浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报