AOP原理分析(一)准备阶段和代理阶段

Aop的执行原理,我们应该基本都了解:通过编写切面类,我们可以在指定的切入点处插入我们额外的代码块,就好比代理模式中,我们可以在执行目标方法的前后干一些自己想干的事情。那么这是怎么样实现的呢?
我们自己写的类中的代码是硬编码写死的,要想改变一个已经写好的类,我们常见的操作就是动态代理了,没错,AOP的底层就是将切入点所在的类创建成了代理对象。
我们知道Spring中的一个主要功能就是管理所有的bean对象,在创建对象的时候,为我们提供了很多的扩展点,可以方便我们来干预对象的创建,那么Aop究竟是在哪一个扩展点的地方帮我们创建了代理对象呢?本文就来讲解下从Aop的前置准备到创建代理对象的整个流程。

一、@EnableAspectJAutoProxy

熟悉Spring开发的模式的话,我们都知道,开启一个新的功能的话,我们基本需要在配置类上加上一个@EnableXxx的注解,而这类@EnableXxx的注解多半是向Spring容器中注入了影响bean创建生命周期的bean信息,开启基于注解的Aop的@EnableAspectJAutoProxy注解同样于此,他为我们导入了AnnotationAwareAspectJAutoProxyCreator类的定义信息。我们来看下AnnotationAwareAspectJAutoProxyCreator的继承实现结构图:

AnnotationAwareAspectJAutoProxyCreator结构图.png

在整个结构图中,能干预bean的生命周期的是左上角的BeanPostProcessor接口,那么整个AOP功能的具体实现就是在对于InstantiationAwareBeanPostProcessor的方法的具体实现中,就是AbstractAutoProxyCreator类中postProcessBeforeInstantiation方法和postProcessAfterInitialization方法,我们下面就关注它的具体实现:

二、Aop的前置准备

1、AOP基础准备工作

首先,我们来看AbstractAutoProxyCreator类中postProcessBeforeInstantiation方法的具体实现,他实现的接口是InstantiationAwareBeanPostProcessor,而InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation的触发时机是在Spring容器开始准备创建bean之前,Spring给我们提供了一个机会可以自己在这创建对象,而不走Spring的创建对象流程,但是AOP会在此就创建对象吗?我们继续往下看。
我们先看下前半部分代码:

    Object cacheKey = getCacheKey(beanClass, beanName);

        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {//advisedBeans已经分析过的组件,不是增强过的组件
                return null;
            }//所有增强了的组件会被缓存在advisedBeans中,如果是我们需要增强的bean,就放在缓存中
            //isInfrastructureClass(beanClass):判断当前类是否有@Aspect注解,即当前类是否是切面;shouldSkip中解析了所有的切面类并封装了切面类中的切面方法
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {// shouldSkip:如果是切面类,则跳过处
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }

这段代码中主要在管理advisedBeans这个集合,这个集合中key为bean的名称,value为boolean值,个人理解为这个集合的意义是,key表示处理过的beanName,而value值表示这个bean应不应该被增强【不代理】。那什么样的类是不需要增强的呢?这就需要关注if中的两个条件了,一个是isInfrastructureClass(beanClass),另一个是shouldSkip(beanClass, beanName),满足这两个条件之一的都会加入advisedBeans集合,并且标记为不增强;一起来看看这两个方法:

  1. isInfrastructureClass(beanClass):判断是不是AOP的基础设施类,如果是的话,就加入到advised集合中,并标记为不应该增强,那么究竟什么类是AOP的基础设施类呢?深入到代码里我们发现:分为两大块:一个是实现了Advice,Pointcut,Advisor,AopInfrastructureBean这四个接口的类属于AOP的基础设施类;另一个是这个类上面标注了@Aspect,即切面类
  2. shouldSkip(beanClass, beanName):这个方法是AOP准备过程中的一个重要点,因为这个方法中干了很多事情,为后面的AOP代理做好了铺垫;代码如下:
        protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        // TODO: Consider optimization by caching the list of the aspect names
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        for (Advisor advisor : candidateAdvisors) {
            if (advisor instanceof AspectJPointcutAdvisor &&
                    ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
                return true;
            }
        }
        return super.shouldSkip(beanClass, beanName);
    }

