Spring3-aop应用和源码解析

学习aop之前,先要了解设计模式中的动态代理模式

基础概念:

为多个有继承关系的类引入公共行为,可以在父类当中引入(当然,这也会破坏源代码)。  如果为毫不相干的多个类引入公共行为,这种就是aop的功能: 横向切入。

pointcut:切点。 指定增强行为的位置,也就是针对哪些方法。

joinpoint:连接点。 指定增强行为的时机,在方法执行的前、后,或者抛出异常后执行等。

advoice:通知。 指编写的需要执行的逻辑。

aspect:切面。 以上的综合

spring中的使用

a、xml配置模式:

引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

定义切面增强逻辑

public class LogUtils {

    /**
     * 业务逻辑开始之前执行
     * JoinPoint可以获取业务方法的参数,不需要可以不写
     */
    public void beforeMethod(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            System.out.println(arg);
        }
        System.out.println("业务逻辑开始执行之前执行.......");
    }


    /**
     * 业务逻辑结束时执行(无论异常与否)
     */
    public void afterMethod() {
        System.out.println("业务逻辑结束时执行,无论异常与否都执行.......");
    }


    /**
     * 异常时时执行
     */
    public void exceptionMethod() {
        System.out.println("异常时执行.......");
    }


    /**
     * 业务逻辑正常时执行
     */
    public void successMethod(Object retVal) {
        System.out.println("业务逻辑正常时执行.......");
    }


    /**
     * 环绕通知
     *
     */
    public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知中的beforemethod....");

        Object result = null;
        try{
            // 控制原有业务逻辑是否执行
            result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch(Exception e) {
            System.out.println("环绕通知中的exceptionmethod....");
        }finally {
            System.out.println("环绕通知中的after method....");
        }

        return result;
    }

spring配置文件

<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>

<!--开始aop的配置-->
<aop:config>
    <!--配置切⾯-->
    <aop:aspect id="logAdvice" ref="logUtil">
        <!--配置前置通知-->
        <aop:before method="printLog" pointcut="execution(com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account))">
        </aop:before>
    </aop:aspect>
</aop:config>

对于上面使用的是前置通知,另外还有几种

返回后通知:<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

抛出异常后通知: <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

方法结束后通知:<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

环绕通知:<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>

有两点注意:

  • 上面的切入点 用的是: pointcut-ref      这种需要把pointcut单独提出来配置,然后引用就行了
    • <aop:pointcut id="pt1" expression="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
  • LogUtil环绕通知和其他的通知类型有差别:
    • 增强逻辑时,环绕通知的方法参数是ProceedingJoinPoint,其他通知的参数是JoinPoint(JoinPoint是获取原业务方法参数等信息,如果不关注可以使用无参方法,也就是不写JoinPoint参数)
    • 其他通知一定会执行目标业务方法,而环绕通知可以控制是否执行。  要执行必须手动调用:proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    • ProceedingJoinPoint是JoinPoint的子类,增加了两个方法: 
      • Object proceed() throws Throwable    继续执行业务方法

      • Object proceed(Object[] var1) throws Throwable   继续执行业务方法,可以传入新参数,也可使用原来的参数(比如上面的:proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs())

    • JoinPoint的相关api:

    • Signature getSignature();

      获取目标方法返回值 包名.类名.方法名(参数):void test.Test.test(String)

      Object[] getArgs();

      获取目标方法的参数值

      getTarget().getClass();

      获取被代理的对象的class

      Object getThis();

      获取代理对象

    • getSignature().getName()

      获取当前的方法名 test

      getSignature().getDeclaringType().getSimpleName()

      获取简单类名 Test

      getSignature().getDeclaringTypeName()

      包名.类名 test.Test

      getTarget().getClass();

      获取被代理的对象的class: Test.Class

      Object getThis();

      获取代理对象

    • Modifier.toString(joinPoint.getSignature().getModifiers()))

      获取方法的声明类型:如public

      (MethodSignature)joinPoint.getSignature().getMethod().getParameterTypes();

      获取方法的参数类型,返回Class<?> []

      (CodeSignature)joinPoint.getSignature().getMethod().getParameterNames();

      获取方法的参数名字,返回String []

      Object getThis();

      获取代理对象

