Spring AOP(二):代理模式与动态代理

160 阅读7分钟

代理与代理模式

代理(Proxy) :在用户与目标之间的中间人角色,软件开发中一般使用代理类来扩展与增强目标类的功能与方法。

代理模式(Proxy Pattern) :为一个真实对象提供一个代理替身,以控制对这个对象的访问。即通过代理对象访问目标对象。用以实现在目标对象的基础上,增强额外的功能操作,即扩展目标对象的功能

被代理的对象一般为:远程对象、无法修改的对象、创建开销大的对象或需要安全控制的对象。

代理模式有不同的形式,主要有静态代理和动态代理:

  • 静态代理:代理类需要实现与目标类相同的接口
  • 动态代理:分为JDK代理和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口)

静态代理

由程序员创建或特定工具自动生成源代码,从 JVM 的角度来说,静态代理是在编译时就已经将接口,被代理类,代理类等确定下来。

核心思想:

  1. 实现接口:目标对象与代理对象一起实现相同的接口或者是继承相同父类
  2. 代理对象需要手动创建

具体定义:

  1. 需要实现的接口或父类 :ITeacherDao
  2. 目标对象需实现或继承父类:TeacherDao implement ITeacherDao
  3. 创建静态代理对象,也需实现接口,并将目标对象放入:TeacherStaticProxy
  4. 调用:创建目标对象和代理对象,并调用代理对象方法,代理对象中会调用目标对象

Target Object

/**
 * 需要被代理的接口  Target Object
 */
public interface ITeacherDao {

    /**
     * 代理方法
     */
    void teach();
}

/**
 * 具体实现类  Target Object Impl
 */
public class TeacherDaoImpl implements ITeacherDao {

    @Override
    public void teach() {
        System.out.println("授课中。。。。。。");
    }
}

Proxy Object

/**
 * 静态代理类 Proxy Object
 */
public class TeacherStaticProxy implements ITeacherDao {
    private ITeacherDao target;
    public TeacherStaticProxy(ITeacherDao target) {
        this.target = target;
    }
    
    @Override
    public void teach() {
        // 方法增强
        System.out.println("proxy  start");
        target.teach();
        // 方法增强
        System.out.println("proxy end");
    }
}

Client

/**
 * 静态代理客户端测试
 */
public class StaticTeacherClient {
    public static void main(String[] args) {
        // 创建目标对象(被代理对象)
        ITeacherDao target = new TeacherDaoImpl();
        // 创建代理对象,同时将被代理对象传递给代理对象
        ITeacherDao proxy = new TeacherStaticProxy(target);
        // 通过代理对象方法执行,调用目标对象方法
        proxy.teach();
    }
}

动态代理

相比于静态代理来说,动态代理更加灵活。不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,就可以直接代理实现类( CGLIB 动态代理机制)。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

JDK 动态代理--目标对象实现接口

核心思想:

  1. 在JDK动态代理机制中,InvocationHandler接口和Proxy类是核心
  2. JDK实现代理需要使用newProxyInstance方法生成代理对象
//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }
  1. 代理对象利用反射机制调用 invoke 方法执行
public interface InvocationHandler {

    // 调用代理对象方法
    // proxy: 动态生成的代理类
    // method: 与代理类对象调用的方法相对应
    // args :当前方法的调用参数
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

具体示例:

Proxy Factory

public class DynamicProxyFactory {
    /**
     * target Object
     */
    private Object target;

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

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("JDK代理开始~~");
                    //反射机制调用目标对象的方法
                    Object returnVal = method.invoke(target, args);
                    System.out.println("JDK代理提交");
                    return returnVal;
                }
            });
    }
}

Client

public class DynamicProxyClient {
    public static void main1(String[] args) {
        ITeacherDao target = new TeacherDaoImpl();

        ITeacherDao proxyInstance = (ITeacherDao) new DynamicProxyFactory(target).getProxyInstance();
        // proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
        System.out.println("proxyInstance=" + proxyInstance.getClass());

        proxyInstance.teach();
    }
}

Cglib 动态代理--目标对象不用实现接口

核心思想:

  1. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
  1. 通过 Enhancer 类的 create()创建代理类(子类对象),并设置父类与回调函数

具体示例:

Target Object

public class CglibTeacherDao {

    public void teach() {
        System.out.println(" 老师授课中,cglib代理,不需要实现接口 ");
    }
}

Proxy Factory

public class DynamicCglibProxyFactory implements MethodInterceptor {
    /**
     * target Object
     */
    private Object target;
    
    public DynamicCglibProxyFactory(Object target) {
        this.target = target;
    }
    //返回一个代理对象:  是 target 对象的代理对象
    public Object getProxyInstance() {
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib代理模式 ~~ 开始");
        Object returnVal = method.invoke(target, objects);
        System.out.println("Cglib代理模式 ~~ 提交");
        return returnVal;
    }
}

两种动态代理方式对比

  1. 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
  2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。
  3. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截
  4. 在AOP编程中如何选择代理模式:
    1. 目标对象需要实现接口,用JDK代理
    2. 目标对象不需要实现接口,用Cglib代理
  1. Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
  2. 代理的类不能为final,否则报错java.lang.IllegalArgumentException
  3. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

静态代理与动态代理对比

  • 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。
  • 可扩展性:静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改。
  • JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

Spring AOP

Spring AOP 技术的本质就是一个动态代理的过程,Spring 中引入了 java 原生代理和 CGLib 代理,并依据场景选择基于哪种代理机制对目标对象进行增强。在Spring中 AOP 代理由 Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,Spring 容器在完成对 bean 对象的创建之后会执行初始化操作,而 AOP 初始化的过程就发生在 bean 的后置初始化阶段,整体流程可以概括为:

  1. 从容器中获取所有的切面定义;
  2. 筛选适用于当前 bean 的增强器集合;
  3. 依据增强器集合基于动态代理机制生成相应的增强代理对象。

Spring创建代理的规则为:

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

强制使用 CGLIB 需要注意以下事项:

  • 不能建议使用 final 方法,因为它们不能在运行时生成的子类中被覆盖
  • proxyTargetClass 属性需要设置为 true

当我们在调用一个被增强的方法时,相应的拦截器会依据连接点的方位在适当的位置触发对应的增强定义,从而最终实现 AOP 中定义的各类增强语义。