整个应不应该跳过的逻辑是:获取所有的Advisor集合,然后遍历Advisor集合,如果当前bean的名称和其中的一个AspectJPointcutAdvisor的aspectName相同,其实就是说明这个类是切面类,那么就会跳过。到这里,我相信有的小伙伴就会发出一个疑问了:前面不是处理过了吗?为什么此处还是需要处理呢?其实这里我也有点疑问,但是它干了更多的事情,那就是创建并缓存了所有的Advisor对象。
在findCandidateAdvisors方法的逻辑中首先是super执行了AbstractAdvisorAutoProxyCreator中的逻辑:找到所有的Advisor的实现类的bean名称,并进行getBean创建对象;其次时寻找@Aspect注解的类,然后为通知方法构建Advisor,整个的构建流程可以归纳如下:

  1. 拿到容器中所有的bean名称
  2. 循环遍历beanName,拿到beanType,判断是不是切面类(@Aspect)
  3. 是切面类的话,就拿到类中除去标有@Pointcut的方法。然后遍历方法:
  4. 把每个切面方法构建为Advisor【InstantiationModelAwarePointcutAdvisorImpl】
  5. 处理@DeclareParents属性注解,最终会构建DeclareParentsAdvisor【属于IntroductionAdvisor,类级别的切入】,其中对应的Advice为DelegatePerTargetObjectIntroductionInterceptor【属于IntroductionInterceptor,同样也是MethodInterceptor】
  6. 缓存进advisorsCache 【beanName--->List<Advisor>】

2、Advice的构建

其中,在构建InstantiationModelAwarePointcutAdvisorImpl中,会构建当前增强方法的Advice,也就是构造方法中的instantiateAdvice方法,最终会调用ReflectiveAspectJAdvisorFactory的getAdvice方法来构建相应的Advice,关键代码如下:

    switch (aspectJAnnotation.getAnnotationType()) {
            case AtPointcut:
                if (logger.isDebugEnabled()) {
                    logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
                }
                return null;
            case AtAround:
                springAdvice = new AspectJAroundAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtBefore:
                springAdvice = new AspectJMethodBeforeAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfter:
                springAdvice = new AspectJAfterAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfterReturning:
                springAdvice = new AspectJAfterReturningAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                    springAdvice.setReturningName(afterReturningAnnotation.returning());
                }
                break;
            case AtAfterThrowing:
                springAdvice = new AspectJAfterThrowingAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                    springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
                }
                break;
            default:
                throw new UnsupportedOperationException(
                        "Unsupported advice type on method: " + candidateAdviceMethod);
        }

        // Now to configure the advice...
        springAdvice.setAspectName(aspectName);
        springAdvice.setDeclarationOrder(declarationOrder);
        String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
        if (argNames != null) {
            springAdvice.setArgumentNamesFromStringArray(argNames);
        }
        springAdvice.calculateArgumentBindings();

最终会把切面类beanName和其中的增强方法Advisor存放到advisorsCache缓存中,方便后面使用。

可以说准备阶段,Spring主要就干了两件事吧:第一件事是标记那些不需要代理的AOP基础设施类;第二件事就是寻找并创建容器中所有的Advisor,这一步也分为两小步:其一,去寻找并创建所有直接实现了Advisor接口的;其二,处理标注了@Aspect注解的类,并将其以aspectName及List<Advisor>的形式缓存到advisorsCache中。

3、Advisor & Advice

有必要介绍下AOP中的Advisor和Advice,这两个东西到底有什么区别和关系呢?Advice就是我们要干预正常代码而额外加的一部分代码逻辑,其实可以理解为拦截器,而Advisor可以说成是对Advice的一种封装吧,因为每个Advisor包含一个Advice,然后Advisor还应该包括Advice增强的增强表达式,即它应该什么时候进行增强,即增强条件吧;在Spring中,提供了两个Advisor的子接口,分别是IntroductionAdvisor和PointcutAdvisor,IntroductionAdvisor提供的getClassFilter()方法和PointcutAdvisor中提供的getPointcut()方法就是各自的增强条件(如下图),从增强条件我们也可以看出:IntroductionAdvisor是基于类级别的增强,而PointcutAdvisor是基于方法级别或者类级别的增强,显然后者方法级别的增强是更加细粒度的,也是我们常用的@Aspect注解的Advisor。如果这么说还是过于抽象的话,那我们拿我们常用的切面类@Aspect来进行来进行类比:


Advisor.png
    @Component  //切面也是容器中的组件
@Aspect //说明这是切面
public class LogAspect {

    public LogAspect() {
        System.out.println("LogAspect....");
    }


    @DeclareParents(value = "com.spring.aop.HelloService", defaultImpl = DeclareParentsTestImpl.class)
    private DeclareParentsTest declareParentsTest;

