本文我们来学习 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。Spring AOP 是基于动态代理来实现 AOP 的;

1. 代理模式

1.1 代理弄模式的定义

        代理模式,也叫委托模式

        定义:为其他对象提供一种代理以控制这个对象的访问它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用

       某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

        使用代理前:

         使用代理后:

        就像生活中的例子,生活中的代理类似与艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人沟通。

1.2 代理模式的主要角色:

1、Subject:业务接口类。可以是抽象类或者接口(不一定有)。

2、RealSubject:业务实现类。具体的业务执行,也就是被代理对象。

3、Proxy:代理类。RealSubject的代理。

比如让xox代言:

        Subject:小偶像就是提前定义了小偶像要和合作方做的事情,交给经纪人代理处理;

        RealSubject:小偶像。

        Proxy:经纪人。

        UML类图如下:

        代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。根据代理的创建时期,代理模式分为静态代理和动态代理

        静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。

        动态代理:在程序运行时,运用反射机制动态创建而成

2. 静态代理

        静态代理:在程序运行前,代理类的 .class文件就已经存在了。(签合约之前,小偶像已经将自己能做到的义务写到了文案里面,就等合约方签字)。

        我们通过代码来加深理解。以经纪人为例:

2.1 定义接口

        (定义小偶像要接的剧和能参加的活动,也是经纪人需要做的事情):

public interface Xox {
    void receiveJuben();
    //xox要接受剧本
}

2.2 实现接口

        (沈梦瑶要接剧本): 

public class ShenMengyao implements Xox{

    @Override
    public void receiveJuben() {
        System.out.println("我是沈梦瑶,我要接剧本");
    }
}

2.3 代理

        (经纪人,帮沈梦瑶接剧本)

public class XoxProxy implements Xox{
    private  Xox target;
    public XoxProxy(Xox target){
        this.target = target;
    }
    @Override
    public void receiveJuben() {
        //代理前
        System.out.println("我是经纪人, 开始代理");
        //出租房子
        target.receiveJuben();
        //代理后
        System.out.println("我是经纪人, 结束代理");
    }
}

2.4 使用

package com.example.aop;

public class Main {
    public static void main(String[] args) {
        Xox xox = new ShenMengyao();
        //创建代理类
        XoxProxy xoxProxy = new XoxProxy(xox);
        //通过代理类访问⽬标⽅法
        xoxProxy.receiveJuben();
    }
}

        运行结果:

 

         上面这个代理实现方式就是静态代理(仿佛啥也没干)。从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成的,非常不灵活。所以日常开发几乎看不到静态代理的场景。

        接下来新增需求:经纪人又新增了其他业务:代理小偶像解约。我们就需要对上述代码进行修改。

2.5 更新代理内容

1、接口定义修改:

package com.example.aop;

public interface Xox {
    void receiveJuben();
    //xox要接受剧本
    void Termination();
    //小偶像要解约
}

2、接口实现修改

package com.example.aop;

public class ShenMengyao implements Xox{

    @Override
    public void receiveJuben() {
        System.out.println("我是沈梦瑶,我要接剧本");
    }

    @Override
    public void Termination() {
        System.out.println("我是沈梦瑶,我要和丝芭解约");
    }
}

3、代理类修改

package com.example.aop;

public class XoxProxy implements Xox{
    private  Xox target;
    public XoxProxy(Xox target){
        this.target = target;
    }
    @Override
    public void receiveJuben() {
        //代理前
        System.out.println("我是经纪人, 开始代理");
        //出租房子
        target.receiveJuben();
        //代理后
        System.out.println("我是经纪人, 结束代理");
    }

    @Override
    public void Termination() {
        //代理前
        System.out.println("我是经纪人, 开始代理");
        //出租房子
        target.Termination();
        //代理后
        System.out.println("我是经纪人, 结束代理");
    }
}

4、使用:  

package com.example.aop;

public class Main {
    public static void main(String[] args) {
        Xox xox = new ShenMengyao();
        //创建代理类
        XoxProxy xoxProxy = new XoxProxy(xox);
        //通过代理类访问⽬标⽅法
        xoxProxy.receiveJuben();
        System.out.println("-----------");
        xoxProxy.Termination();
    }
}

        从上述代码可以看出,我们修改xox接口,接需要在后续修改三个代码,这样十分加大了我们的 工作量,即每增加一个接口就需要实现类来完善该接口的方法,我们的代理也需要进行完善相应的代码,为了减少工作量,所以接下来学习动态代理技术;

