Spring AOP源码分析,动态代理与调用确实难,那是因为你没有看这篇文章!

2020/11/12 09:09
阅读数 81

本文目标:带领大家阅读aop的源码,深入理解aop的原理,内容有点长,消化需要大概一周时间,做好准备。

Aop原理介绍
介绍aop相关的一些类
通过源码详解aop代理的创建过程
通过源码详解aop代理的调用过程
Aop代理一些特性的使用案例



最新2020整理收集的一些面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud等详细讲解,也有详细的学习规划图,面试题整理等,我感觉在面试这块讲的非常清楚:获取面试资料只需:[点击这里领取!!!] 暗号:CSDN在这里插入图片描述

Spring AOP原理

原理比较简单,主要就是使用jdk动态代理和cglib代理来创建代理对象,通过代理对象来访问目标对象,而代理对象中融入了增强的代码,最终起到对目标对象增强的效果。
aop相关的一些类
连接点(JoinPoint)相关类
通知(Advice)相关的类
切入点(Pointcut)相关的类
切面(Advisor)相关的类
连接点(JoinPoint)相关类
JoinPoint接口






这个接口表示一个通用的运行时连接点(在AOP术语中)

package org.aopalliance.intercept;

public interface Joinpoint {
   
   

    /**
     * 转到拦截器链中的下一个拦截器
     */
    Object proceed() throws Throwable;

    /**
     * 返回保存当前连接点静态部分【的对象】,这里一般指被代理的目标对象
     */
    Object getThis();

    /**
     * 返回此静态连接点  一般就为当前的Method(至少目前的唯一实现是MethodInvocation,所以连接点得静态部分肯定就是本方法)
     */
    AccessibleObject getStaticPart();

}

几个重要的子接口和实现类,如下:在这里插入图片描述
Invocation接口

此接口表示程序中的调用,调用是一个连接点,可以被拦截器拦截。

**

package org.aopalliance.intercept;
/**
 * 此接口表示程序中的调用
 * 调用是一个连接点,可以被拦截器拦截。
 */
public interface Invocation extends Joinpoint {
   
   
    /**
     * 将参数作为数组对象获取,可以更改此数组中的元素值以更改参数。
     * 通常用来获取调用目标方法的参数
     */
    Object[] getArguments();
}

**
MethodInvocation接口

用来表示连接点中方法的调用,可以获取调用过程中的目标方法。

package org.aopalliance.intercept;
import java.lang.reflect.Method;
/

  • 方法调用的描述,在方法调用时提供给拦截器。
  • 方法调用是一个连接点,可以被方法拦截器拦截。
    /
    public interface MethodInvocation extends Invocation {
    /

    *
    • 返回正在被调用得方法~~~ 返回的是当前Method对象。
    • 此时,效果同父类的AccessibleObject getStaticPart() 这个方法
      /
      Method getMethod();
      }

      *
      ProxyMethodInvocation接口


表示代理方法的调用

public interface ProxyMethodInvocation extends MethodInvocation {
   
   

    /**
     * 获取被调用的代理对象
     */
    Object getProxy();

    /**
     * 克隆一个方法调用器MethodInvocation
     */
    MethodInvocation invocableClone();

    /**
     * 克隆一个方法调用器MethodInvocation,并为方法调用器指定参数
     */
    MethodInvocation invocableClone(Object... arguments);

    /**
     * 设置要用于此链中任何通知的后续调用的参数。
     */
    void setArguments(Object... arguments);

    /**
     * 添加一些扩展用户属性,这些属性不在AOP框架内使用。它们只是作为调用对象的一部分保留,用于特殊的拦截器。
     */
    void setUserAttribute(String key, @Nullable Object value);

    /**
     * 根据key获取对应的用户属性
     */
    @Nullable
    Object getUserAttribute(String key);

}
通俗点理解:连接点表示方法的调用过程,内部包含了方法调用过程中的所有信息,比如被调用的方法、目标、代理对象、执行拦截器链等信息。

上面定义都是一些接口,最终有2个实现。

ReflectiveMethodInvocation