    //前置通知
    @Before("execution(* com.spring.aop.HelloService.sayHello(..))")
    public void logStart(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logStart()===>" + name + "...【args:" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //返回通知
    @AfterReturning(value = "execution(* com.spring.aop.HelloService.sayHello(..))",returning = "result")
    public void logReturn(JoinPoint joinPoint,Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logReturn()==>" + name + "...【args:"+ Arrays.asList(joinPoint.getArgs())+"】【result:"+result+"】");
    }

    //后置通知
    @After("execution(* com.spring.aop.HelloService.sayHello(..))")
    public void logEnd(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logEnd()===>" + name + "...【args:" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //异常通知
    @AfterThrowing(value = "execution(* com.spring.aop.HelloService.sayHello(..))",throwing = "e")
    public void logError(JoinPoint joinPoint,Exception e) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logError()==>" + name + "...【args:"+ Arrays.asList(joinPoint.getArgs())+"】【result:"+e+"】");
    }
}

像上面logStart,logReturn,logEnd,logError等就可以理解为一个Advice,而@Before,@AfterReturning,@After,@AfterThrowing中的表达式就会说一个Pointcut,然后方法加上注解中的表达式就构成了一个Advisor(PointcutAdvisor),相信这么说,大家应该都能明白了吧。

三、Aop生成代理

Spring提供了两个地方来生成Aop代理对象,下面我们来看看在哪两个地方可以生成Aop的代理对象:

1、Aop生成代理对象的第一个地方

第一个地方是在AbstractAutoProxyCreator的postProcessBeforeInstantiation方法中,Aop的准备工作做完后,就会查看有没有自定义的TargetSource,如果有符合的TargetSource的话就会在此处直接创建bean的代理对象,不会继续走Spring的创建bean的流程,但是,这个地方有个麻烦的点,就是我们需要干预到AnnotationAwareAspectJAutoProxyCreator的创建,需要修改其Bean的定义信息,将我们自定义的TargetSourceCreator赋值给AnnotationAwareAspectJAutoProxyCreator的customTargetSourceCreators属性,我们可以如下操作:
自定义的TargetSource:

    public class MyTargetSource implements TargetSource {
    private final Object target;

    public MyTargetSource(Object target) {
        this.target = target;
    }

    @Override
    public Class<?> getTargetClass() {
        return HelloService.class;
    }

    @Override
    public boolean isStatic() {
        return true;
    }

    @Override
    public Object getTarget() throws Exception {
        return target;
    }

    @Override
    public void releaseTarget(Object target) throws Exception {

    }
}

封装成TargetSourceCreator:

    public class MyTargetSourceCreator implements TargetSourceCreator {
    @Override
    public TargetSource getTargetSource(Class<?> beanClass, String beanName) {
        if (beanName.equals("helloService")) {
            try {
                return new MyTargetSource(ReflectionUtils.accessibleConstructor(HelloService.class).newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

实现BeanFactoryPostProcessor修改AnnotationAwareAspectJAutoProxyCreator的bean定义信息:

    @Component
public class TargetSourceCreatorBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition bd = beanFactory.getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); // AnnotationAwareAspectJAutoProxyCreator的beanName
        bd.getPropertyValues().add("customTargetSourceCreators",new TargetSourceCreator[]{new MyTargetSourceCreator()});
    }
}

如上述配置后,HelloService的对象创建就会在AOP准备工作做好后,通过如下代码,获取到自定义TargetSource后直接创建AOP代理对象,不走这个bean后面的生命周期了:

//创建个代理,如果为这个类指定了targetSource会在此就生成代理直接返回了,不走这个bean后面的生命周期了
    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;
        }

2、Aop生成代理对象的第二个地方

另一种情况是,当没有为对象指定自定义的TargetSource时,Spring会在bean的对象创建完成后的AbstractAutoProxyCreator的postProcessAfterInitialization方法中的wrapIfNecessary方法中创建Aop代理对象,代码如下:

    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = createProxy(//创建代理对象,specificInterceptors所有的增强器
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

两处创建Aop代理对象的逻辑都是一样的,仅仅是时机不一样罢了,Spring默认使用的是CGLIB来创建代理对象的,具体的创建过程在此就不说了,后续有时间再补上,我们需要知道的是:当我们执行被AOP增强的类时,需要回调DynamicAdvisedInterceptor这个类中的intercept方法,这也是后面我们讲述AOP执行流程的入口。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容