b、xml + 注解模式

配置文件中开启spring对注解aop的⽀持
  • <aop:aspectj-autoproxy/>

将上面xml中的其他aop配置去掉,在LogUtil类中添加相应的注解即可

@Pointcut("execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))")
public void pt1(){

}


/**
 * 业务逻辑开始之前执行
 */
@Before("pt1()")
public void beforeMethod(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    for (int i = 0; i < args.length; i++) {
        Object arg = args[i];
        System.out.println(arg);
    }
    System.out.println("业务逻辑开始执行之前执行.......");
}


/**
 * 业务逻辑结束时执行(无论异常与否)
 */
@After("pt1()")
public void afterMethod() {
    System.out.println("业务逻辑结束时执行,无论异常与否都执行.......");
}


/**
 * 异常时时执行
 */
@AfterThrowing("pt1()")
public void exceptionMethod() {
    System.out.println("异常时执行.......");
}


/**
 * 业务逻辑正常时执行
 */
@AfterReturning(value = "pt1()",returning = "retVal")
public void successMethod(Object retVal) {
    System.out.println("业务逻辑正常时执行.......");
}


/**
 * 环绕通知
 *
 */
/*@Around("pt1()")*/
public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕通知中的beforemethod....");

    Object result = null;
    try{
        // 控制原有业务逻辑是否执行
        // result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }catch(Exception e) {
        System.out.println("环绕通知中的exceptionmethod....");
    }finally {
        System.out.println("环绕通知中的after method....");
    }

    return result;
}

c、纯注解模式

不需要在xml中开启aop支持,只需要在启动类中增加注解@EnableAspectJAutoProxy。 

对于springAOP,如果target实现了接口,默认采用JDK动态代理, 否则采用cglib。   

当然,对于不是final修饰的类都可以使用cglib,所以及时实现了接口,也可强制指定使用cglib。 两种配置方式如下:

  • xml中配置:<aop:config proxy-target-class="true">       或者    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
  • 注解配置:@EnableAspectJAutoProxy(proxyTargetClass = true)

SpringAOP源码解析

如果是注解开启aop:

从开启aop功能的注解@EnableAspectJAutoProxy点进去,发现它引入了AspectJAutoProxyRegistrar类,

点进去看到调用了方法

 

跟进此方法最终看到注册了 AnnotationAwareAspectJAutoProxyCreator类

到这里先暂停,假如是xml配置aop:aspectj-autoproxy />开启aop支持,其实最终也能跟踪到此类,来看一下

如果spring配置中使用了自定义标签,那么一定会注册对应的解析器。 在AopNamespaceHandler的初始化方法中,我们可以看到,使用的是解析器是

AspectJAutoProxyBeanDefinitionParser

继续跟代码:

继续跟,发现和上面一样,也是调用AopConfigUtils的注册方法,那么最终,肯定也是注册了AnnotationAwareAspectJAutoProxyCreator类

所以,不管从注解入手,还是xml入手,最终我们发现都是注册了 AspectJAwareAdvisorAutoProxyCreator。此类叫: 自动代理创建器,AOP的实现,基本就是靠这个类去完成。 

注册后,回到上一层,先看看准备工作:处理是否强制使用cglib代理

proxy-target-class属性也就是我们说的,是否强制使用cglib代理! 如果是,继续跟进,我们发现又调用了AopConfigUtils的方法。 
上面已经调用它的另一个方法注册了AnnotationAwareAspectJAutoProxyCreator类,所以这里取出来的BeanDefinition就是该类,

将强制使用cglib的属性设置进去。

说了这么多,正式来看看AnnotationAwareAspectJAutoProxyCreator如何工作的?

可以看到,其实现了BeanPostProcessor接口,那么会在初始化后调用 postProcessAfterInitialization方法,拦截每一个bean为其创建代理。  具体逻辑在曾爷爷类中

AbstractAutoProxyCreator 就重写了该方法,实现了具体逻辑。   先对拦截的bean,创建一个key,格式: beanClassName_beanName。   如果该bean适合被代理,则需要封装该bean生成代理返回。   