当代理对象是采用jdk动态代理创建的,通过代理对象来访问目标对象的方法的时,最终过程是由ReflectiveMethodInvocation来处理的,内部会通过递归调用方法拦截器,最终会调用到目标方法。

CglibMethodInvocation

功能和上面的类似,当代理对象是采用cglib创建的,通过代理对象来访问目标对象的方法的时,最终过程是由CglibMethodInvocation来处理的,内部会通过递归调用方法拦截器,最终会调用到目标方法。

这2个类源码稍后详解。

通知相关的类

通知用来定义需要增强的逻辑。
在这里插入图片描述
Advice接口

通知的底层接口

package org.aopalliance.aop;
public interface Advice {
   
   
}

BeforeAdvice接口

方法前置通知,内部空的

package org.springframework.aop;

public interface BeforeAdvice extends Advice {
   
   
}
Interceptor接口

此接口表示通用拦截器

package org.aopalliance.intercept;

public interface Interceptor extends Advice {
   
   
}

MethodInterceptor接口

方法拦截器,所有的通知均需要转换为MethodInterceptor类型的,最终多个MethodInterceptor组成一个方法拦截器连。

package org.aopalliance.intercept;

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
   
   
    /**
     * 拦截目标方法的执行,可以在这个方法内部实现需要增强的逻辑,以及主动调用目标方法
     */
    Object invoke(MethodInvocation invocation) throws Throwable;
}
AfterAdvice接口

后置通知的公共标记接口

package org.springframework.aop;

public interface AfterAdvice extends Advice {
   
   
}

MethodBeforeAdvice接口

方法执行前通知,需要在目标方法执行前执行一些逻辑的,可以通过这个实现。

通俗点说:需要在目标方法执行之前增强一些逻辑,可以通过这个接口来实现。before方法:在调用给定方法之前回调。

package org.springframework.aop;

public interface MethodBeforeAdvice extends BeforeAdvice {
   
   

    /**
     * 调用目标方法之前会先调用这个before方法
     * method:需要执行的目标方法
     * args:目标方法的参数
     * target:目标对象
     */
    void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
如同

public Object invoke(){
   
   
    调用MethodBeforeAdvice#before方法
    return 调用目标方法;
}

AfterReturningAdvice接口

方法执行后通知,需要在目标方法执行之后执行增强一些逻辑的,可以通过这个实现。

不过需要注意一点:目标方法正常执行后,才会回调这个接口,当目标方法有异常,那么这通知会被跳过。

package org.springframework.aop;

public interface AfterReturningAdvice extends AfterAdvice {
   
   

    /**
     * 目标方法执行之后会回调这个方法
     * method:需要执行的目标方法
     * args:目标方法的参数
     * target:目标对象
     */
    void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;

}

如同

public Object invoke(){
   
   
    Object retVal = 调用目标方法;
    调用AfterReturningAdvice#afterReturning方法
    return retVal;
}
ThrowsAdvice接口

package org.springframework.aop;

public interface ThrowsAdvice extends AfterAdvice {
   
   

}

此接口上没有任何方法,因为方法由反射调用,实现类必须实现以下形式的方法,前3个参数是可选的,最后一个参数为需要匹配的异常的类型。

void afterThrowing([Method, args, target], ThrowableSubclass);
有效方法的一些例子如下:

public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException)
public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)

通知包装器

负责将各种非MethodInterceptor类型的通知(Advice)包装为MethodInterceptor类型。

刚才有说过:Aop中所有的Advice最终都会转换为MethodInterceptor类型的,组成一个方法调用链,然后执行

3个包装器类


```java
MethodBeforeAdviceInterceptor
AfterReturningAdviceInterceptor
ThrowsAdviceInterceptor
MethodBeforeAdviceInterceptor类

这个类实现了MethodInterceptor接口,负责将MethodBeforeAdvice方法前置通知包装为MethodInterceptor类型,创建这个类型的对象的时候需要传递一个MethodBeforeAdvice类型的参数,重点是invoke方法

```java
package org.springframework.aop.framework.adapter;

@SuppressWarnings("serial")
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

    private final MethodBeforeAdvice advice;

    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }


    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //负责调用前置通知的方法
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        //继续执行方法调用链
        return mi.proceed();
    }

}

