Featured image of post Java编程思想_动态代理

Java编程思想_动态代理

🌏Java编程思想 第14章 类型信息 14.7 动态代理 🎯主要呈现的内容是对原书代码的理解、实践和反思;另外 书中只介绍了JDK动态代理的方式 我会补充CGLib的动态代理介绍

🎄Java编程思想·代理

代理是基本的设计模式之一 它是你为了提供额外的或不同的操作 而插入的用来代理“实际”对象的对象 这些操作通常涉及与“实际”对象的通信 因此代理通常充当着中间人的角色

🍭静态代理

下面我们看到第一个展示代理结构的简单程序示例:

SimpleProxy代理对象作为“中间人” 客户端通过这个“中间人” 执行了RealObject的方法 SimpleProxy展现出额外的功能 这是静态代理的实现方法

package bigbigmeng.online.SE_14_typeinfo.SE_14_7;

/**
@author Liu Xianmeng
@createTime 2023/9/18 8:23
@instruction
*/

// 方法接口
interface Interface {
    void doSomething();
    void somethingElse(String arg);
}
// 实际对象 -> 实现接口
class RealObject implements Interface {
    @Override
    public void doSomething() { System.out.println("doSomething"); }
    @Override
    public void somethingElse(String arg) {
        System.out.println("somethingElse " + arg);
    }
}
/**
 * 代理对象 -> 实现接口
 *
 * 持有被代理接口 从而能调用被代理对象的方法
 */
class SimpleProxy implements Interface {
    // 持有被代理接口(被代理对象)
    private Interface proxied;
    public SimpleProxy(Interface proxied) {
        this.proxied = proxied;
    }
    @Override
    public void doSomething() {
        // 代理对象提供的额外功能或其他功能
        System.out.println("SimpleProxy doSomething");
        // 被代理对象本身的功能
        proxied.doSomething();
    }
    @Override
    public void somethingElse(String arg) {
        // 代理对象提供的额外功能或其他功能
        System.out.println("SimpleProxy somethingElse " + arg);
        // 被代理对象本身的功能
        proxied.somethingElse(arg);
    }
}
@SuppressWarnings("all")
public class SimpleProxyDemo {
    /**
     * 使用多态 -> 
     * consumer方法的形式参数是Interface 
     * 所以这个方法不知道传入的是RealObject还是SimpleProxy
     * 传入不同的对象 表现出不同的子类对应的行为
     */
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
        consumer(new RealObject());
        System.out.println("**********************************");
        consumer(new SimpleProxy(new RealObject()));
    }
    /* OutPut:
            doSomething
            somethingElse bonobo
            **********************************
            SimpleProxy doSomething
            doSomething
            SimpleProxy somethingElse bonobo
            somethingElse bonobo
     */
}

🍭JDK动态代理

Java的动态代理比静态代理的思想更向前迈进了一步 因为它可以动态地创建代理对象并动态地处理所代理方法的调用 在动态代理行所做的调用都会被重定向到单一的调用处理器

下面是用JDK动态代理重写的SimpleProxyDemo.java :

package bigbigmeng.online.SE_14_typeinfo.SE_14_7;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
@author Liu Xianmeng
@createTime 2023/9/18 9:11
@instruction
*/

