Spring AOP 的实现原理

2020-07-11  本文已影响0人  码而优则仕

Spring AOP 的实现原理

首先要了解代理模式:

proxy类图.png

抽象主题:可以是抽象类或接口

代理类和被代理类都要继承或实现抽象类或接口

对象实例化的时候使用代理类(持有被代理对象),用户只需要面向抽象类或接口来编程

Spring 实现AOP 需要做的事情就是生成一个代理类,替换掉真正的实现类来对外提供服务

静态代理模式:

示例代码如下:

/**
 * 抽象主题
 */
public interface ToCPayment {

    void pay();
}
/**
 * 被代理类
 */
public class ToCPaymentImpl implements ToCPayment {

    /**
     * 用户只和这个 pay方法打交道,以用户的名义进行支付
     * 用户只关心支付功能,但是背后的取钱和转账操作需要人实现
     * 这个实现就是支付宝,支付宝作为代理类存在
     *
     */
    @Override
    public void pay() {
        System.out.println("以用户的名义进行支付");
    }
}
/**
 * 代理类
 */
public class AlipayToC implements ToCPayment {

    /**
     * 代理类需要持有 被代理类 ToCPaymentImpl
     */
    @Autowired
    private ToCPayment toCPayment;

    /**
     * 执行被代理类的方法---
     * 加入事前逻辑
     * 事后逻辑
     */
    @Override
    public void pay() {
        beforePay();
        toCPayment.pay();
        afterPay();
    }

    private void afterPay() {
        System.out.println("支付给商家");
    }

    private void beforePay() {
        System.out.println("从招商银行取款");
    }

    public AlipayToC(ToCPayment toCPayment) {
        this.toCPayment = toCPayment;
    }
}
/**
 * 抽象主题
 */
public interface ToBPayment {

    void pay();
}
/**
 * 被代理类
 */
public class ToBPaymentImpl implements ToBPayment {

    /**
     * 用户只和这个 pay方法打交道,以用户的名义进行支付
     * 用户只关心支付功能,但是背后的取钱和转账操作需要人实现
     * 这个实现就是支付宝,支付宝作为代理类存在
     */
    @Override
    public void pay() {
        System.out.println("以公司的名义进行支付");
    }
}
/**
 * 代理类
 */
public class AlipayToB implements ToBPayment {

    /**
     * 代理类需要持有 被代理类 ToBPaymentImpl
     */
    @Autowired
    private ToBPayment toBPayment;

    /**
     * 执行被代理类的方法---
     * 加入事前逻辑
     * 事后逻辑
     */
    @Override
    public void pay() {
        beforePay();
        toBPayment.pay();
        afterPay();
    }

    private void afterPay() {
        System.out.println("支付给商家");
    }

    private void beforePay() {
        System.out.println("从招商银行取款");
    }

    public AlipayToB(ToBPayment toBPayment) {
        this.toBPayment = toBPayment;
    }
}
public class ProxyDemo {

    public static void main(String args[]) {
        //静态代理模式
        //之所以叫静态代理,是因为代理对象(代理被代理对象的对象)需要在编译的时候就实现了
        //用户硬编码的静态代理模式--需要不同形式的代理被代理对象的时候,就需要硬编码的方式去
        //实现不同的代理对象,在编译前完成代码编写,编译后代理类就是一个实际的class文件
        ToCPayment toCPayment = new ToCPaymentImpl();
        ToCPayment alipayToC = new AlipayToC(toCPayment);
        alipayToC.pay();

        ToBPayment toBPayment = new ToBPaymentImpl();
        ToBPayment alipayToB = new AlipayToB(toBPayment);
        alipayToB.pay();

        //所以静态代理实现AOP的话,代理逻辑一变就要新的代理类,类的数量将成指数级别增长
        //所以需要动态代理来拯救

    }
}

对静态代理寻求改进

溯源ClassLoader

改进的切入点:

既然字节流能定义类的行为,我们能否根据一定规则去改动或者重新生成新的字节流,将切面逻辑织入其中,使其动态生成天生织入了切面逻辑的类。

行之有效的方案就是 动态代理机制:

该机制会根据接口或实现类目标类,计算出代理类的字节码并加载到JVM中去使用

Spring AOP 的实现原理之 JDK 动态代理

相比于静态代理模式:编译前完成代理类代码编写,编译后代理类就是一个实际的class文件;动态代理的代理类是在运行时动态生成的,编译完成后并没有实际的class文件;

JDK 动态代理涉及的核心类和接口。

Java.lang.refrect.Proxy

Java.lang.refrect.InvocationHandler

InvocationHandler接口就和我们的注解@Aspect一样,只有实现了 InvocationHandler接口的类才具有代理功能,相关的切面逻辑就存在于 InvocationHandler 的实现类里面

InvocationHandler 中只有一个方法,如下:

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

当我们通过动态代理对象调用一个方法的时候,这个被代理方法的调用就会被转发到实现了 InvocationHandler 接口的实现类的 invoke 方法。

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

参数讲解:

Object proxy:真实的代理对象(不是被代理对象)。

不同于静态代理模式,静态代理方式在调用被代理对象的方法的时候需要显式的调用代理对象的方法才可以。而动态代理方式来讲:在后续调用被织入了横切逻辑的方法的时候显式调用的是目标对象(被代理对象)本身的方法,而不是代理对象的方法。

静态代理调用的就是实现代理的代理对象的方法,而动态代理模式就是用户显式调用的是被代理对象的方法,效果和直接调用有代理逻辑的代理对象的方法一样。