AfterReturningAdviceInterceptor类

这个类实现了MethodInterceptor接口,负责将AfterReturningAdvice方法后置通知包装为MethodInterceptor类型,创建这个类型的对象的时候需要传递一个AfterReturningAdvice类型的参数,重点是invoke方法

public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
   
   

    private final AfterReturningAdvice advice;

    public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
   
   
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }


    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
   
   
        //先执行方法调用链,可以获取目标方法的执行结果
        Object retVal = mi.proceed();
        //执行后置通知
        this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        //返回结果
        return retVal;
    }

}

ThrowsAdviceInterceptor类

这个类实现了MethodInterceptor接口,负责将ThrowsAdvice异常通知包装为MethodInterceptor类型,创建这个类型的对象的时候需要传递一个Object类型的参数,通常这个参数是ThrowsAdvice类型的,重点是invoke方法

package org.springframework.aop.framework.adapter;

public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
   
   

    private static final String AFTER_THROWING = "afterThrowing";

    private final Object throwsAdvice;

    //创建ThrowsAdviceInterceptor
    public ThrowsAdviceInterceptor(Object throwsAdvice) {
   
   
        Assert.notNull(throwsAdvice, "Advice must not be null");
        this.throwsAdvice = throwsAdvice;
        //获取异常通知中定义的所有方法(public、默认的、protected、private)
        Method[] methods = throwsAdvice.getClass().getMethods();
        //轮询methods
        for (Method method : methods) {
   
   
            //方法名称为afterThrowing && 方法参数为1或者4
            if (method.getName().equals(AFTER_THROWING) &&
                    (method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
   
   
                //获取方法的最后一个参数类型
                Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];
                //判断方法参数类型是不是Throwable类型的
                if (Throwable.class.isAssignableFrom(throwableParam)) {
   
   
                    // 缓存异常处理方法到map中(异常类型->异常处理方法)
                    this.exceptionHandlerMap.put(throwableParam, method);
                }
            }
        }
        //如果exceptionHandlerMap,抛出异常,所以最少要有一个异常处理方法
        if (this.exceptionHandlerMap.isEmpty()) {
   
   
            throw new IllegalArgumentException(
                    "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
        }
    }


    /**
     * 获取异常通知中自定义的处理异常方法的数量
     */
    public int getHandlerMethodCount() {
   
   
        return this.exceptionHandlerMap.size();
    }


    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
   
   
        try {
   
   
            //调用通知链
            return mi.proceed();
        }
        catch (Throwable ex) {
   
   
            //获取异常通知中自定义的处理异常的方法
            Method handlerMethod = getExceptionHandler(ex);
            //当处理的方法不为空
            if (handlerMethod != null) {
   
   
                //调用异常处理方法
                invokeHandlerMethod(mi, ex, handlerMethod);
            }
            //继续向外抛出异常
            throw ex; //@1
        }
    }

    /**
     * 获取throwsAdvice中处理exception参数指定的异常的方法
     */
    @Nullable
    private Method getExceptionHandler(Throwable exception) {
   
   
        //获取异常类型
        Class<?> exceptionClass = exception.getClass();
        //从缓存中获取异常类型对应的方法
        Method handler = this.exceptionHandlerMap.get(exceptionClass);
        //来一个循环,查询处理方法,循环条件:方法为空 && 异常类型!=Throwable
        while (handler == null && exceptionClass != Throwable.class) {
   
   
            //获取异常的父类型
            exceptionClass = exceptionClass.getSuperclass();
            //从缓存中查找异常对应的处理方法
            handler = this.exceptionHandlerMap.get(exceptionClass);
        }
        //将查找结果返回
        return handler;
    }

    //通过反射调用异常通知中的异常方法
    private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
   
   
        //构建方法请求参数
        Object[] handlerArgs;
        //若只有1个参数,参数为:异常对象
        if (method.getParameterCount() == 1) {
   
   
            handlerArgs = new Object[] {
   
   ex};
        }
        else {
   
   
            //4个参数(方法、方法请求参数、目标对象、异常对象)
            handlerArgs = new Object[] {
   
   mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
        }
        try {
   
   
            //通过反射调用异常通知中的方法
            method.invoke(this.throwsAdvice, handlerArgs);
        }
        catch (InvocationTargetException targetEx) {
   
   
            throw targetEx.getTargetException();
        }
    }

}