3. 动态代理

        相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时,由JVM来实现。也就是说动态代理在程序运行时,根据需要动态创建生成

        比如经纪人,她不需要提前预测都有哪些业务,而是业务来了我再根据情况创建。

        先看代码再来理解。Java也对动态代理进行了实现,并给我们提供一些API,常见的实现方式有两种:

        1、JDK动态代理

        2、CGLIB动态代理

        动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

3.1 JDK动态代理

3.1.1 JDK动态代理实现步骤

1、定义一个接口及其实现类(静态代理中的 Xox和 shenmengyao)。

2、自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法),并自定义一些处理逻辑。

3、通过 Proxy.newProxyInstance(ClassLoader, Class<?>[ ]  Interfaces, InvocationHandler  h)方法创建代理对象。

3.1.2 定义JDK动态代理类

        创建 JDKInvocationHandler类 实现 InvocationHandler 接口:

package com.example.aop;

import org.springframework.cglib.proxy.InvocationHandler;

import java.lang.reflect.Method;

public class JDKInvocationHandler implements InvocationHandler {
    //目标对象,即被代理的对象
    private Object target;
 
    public JDKInvocationHandler(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理增强内容
        System.out.println("我是经纪人,开始代理");
        //通过反射调用被代理类的方法
        Object result = method.invoke(target, args);
        //代理增强内容
        System.out.println("我是经纪人,结束代理");
        return result;
    }
}

        创建一个代理对象并使用:

package com.example.aop;

import org.springframework.cglib.proxy.Proxy;

public class Main {
    public static void main(String[] args) {
        /**
         * JDK动态代理
         */

        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        ShenMengyao shenMengyao = new ShenMengyao();//目标对象
        /**
         * newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * loader:加载我们的被代理类的ClassLoad
         * interfaces:要实现的接口
         * h:代理要做的事情,需要实现 InvocationHandler 这个接口
         */
        Xox proxy = (Xox) Proxy.newProxyInstance(
                shenMengyao.getClass().getClassLoader(),
                new Class[]{Xox.class},
                new JDKInvocationHandler(shenMengyao)
        );
        //通过代理类访问⽬标⽅法
        proxy.receiveJuben();
        System.out.println("-----------");
        proxy.Termination();
    }
}

         运行程序,结果如下:

         假设代理的是类,而不是对象,代码如下:

package com.example.aop;

import org.springframework.cglib.proxy.Proxy;

public class Main {
    public static void main(String[] args) {
        /**
         * JDK动态代理
         */

        ShenMengyao shenMengyao  = new ShenMengyao();
        ShenMengyao proxy = (ShenMengyao) Proxy.newProxyInstance(
                shenMengyao.getClass().getClassLoader(),
                new Class[]{ShenMengyao.class},
                new JDKInvocationHandler(shenMengyao)
        );

        //通过代理类访问⽬标⽅法
        proxy.receiveJuben();
        System.out.println("-----------");
        proxy.Termination();
    }
}

          运行程序,结果如下:(报错了)

3.1.3 代码简单讲解

        主要是学习API的使用,我们按照 Java API 的规范来使用即可。

1、InvocationHandler:

        InvocationHandler 接口是 Java 动态代理的关键接口之一,它定义了一个单一方法 invoke(),用于处理被代理对象的方法调用

    public interface InvocationHandler {
        /**
         * 参数说明
         * proxy:被代理对象
         * method:被代理对象需要实现的⽅法,即其中需要重写的⽅法
         * args:method所对应⽅法的参数
         */
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable;
    }

        通过实现 InvocationHandler 接口,可以对被代理对象的方法进行功能增强。

2、Proxy:

        Proxy 类中使用频率最高的方法:newProxyInstance(),这个方法主要用来生成一个代理对象

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException
    {
        //...代码省略
    }

  这个方法一共有 3 个参数:

        loader:类加载器,用于加载被代理对象。

        interface:被代理类实现的一些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的一些类)。

        h:代理要做的事情,实现 InvocationHandler 接口的对象。

