浅谈 Java 动态代理

2023-05-16 11:03:03 浏览数 (1)

前言

这几天终于把 Java 基础进度拉满了,这个知识比较绕,记录一手便于后期复习

代理模式

Proxy Pattern是程序设计中的一种设计模式,又称委托模式。代理对象对真实对象提供一种代理以控制其他对象对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。、

通俗地来讲代理模式就是我们生活中常见的中介。

代理可分为静态代理和动态代理,先康康容易理解的静态对象

静态代理

背景:租客找房东租房,中间经过租房中介,以此为例进行描述。那么这里租房中介是代理对象,房东是真实对象。

用户只关心接口的功能,而不在乎谁提供了功能,首先定义接口RentHouse

代码语言:javascript复制
public interface RentHouse {
    // 出租房屋
    void rentHouse();
}

接口(即租房)真正实现者是真实对象(即房东),但它不与用户直接接触,而是通过代理对象(即租房中介),他们都需要实现租房的接口,这是静态代理的前提。代理对象实现了接口所以它能够直接与用户接触。

代码语言:javascript复制
// 房东
public class Host implements RentHouse{
    @Override
    public void rentHouse() {
        System.out.println("收钱...交房...");
    }
}
代码语言:javascript复制
// 中介
public class RentHouseProxy implements RentHouse{
    private Host host;

    public RentHouseProxy(Host host) {
        this.host = host;
    }

    @Override
    public void rentHouse() {
        System.out.println("寻找客源...");
        System.out.println("签署租房合同...");

        host.rentHouse();  // 核心业务

        System.out.println("提供后续业务...");
    }
}

用户调用代理对象时,代理对象内部调用了真实对象,所以代理对象可以在不修改真实对象的基础上附加和增强真实对象的功能。最后,创建一个主类实现业务:首先创建一个真实类的对象(房东),然后交给代理类的对象(租房中介),让代理对象来完成租房的一系列操作,而真实对象只需要关注核心业务。

代码语言:javascript复制
// 测试类
public class Demo1 {
    public static void main(String[] args) {
        Host host = new Host();
        RentHouseProxy rentHouseProxy = new RentHouseProxy(host);
        rentHouseProxy.rentHouse();
    }
}

这就是静态代理。在静态代理实现中,一个真实类对应一个代理类,并且代理类在编译期间就已经确定。因此静态代理存在明显的缺陷:如果需要代理多个类,每个类都会有一个代理类,会导致代理类无限制扩展;如果类中有多个方法,同样的代理逻辑需要反复实现、应用到每个方法上,一旦接口增加方法,目标对象与代理对象都要进行维护。

通俗来讲,我们租房要找租房中介,卖车要找卖车中介,租游戏号需要租游戏号中介,会导致有很多很多中介,那能不能有一个灵活的中介帮助我们完成呢?

这时候就轮到 \ 动态代理 // 闪亮出场了(撒花

动态代理

动态代理允许使用一种方法的单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务。动态代理中,代理类并不是在 Java 代码中实现,而是在运行时期生成,而且一个动态代理类可以为多个不同的接口实现代理,相比静态代理,动态代理可以很方便的对真实类的方法进行统一处理。

动态代理常见的有JDK动态代理Cglib动态代理

JDK动态代理

首先如何为Java对象动态创建一个代理对象?

java.lang.reflect.Proxy类提供了为对象产生代理对象的方法newProxyInstance()

代码语言:javascript复制
public static Object newProxyInstance(
	ClassLoader loader,     // 用于指定用哪个类加载器去加载生成的代理类
	Class<?>[] interfaces,  // 指定接口,这些接口用于指定生成的代理长什么样,也就是有哪些方法
	InvocationHandler h     // 用于指定生成的代理对象要干什么事情
)

然后康康在哪指定代理对象干的事情

实现动态代理的主要目的是 “实现附加业务”

InvocationHandler是一个接口,所以需要创建它的实现类并传进去,并且它只有一个方法invoke

代码语言:javascript复制
public interface InvocationHandler {
    public Object invoke(
        Object proxy,   // 核心业务对象。代理后的对象,而不是原始对象
        Method method,  // 核心业务。通过invoke方法调用
        Object[] args   // 核心业务的参数。调用方法是传递的实参
    )throws Throwable;
}  // 返回值:附加业务要与核心业务的返回值相同

动态代理编程分为三步 :

  1. 创建原始对象
  2. 完成 InvocationHandler 代理
  3. 调用 Proxy.newProxyInstance

首先定义接口,目标类要完成的功能

代码语言:javascript复制
public interface RentHouse {
    // 出租房屋
    void rentHouse();
}

定义真实类,实现该接口

代码语言:javascript复制
// 房东
public class Host implements RentHouse{
    @Override
    public void rentHouse() {
        System.out.println("收钱...交房...");
    }
}

定义动态代理类,在invoke()方法中完成代理类的功能