从上面可以看出,异常通知,自定义处理异常的方法有几个特点

方法名称必须为afterThrowing
方法参数必须1个或4个,最后一个参数是Throwable类型或其子类型
可以在异常处理中记录一些异常信息,这个还是比较有用的,但是注意一点目标方法抛出的异常最后还是会向外继续抛出@1
光讲源码,大家看着枯燥乏味,来点案例。


先来一个类,用来模拟用户资金操作:充值、提现、查询资金余额;提现的时候余额不足的时候,会抛出异常。

package com.javacode2018.aop.demo4;

//模拟资金操作
public class FundsService {
   
   
    //账户余额
    private double balance = 1000;

    //模拟提现
    double recharge(String userName, double price) {
   
   
        System.out.println(String.format("%s提现%s", userName, price));
        balance += price;
        return balance;
    }

    //模拟提现
    double cashOut(String userName, double price) {
   
   
        if (balance < price) {
   
   
            throw new RuntimeException("余额不足!");
        }
        System.out.println(String.format("%s提现%s", userName, price));
        balance -= price;
        return balance;
    }

    //获取余额
    double getBalance(String userName) {
   
   
        return balance;
    }
}

案例1:前置通知拦截非法访问

资金操作的所有方法都需要验证用户名,当用户名不是“路人”的时候,直接抛出非法访问异常。

package com.javacode2018.aop.demo4;

import org.junit.Test;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;

public class AopTest4 {
   
   

    @Test
    public void test1() {
   
   
        //代理工厂
        ProxyFactory proxyFactory = new ProxyFactory(new FundsService());
        //添加一个方法前置通知,判断用户名不是“路人”的时候,抛出非法访问异常
        proxyFactory.addAdvice(new MethodBeforeAdvice() {
   
   
            @Override
            public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
   
   
                String userName = (String) args[0];
                //如果不是路人的时候,抛出非法访问异常
                if (!"路人".equals(userName)) {
   
   
                    throw new RuntimeException(String.format("[%s]非法访问!", userName));
                }
            }
        });
        //通过代理工厂创建代理
        FundsService proxy = (FundsService) proxyFactory.getProxy();
        //调用代理的方法
        proxy.recharge("路人", 100);
        proxy.recharge("张学友", 100);
    }
}

运行输出

路人提现100.0

java.lang.RuntimeException: [张学友]非法访问!

    at com.javacode2018.aop.demo4.AopTest4$1.before(AopTest4.java:25)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:55)

案例2:通过异常通知记录异常

通过异常通知来捕获所有方法的运行,发现异常之后,通知开发修复bug。

public static class SendMsgThrowsAdvice implements ThrowsAdvice {
   
   
    //注意方法名称必须为afterThrowing
    public void afterThrowing(Method method, Object[] args, Object target, RuntimeException e) {
   
   
        //监控到异常后发送消息通知开发者
        System.out.println("异常警报:");
        System.out.println(String.format("method:[%s],args:[%s]", method.toGenericString(), Arrays.stream(args).collect(Collectors.toList())));
        System.out.println(e.getMessage());
        System.out.println("请尽快修复bug!");
    }
}

@Test
public void test2() {
   
   
    //代理工厂
    ProxyFactory proxyFactory = new ProxyFactory(new FundsService());
    //添加一个异常通知,发现异常之后发送消息给开发者尽快修复bug
    proxyFactory.addAdvice(new SendMsgThrowsAdvice());
    //通过代理工厂创建代理
    FundsService proxy = (FundsService) proxyFactory.getProxy();
    //调用代理的方法
    proxy.cashOut("路人", 2000);
}

运行输出

异常警报:
method:[double com.javacode2018.aop.demo4.FundsService.cashOut(java.lang.String,double)],args:[[路人, 2000.0]]
余额不足!
请尽快修复bug!

java.lang.RuntimeException: 余额不足!

    at com.javacode2018.aop.demo4.FundsService.cashOut(FundsService.java:18)

切入点(PointCut)相关类