设置这个参数的目的是不排出有时候用户想要获取真实的代理类的情况

Method method:表示的是目标对象(被代理对象)的方法的实例

Object[] args:方法里面需要用到的参数

Proxy类的讲解:

该类的作用是用来创建动态代理类的

InvocationHandler接口并非代理类,而是用来统一管理横切逻辑的Aspect,而真正的代理类是由Proxy类创建出来的

Proxy最关键的方法是:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
这个方法是用来创建代理类实例的,

参数讲解:

ClassLoader loader:需要使用哪个加载器来对生成的代理对象进行加载

Class<?>[] interfaces:用来表示将要给我们的代理类对象提供一组什么样的接口,声明代理类会最终去实现这些接口,通过这个动态代理类就可以调用接口数组中的所有接口声明的所有方法。

InvocationHandler h:表示当前动态代理对象的方法被调用的时候会关联到哪个InvocationHandler的实现类实例上;

该方法最后会返回最终被创建好的动态代理实例。



#### 下面使用JDK 动态代理改造我们的静态代理支付
//相当于AOP 中的 Aspect 用来封装通用的横切逻辑
public class AlipayInvocationHandler implements InvocationHandler {

    /**
     * 定义私有成员变量,保存被代理对象(目标对象),
     */
    private Object targetObject;

    /**
     * 带参数构造函数,赋值 被代理类对象(目标对象)
     *
     * @param targetObject
     */
    public AlipayInvocationHandler(Object targetObject) {
        this.targetObject = targetObject;
    }

    /**
     * 不同于静态代理模式,静态代理方式在调用被代理对象的方法的时候需要显式的调用代理对象的方法才可以。
     * 而动态代理方式来讲:在后续调用被织入了横切逻辑的方法的时候显式调用的是目标对象(被代理对象)本身的方法,
     * 而不是代理对象的方法。
     * <p>
     * <p>
     * 静态代理调用的就是实现代理的代理对象的方法,而动态代理模式就是用户显式调用的是被代理对象的方法,
     * 效果和直接调用有代理逻辑的代理对象的方法一样。
     *
     * 在目标类对象 targetObject 方法 method 被调用的时候 JDKProxy就会自动调用 InvocationHandler 的 invoke 方法,将动态生成的代理对象实例,被代理类实例的方法实例
     * 以及对应方法需要传入的参数
     *
     * @param proxy  Object proxy:真实的代理对象(不是被代理对象)。 设置这个参数的目的是不排出有时候
     *               用户想要获取真实的代理类的情况
     * @param method Method method:表示的是目标对象(被代理对象)的方法的实例
     * @param args   Object[] args:方法里面需要用到的参数
     * @return  完成对被代理对象方法的封装,返回被代理对象方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前置逻辑
        beforePay();
        //调用被代理对象方法实例的 invoke方法,执行被代理方法
        //要执行被代理方法,需要传如被代理类实例和相关方法参数
        Object result = method.invoke(targetObject, args);
        //后置逻辑
        afterPay();
        return result;
    }

    private void afterPay() {
        System.out.println("支付给商家");
    }

    private void beforePay() {
        System.out.println("从招商银行取款");
    }
}
/**
 * 针对 AlipayInvocationHandler 这个 Aspect 创建出动态代理实例来
 */
public class JdkDynamicProxyUtil {

    /**
     * @param targetObject 被代理对象类的实例
     * @param handler      具体Aspect 如:AlipayInvocationHandler
     * @param <T>          被代理对象类 类型
     * @return
     */
    public static <T> T newProxyInstance(T targetObject, InvocationHandler handler) {
        ClassLoader classLoader = targetObject.getClass().getClassLoader();
        Class<?>[] interfaces = targetObject.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}
   public static void main(String args[]) {
        //静态代理模式
        //之所以叫静态代理,是因为代理对象(代理被代理对象的对象)需要在编译的时候就实现了
        //用户硬编码的静态代理模式--需要不同形式的代理被代理对象的时候,就需要硬编码的方式去
        //实现不同的代理对象
//        ToCPayment toCPayment = new ToCPaymentImpl();
//        ToCPayment alipayToC = new AlipayToC(toCPayment);
//        alipayToC.pay();
//
//        ToBPayment toBPayment = new ToBPaymentImpl();
//        ToBPayment alipayToB = new AlipayToB(toBPayment);
//        alipayToB.pay();

        //所以静态代理实现AOP的话,代理逻辑一变就要新的代理类,类的数量将成指数级别增长
        //所以需要动态代理来拯救

        //创建被代理对象实例
        ToCPayment toCPayment = new ToCPaymentImpl();
        //将 被代理对象实例 传入创建出对应切面类实例
        InvocationHandler handlerToC = new AlipayInvocationHandler(toCPayment);
        ToCPayment proxyToCPayment = JdkDynamicProxyUtil.newProxyInstance(toCPayment, handlerToC);
        proxyToCPayment.pay();

        ToBPayment toBPayment = new ToBPaymentImpl();
        InvocationHandler handlerToB = new AlipayInvocationHandler(toBPayment);
        ToBPayment proxyToBPayment = JdkDynamicProxyUtil.newProxyInstance(toBPayment, handlerToB);
        proxyToBPayment.pay();
    }
}

输出:

从招商银行取款
以用户的名义进行支付
支付给商家
从招商银行取款
以公司的名义进行支付
支付给商家

Process finished with exit code 0

上一篇下一篇

猜你喜欢

热点阅读