代码语言:javascript复制
// 代理类的调用处理器
public class MyInvocationHandler implements InvocationHandler {
    private Object object;
    
    public MyInvocationHandler() {}

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    //此函数在代理对象调用任何一个方法时都会被调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        //定义预处理的工作,当然你也可以根据 method 的不同进行不同的预处理工作
        System.out.println("寻找客户...");
        System.out.println("带客户看房...");
        System.out.println("签署租房合同...");

        Object invoke = method.invoke(object, args);

        System.out.println("提供后续业务...");
        return invoke;
    }
}

主类

代码语言:javascript复制
public class Demo1{
    public static void main(String[] args){
        //1.创建原始对象
        RentHouse host = new Host();
        //2.创建调用处理器对象
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(host);
        //3.动态生成代理对象
        RentHouse proxy = (RentHouse) Proxy.newProxyInstance(
                Demo1.class.getClassLoader(),
                new Class[]{RentHouse.class},
                myInvocationHandler);
        //4.客户端通过代理对象调用方法
        //本次调用将自动被代理处理器的invoke方法接收
        proxy.rentHouse();
    }
}

这就是JDK动态代理的过程,它无需声明式的创建 java 代理类,而是在运行过程中生成”虚拟”的代理类,被ClassLoader加载。从而避免了静态代理那样需要声明大量的代理类。

下图是动态代理的 uml 图

同样,JDK 动态代理也有一定局限性,它通过反射类Proxy和回调接口InvocationHandler实现动态代理,要求真实类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方式实现动态代理。

这时候就需要cglib动态代理出手了!

CGLIB动态代理

CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,在使用时需要导入相应 jar 包。和 JDK 动态代理不同,CGLIB 通过继承方式实现代理。CGLIB通过继承目标类,创建它的子类,在子类中重写父类中非 final 的方法,实现功能的修改。

CGLIB提供了一个类Enhancer,该类提供了create方法通过字节码技术生成动态代理类,提供setClassLoader方法指定类加载器、setSuperclass方法指定父类Class实例、提供setCallback方法指定代理类动作的MethodInterceptor实现类

代码语言:javascript复制
public interface MethodInterceptor extends Callback {
	Object intercept(Object o, Method method, Object[] objects, 
                     MethodProxy methodProxy)   // 生成的代理类对方法的代理引用,一般用不上
        throws Throwable;
}

那么完整的过程就是

定义真实类

代码语言:javascript复制
// 原始对象
public class Host {
    public void rendHouse() {
        System.out.println("收钱...");
    }
}

定义动态代理类,在invoke()方法中完成代理类的功能

代码语言:javascript复制
//所需的代理类
public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer=new Enhancer();
    private Object object;

    public CglibProxy(Object object) {
        this.object = object;
    }

    public Object getProxy(){
        //设置需要创建子类的类
        enhancer.setClassLoader(object.getClass().getClassLoader());
        enhancer.setSuperclass(object.getClass());
        //创造object的子类并把Callback指向this,也就是当前这个proxy对象
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("寻找客户...");
        System.out.println("带客户看房...");
        System.out.println("签署租房合同...");

        Object invoke = method.invoke(object, args);

        System.out.println("提供后续业务...");
        return invoke;
    }
}

主类

代码语言:javascript复制
public class Demo1{
    public static void main(String[] args){
        CglibProxy proxy = new CglibProxy(new Host());
        Host host = (Host) proxy.getProxy();
        host.rentHouse();
    }
}
总结
  • 静态代理:对每个方法,需要静态编码(理解简单,但代码繁琐)
  • 动态代理:对目标对象的方法每次被调用,进行动态拦截

动态代理中,CGLIB 动态代理和 JDK 动态代理的区别显而易见,但是实现逻辑差不多。

  • CGLIB 代理类是通过实现MethodInterceptor,重写 intercept 方法,通过生成被代理类的子类来达到代理增强代码的目的;而 JDK 代理是通过实现 InvocationHandler,重写 invoke 方法,通过生成接口的代理类来达到代码增强的目的,所以 JDK 动态代理的实现需要接口,CGLIB 则不需要。
  • JDK 动态代理是在运行时JDK根据 class 文件的格式动态拼装 class 文件,并加载到 jvm 中生成代理对象的。而 CGLIB 动态代理是通过 ASM 库来操作 class 文件动态生成代理类的
  • CGLIB 创建的动态代理对象性能比 JDK 创建的动态代理对象的性能高不少,但是 CGLIB 在创建代理对象时所花费的时间却比 JDK 多得多,所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适
  • JDK动态代理原理是拦截器 反射机制,将原始类拦下进行包装;CGLIB动态代理原理是动态字节码,通过修改字节码生成子类
  • JDK动态代理是针对接口的代理技术;CGLIB动态代理针对继承的代理技术

参考链接: 说透常见设计模式之代理模式 | 程序那点事 静态代理、动态代理概念及使用 | 小何┌ Java篇之Java动态代理 | Arsene.Tang Java代理 | hunter-w

0 人点赞