通知(Advice)用来指定需要增强的逻辑,但是哪些类的哪些方法中需要使用这些通知呢?这个就是通过切入点来配置的。在这里插入图片描述

代理创建过程源码解析

先看一段代码

//代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new FundsService());
//添加一个方法前置通知,判断用户名不是“路人”的时候,抛出非法访问异常
proxyFactory.addAdvice(new MethodBeforeAdvice() {
   
   
    @Override
    public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
   
   
        String userName = (String) args[0];
        //如果不是路人的时候,抛出非法访问异常
        if (!"路人".equals(userName)) {
   
   
            throw new RuntimeException(String.format("[%s]非法访问!", userName));
        }
    }
});
//通过代理工厂创建代理
FundsService proxy = (FundsService) proxyFactory.getProxy();
我们将上面代码拆分一下,变成下面这样

//1.创建代理所需参数配置(如:采用什么方式的代理、通知列表等)
AdvisedSupport advisedSupport = new AdvisedSupport();
//如:添加一个前置通知
advisedSupport.addAdvice(new MethodBeforeAdvice() {
   
   
    @Override
    public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
   
   
        String userName = (String) args[0];
        //如果不是路人的时候,抛出非法访问异常
        if (!"路人".equals(userName)) {
   
   
            throw new RuntimeException(String.format("[%s]非法访问!", userName));
        }
    }
});
//设置被代理的目标对象
FundsService target = new FundsService();
advisedSupport.setTarget(target);

//2.根据配置信息获取AopProxy对象,AopProxy用来负责创建最终的代理对象
// AopProxy接口有2个实现类(JDK动态代理、cglib代理)
// 具体最终会使用哪种方式,需要根据AdvisedSupport中指定的参数来判断
// 创建AopProxy使用了简单工厂模式
AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
//通过AopProxy工厂获取AopProxy对象
AopProxy aopProxy = aopProxyFactory.createAopProxy(advisedSupport);

//3.通过AopProxy创建代理对象
Object proxy = aopProxy.getProxy();

从上面可以看出创建代理有3个步骤。

创建代理3大步骤
创建代理所需参数配置
根据代理参数获取AopProxy对象
通过AopProxy获取代理对象


根据代理参数获取AopProxy对象

TargetClassAware接口

比较简单的一个接口,定义了一个方法,用来获取目标对象类型。

所谓目标对象:就是被代理对象,比如上面的fundsService对象。

package org.springframework.aop;

public interface TargetClassAware {
   
   
    @Nullable
    Class<?> getTargetClass();
}

ProxyConfig类

这个类比较关键了,代理配置类,内部包含了创建代理时需要配置的各种参数。

package org.springframework.aop.framework;

/**
 * 对外提供统一的代理参数配置类,以确保所有代理创建程序具有一致的属性
 */
public class ProxyConfig implements Serializable {
   
   

    // 标记是否直接对目标类进行代理,而不是通过接口产生代理
    private boolean proxyTargetClass = false;

    // 标记是否对代理进行优化。启动优化通常意味着在代理对象被创建后,增强的修改将不会生效,因此默认值为false。
    // 如果exposeProxy设置为true,即使optimize为true也会被忽略。
    private boolean optimize = false;

    // 标记是否需要阻止通过该配置创建的代理对象转换为Advised类型,默认值为false,表示代理对象可以被转换为Advised类型
    boolean opaque = false;

    // 标记代理对象是否应该被aop框架通过AopContext以ThreadLocal的形式暴露出去。
    // 当一个代理对象需要调用它自己的另外一个代理方法时,这个属性将非常有用。默认是是false,以避免不必要的拦截。
    boolean exposeProxy = false;

    // 标记该配置是否需要被冻结,如果被冻结,将不可以修改增强的配置。
    // 当我们不希望调用方修改转换成Advised对象之后的代理对象时,这个配置将非常有用。
    private boolean frozen = false;


    //省略了属性的get set方法
}

最新2020整理收集的一些面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud等详细讲解,也有详细的学习规划图,面试题整理等,我感觉在面试这块讲的非常清楚:获取面试资料只需:[点击这里领取!!!] 暗号:CSDN在这里插入图片描述

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部