class DynamicProxyHandler implements InvocationHandler {
    // 持有被代理对象
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    /**
     * 当我们让代理实例对象proxy调用被代理对象的任何方法时 都会触发invoke方法的执行
     * 即 动态代理可以将所有的调用都重定向到调用处理器
     *
     * @param proxy  调用该方法的代理实例
     * @param method 被代理的方法
     * @param args   被代理的方法运行时的参数
     *
     * @return       返回代理对象
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[proxy]: " + proxy.getClass() + ", method: " + method + ", args: " + args);
        if(args != null) {
            int count = 1;
            for(Object arg : args) {
                System.out.println("param" + count + ": " + arg);
                ++count;
            }
        }
        // 🎯method.invoke将请求转发给被代理的对象,并传入必要的参数
        return method.invoke(proxied, args);
    }
}

@SuppressWarnings({"all"})
class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        System.out.println("被代理类本身执行方法:");
        iface.doSomething();
        System.out.println("代理类执行方法:");
        iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
        // 实际对象
        RealObject real = new RealObject();
        // 用实际对象调用consumer方法
        consumer(real);
        System.out.println("**********************************");
        // Insert a proxy and call again:
        // 插入一个代理对象并再次调用consumer方法
        Interface proxy = (Interface) Proxy.newProxyInstance(
            Interface.class.getClassLoader(),
            // 一个你希望代理对象实现的接口列表
            new Class[]{ Interface.class },
            // 一个调用处理器 DynamicProxyHandler 实现了 InvocationHandler接口
            // 所以它可以作为一个调用处理器传入
            new DynamicProxyHandler(real)
        );
        consumer(proxy);
    }
} /* Output: (95% match)
被代理类本身执行方法:
doSomething
代理类执行方法:
somethingElse bonobo
**********************************
被代理类本身执行方法:
[proxy]: class bigbigmeng.online.SE_14_typeinfo.SE_14_7.$Proxy0, method: public abstract void bigbigmeng.online.SE_14_typeinfo.SE_14_7.Interface.doSomething(), args: null
doSomething
代理类执行方法:
[proxy]: class bigbigmeng.online.SE_14_typeinfo.SE_14_7.$Proxy0, method: public abstract void bigbigmeng.online.SE_14_typeinfo.SE_14_7.Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@2503dbd3
param1: bonobo
somethingElse bonobo
*///:~

❓ 在DynamicProxyHandler类中 我们注意到invoke方法中的Object proxy参数 这个参数似乎并没有在invoke方法中用到 那么这个Object proxy参数的意义在于什么?

在具体实现中,我们可能不会直接使用proxy对象,但它对于实现动态代理非常重要。通过proxy对象,我们可以获得代理对象的类、接口、方法等信息,进而实现对方法的拦截、日志记录、权限控制等功能。

invoke()方法中传递进来了代理对象Object proxy 以防我们需要区分请求的来源 然而在许多情况下 我们并不关心这一点

method.invoke(proxied, args)返回了什么?

method.invoke(proxied, args) 返回的是目标对象(proxied)上调用指定方法(method)时的返回值。这个返回值的类型与被调用方法的返回类型相匹配。如果被调用方法没有返回值(即返回类型为void),则返回null。实际操作的效果是在目标对象上执行了指定的方法,并返回了该方法的结果。

🎯method.invoke将请求转发给被代理的对象,并传入必要的参数 我们可以通过使用invoke方法中的method参数来过滤某些方法的调用:

package bigbigmeng.online.SE_14_typeinfo.SE_14_7;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
@author Liu Xianmeng
@createTime 2023/9/18 9:55
@instruction
*/

class MethodSelector implements InvocationHandler {
    private Object proxied;
    public MethodSelector(Object proxied) {
        this.proxied = proxied;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 🎯过滤interesting方法的应用
        if(method.getName().equals("interesting")) {
            System.out.println("Proxy detected the interesting method");
        }
        return method.invoke(proxied, args);
    }
}

interface SomeMethods {
    void boring1();
    void boring2();
    void interesting(String arg);
    void boring3();
}

class Implementation implements SomeMethods {
    public void boring1() { System.out.println("boring1"); }
    public void boring2() { System.out.println("boring2"); }
    public void interesting(String arg) {
        System.out.println("interesting " + arg);
    }
    public void boring3() { System.out.println("boring3"); }
}

@SuppressWarnings({"all"})
public class SelectingMethods {
    public static void main(String[] args) {
        SomeMethods proxy= (SomeMethods) Proxy.newProxyInstance(
            SomeMethods.class.getClassLoader(),
            new Class[]{ SomeMethods.class },
            new MethodSelector(new Implementation())
        );
        proxy.boring1();
        proxy.boring2();
        proxy.interesting("bonobo");
        proxy.boring3();
    }
} /* Output:
boring1
boring2
Proxy detected the interesting method
interesting bonobo
boring3
*///:~

🎄CGLib动态代理

在Java编程思想书中 并没有介绍有关CGLib动态代理的知识点 这一节对CGLib动态代理作出补充 在Java中,CGLib是一个强大的动态代理库,可以通过生成目标类的子类来实现代理功能

🍭通过一个CGLib代码实战来初步了解CGLib动态代理

package bbm.proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
@author Liu Xianmeng
@createTime 2023/9/17 13:48
@instruction
*/

// 一个接口
interface Dinner{
    void eat(String foodName);
    void drink();
}
// 一个测试类
class Person implements Dinner{
    private String name;
    public Person(){};
    public Person(String name) {
        System.out.println("C Person M Person()..构造方法");
        this.name = name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
    @Override
    public void eat(String foodName) {
        System.out.println(name + "正在吃" + foodName);
    }
    @Override
    public void drink() {
        System.out.println(name + "正在喝茶");
    }
}

@SuppressWarnings({"all"})
public class CGLibProxy {
    public static void main(String[] args) {
        // 创建一个Person实例
        Person person = new Person("Jack");
        // 创建一个Enhancer实例
        Enhancer enhancer = new Enhancer();
        // 将Enhancer实例的superClass设置为Person.class
        enhancer.setSuperclass(person.getClass());
        /**
         * 创建一个MethodInterceptor实例
         *
         * 可以看到这个实例和JDK动态代理的InvocationHandler实例非常相似
         * 区别在于intercept方法多了一个MethodProxy参数
         *
         * 通过使用方法拦截器,我们可以在不修改原始类代码的情况下,对方法进行增强
         * 添加统一的处理逻辑、实现AOP(面向切面编程)等功能
         */
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            /**
             * 当我们使用CGLib创建代理对象时,会生成一个继承自目标类的子类
             * 这个子类会覆盖目标类中的所有非final方法
             * 并将这些方法的调用委托给intercept方法进行处理
             * 因此,当我们调用代理对象的方法时,实际上是通过intercept方法来进行处理
             * 
             * @param o 生成之后的代理对象 personProxy
             * @param method 父类中原本要执行的方法
             * @param objects 方法在调用时传入的实参数组
             * @param methodProxy 子类中重写父类的方法 personProxy >>> eat()
             * @return  在目标对象上执行了指定的方法 并返回了该方法的结果
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object rst = null;
                if(method.getName().equals("eat")){
                    // 如果是eat方法 则增强并运行
                    System.out.println("饭前洗手");
                    rst = methodProxy.invokeSuper(o, objects);
                    System.out.println("饭后刷碗");
                }else{
                    // 如果是其他方法 不增强运行
                    rst = methodProxy.invokeSuper(o, objects); // 子类对象方法在执行 默认会调用父类对应被重写的方法
                }
                return rst;
            }
        };
        /**
         * setCallback方法用于设置回调函数
         * 将方法拦截器(methodInterceptor)设置到增强器(enhancer)中
         * 当生成的代理类的方法被调用时 代理类会调用方法拦截器中的intercept方法来执行额外的逻辑
         * 如前置处理、后置处理、异常处理等
         */
        enhancer.setCallback(methodInterceptor);
        Person personProxy = (Person)enhancer.create();
        personProxy.eat("包子");
        System.out.println("**************************");
        personProxy.drink();
    }
    /**OutPut*******************************************************
        C Person M Person()..构造方法
        饭前洗手
        null正在吃包子
        饭后刷碗
        **************************
        null正在喝茶
    ***************************************************************/
}

也许大家会困惑😕 我明明在创建Person对象的时候Person person = new Person("Jack");指定了name属性为“Jack” 为什么打印的结果却是null❓

在使用CGLib生成代理对象时,CGLib创建的是目标类的子类,并且子类是通过继承父类来实现的。因此,子类的构造函数不会被执行,只有父类的构造函数会在生成代理对象时被调用。在上面的示例中,并没有调用父类的构造函数来完成name属性的初始化,而是直接通过字节码复制来生成了一个新的子类对象 所以打印的结果为null

要解决这个问题,可以通过修改main函数,手动设置name属性的值:

...
Person personProxy = (Person)enhancer.create();
personProxy.setName("Jack");
...

再次运行查看结果:Jack打印出来了

🍭JDK动态代理和CGlib动态代理比较

🎯JDK动态代理的使用场景:当需要为某个接口创建代理对象时,JDK动态代理会在运行时生成一个实现该接口的代理类,并在调用代理对象的方法时进行拦截,执行一些额外的逻辑。❗这种代理方式只能代理接口,不支持直接代理类。由于基于接口的动态代理是Java的标准库提供的功能,因此使用JDK动态代理不需要引入任何第三方库,非常方便

🎯CGlib动态代理则是通过继承目标类,创建目标类的子类作为代理对象:相比JDK动态代理,CGlib动态代理不需要目标类必须实现接口,可以代理类或者抽象类。CGlib动态代理底层使用ASM(一种Java字节码操作框架)来操作字节码,生成代理类。由于需要继承目标类,所以❗如果目标类被final修饰,就无法使用CGlib动态代理。并且,由于CGlib动态代理是对目标类的继承,可能会存在final方法无法被代理等限制。

🎯性能差异:JDK动态代理是基于接口的代理,生成的代理类相对较轻量级,性能也会更好;而CGlib动态代理是基于继承的代理,生成的代理类相对较重量级,性能略低于JDK动态代理。性能差异可能在具体使用场景中并不明显,因此选择哪种代理方式主要取决于具体需求。

总的来说,JDK动态代理适用于代理接口的情况,简单易用;而CGlib动态代理适用于代理类或抽象类的情况,提供更多的灵活性

🎄参考列表

Java编程思想 第四版

https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984

https://www.cnblogs.com/threeAgePie/p/15832586.html

https://developer.aliyun.com/article/909887