3.2 CGLIB动态代理

        JDK动态代理有一个最致命的问题,是只能代理实现了接口的类

        有些场景下,我们的业务码是直接实现的,并没有接口定义。为了解决这个问题,我们可以用 CGLIB 动态代理机制来解决。

        CGLIB(Code Generation Library)是一个基于 ASM 的字节码生产库,它允许我们在运行时对字节码进行修改和动态生成

        CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB。例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。(其中 Spring 是基于动态代理实现的,动态代理是基于反射实现的)

3.2.1 CGLIB 动态代理类实现步骤

1、定义一个类(被代理类)。

2、自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 动态代理中的 invoke 方法类似。

3、通过 Enhancer 类的 create() 创建代理类。

        接下来看实现:

3.2.2 添加依赖

        和 JDK 动态代理不同,CGLIB(Code Generation Library)实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

3.2.3 自定义MethodInterceptor(方法拦截器)

        实现 MethodInterceptor 接口:

import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;
 
public class CGLibInterceptor implements MethodInterceptor {
    private Object target;
 
    public CGLibInterceptor(Object target) {
        this.target = target;
    }
 
    /**
     * 调用代理对象的方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, org.springframework.cglib.proxy.MethodProxy proxy) throws Throwable {
        //代理增强内容
        System.out.println("我是中介,开始代理");
        Object result = method.invoke(target, args);
        //代理增强内容
        System.out.println("我是中介,结束代理");
        return result;
    }
}

3.2.4 创建代理类,并使用

        代理接口:

public class Main {
    public static void main(String[] args) {
        //目标对象
        Xox target = new ShenMengyao();
        Xox proxy  = (Xox) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
        proxy.receiveJuben();
        System.out.println("=============");
        proxy.
    Termination();
    }
    }

   运行程序,执行结果如下:

        代理类:

public class Main {
    public static void main(String[] args) {
        //目标对象
        Xox target = new ShenMengyao();
        ShenMengyao proxy  = (ShenMengyao) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
        proxy.receiveJuben();
        System.out.println("=============");
        proxy.Termination();
    }
    }

结果没有发生变化;

3.2.5 代码简单讲解

1、MethodInterceptor

        MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法

    public interface MethodInterceptor extends Callback {
        /**
         * 参数说明:
         * o: 被代理的对象
         * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
         * objects: ⽅法⼊参
         * methodProxy: ⽤于调⽤原始⽅法
         */
        Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
    }

2、Enhancer.create()

    public static Object create(Class type, Callback callback) {
        //...代码省略
    }

type:被代理类的类型(类或接口)

callback:自定义方法拦截器 MethodInterceptor

4. 常见面试题

1、什么是 AOP?
        AOP是 面向切面编程,也是一种思想,切面指的是某一类特定问题,所以 AOP 也可以理解为 面向切面编程。

2、Spring AOP的实现方式有哪些?
(1)基于注解(@Aspect 或 自定义注解)

(2)基于 xml

(3)基于代理

3、Spring AOP 的实现原理?
        基于动态代理实现的,其中的动态代理有两种形式:(1)JDK   (2)CGLIB

4、Spring 使用的是哪种代理方式?
        Spring 的 proxyTargetClass 默认为:false,其中:实现了接口,使用 JDK 代理;普通类:使用CGLIB代理。

        Spring Boot 从 2.X 之后,proxyTargetClass 默认为:true,默认使用 CGLIB 代理;

5、JDK 和 CGLIB 的区别?

        使用JDK 动态代理只能代理接口。

        使用 CGLIB 动态代理 既可以代理接口,也可以代理类。

6、总结
        1、AOP 是一种思想,是对某一类事情的集中处理。Spring 框架实现了AOP,称之为 Spring AOP。

        2、Spring AOP 场景的实现方式有两种:(1)基于注解@Aspect来实现。(2)基于自定义注解来实现,还有一些更原始的方式,比如基于代理、基于 xml 配置的方式,但目标比较少见。

        3、Spring AOP 是基于动态代理实现的,有两种方式:(1)基于 JDK 动态代理实现。(2)基于 CGLIB 动态代理实现。运行时使用哪种方式与项目配置的代理对象有关。

ps:本文就写到这里了,谢谢观看!!!

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