继续跟踪: 已经处理过或者无须增强,又或者该bean是基础设施类,直接返回。   真正的创建代理在createProxy方法。 

那么,在进入创建代理的createProxy方法前,需要先判断是否有切面增强,有才需要创建代理啊。  而获取所有增强的方法就是上面两行

this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);   

进入子类方法:AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean,其中

findCandidateAdvisors 获取所有的增强   
findAdvisorsThatCanApply  从所有增强中找到适用于当前bean的增强

先看看获取所有增强,进入子类AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors:

该方法主要有两个逻辑:

super.findCandidateAdvisors();    调用父类获取配置文件中的所有增强,其实就是提取配置文件中指明为切面的类。
aspectJAdvisorsBuilder.buildAspectJAdvisors()     获取注解增强,其实就是遍历所有的beanName,获取bean后判断是否有AspectJ注解,如果有,进一步提取并放入增强的缓存。

其中最复杂的获取增强器,委托给getAdvisors方法实现

进一步跟踪getAdvisors方法:

循环调用,继续跟踪getAdvisor,,看看如何获取切点信息,并生成增强的

如上,

findAspectJAnnotationOnMethod 中先获取切点信息(这里的切点信息,如@Before("test()") ),

找到Method的增强类型后,就该生成对应的增强器了(如@Before就生成一个Before类型的增强器),回到getAdvisor继续跟一下代码看看:

如上: 增强器使用InstantiationModelAwarePointcutAdvisorImpl包装,进入构造函数:

如上,根据不同的增强类型,生成不同的增强器,比如我们看一下后置增强器 AspectJAfterAdvice

它实现了MethodInterceptor,所以它自身就是一个拦截器,在finally中就是增强方法,会在正常逻辑执行完成后执行。

那再看一下前置增强器 AspectJMethodBeforeAdvice, ,看看为什么增强逻辑会在正常逻辑之前

如图,前置增强没有直接实现MethodInterceptor,所以它本身不是一个拦截器。 而是借助另一个拦截器:MethodBeforeAdviceInterceptor。

当前置增强器创建好后会注入到该拦截器,然后调用。  如图,先调用增强器的方法,再执行业务逻辑。

当所有的getAdvisor循环调用完成后,一个切面类的所有方法增强就生成了; 当所有的beanName循环完成后,所有切面类的所有方法增强就生成了。 但是,并不是所有的增强都适合当前拦截的目标bean,所以还要筛选:

有了增强器,剩下的就是对目标bean生成代理了,继续回到上一层的createProxy方法:

如上,先给factory指定合适的代理方式,并把所有增强转换后设置给factory,最后在getProxy方法中生成代理。 继续跟踪:

createAopProxy():  创建代理-这一步只是先确定使用哪种方式创建

如上:如果没有强制使用cglib,目标类也实现了接口,则使用JDK代理;     如果目标对象没有实现接口,则必须使用cglib

getProxy(classLoader):  获取代理-在确定后这一步真正开始创建

为目标创建代理后,该代理中也注入了之前获取的增强信息。  当目标方法调用时,会调用代理invoke方法,我们来看看invoke方法中,是如何实现增强的调用? 比如: 为什么前置增强器在目标方法之前,后置增强器在目标方法之后?

在invoke中的关键代码如上,先创建拦截器链,封装相应的增强,并用ReflectiveMethodInvocation封装后逐步调用。  (如果没有增强,在if中直接调用目标方法)

跟踪proceed():

如上,维护了一个拦截器计数器的维护,以便链可以有序进行。   但是这里并没有维护拦截器的顺序,而是依次调用。  至于增强的顺序是在目标方法的哪个位置执行,都是由各个增强器自行实现。

在上面创建增强器的时候,我们已经看过了。

到这里,springAOP的主要流程就算结束了, cglib创建代理暂时不看了。

总结起来就是: 一个实现了BeanPostProcessor的类,为所有的bean查找所有的增强,并生成代理, 在代理的invoke方法中调用所有的增强,而执行位置由增强器自己实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值