您现在的位置是:首页 >技术杂谈 >【设计模式】代理模式网站首页技术杂谈
【设计模式】代理模式
Java 的代理模式是一种设计模式,它可以让一个对象(代理对象)代替另一个对象(目标对象)去执行一些操作,并且可以在执行前后添加一些额外的功能。代理模式可以实现对目标对象的功能扩展和保护。
Java 的代理模式有两种实现方式:静态代理和动态代理。静态代理是在编译时就生成了代理类的字节码文件,而动态代理是在运行时动态生成代理类并加载到 JVM 中。
静态代理
静态代理的实现步骤如下:
- 定义一个接口及其实现类;
 - 创建一个代理类同样实现这个接口;
 - 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法;
 - 通过代理类来访问目标对象的方法,并且可以在方法执行前后添加自定义的操作。
 
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活。需要对每个目标类都单独写一个代理类,而且接口一旦新增加方法,目标对象和代理对象都要进行修改。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。从 JVM 层面来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
动态代理
动态代理的实现方式有多种,比如 JDK 动态代理、CGLIB 动态代理等。JDK 动态代理是利用 Java 内部的反射机制生成一个实现了目标接口的匿名类,然后调用目标方法;CGLIB 动态代理是利用 ASM 开源包,对目标类的字节码文件进行修改,生成子类来处理目标方法。CGLIB 动态代理这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
JDK 动态代理
JDK动态代理是面向接口的,必须要实现了接口的业务类才生成代理对象。
在运行期动态创建一个interface实例的方法如下:
-  
定义一个
InvocationHandler实例,它负责实现接口的方法调用; -  
通过
Proxy.newProxyInstance()创建实例,它需要3个参数:- 使用的 
ClassLoader,通常就是接口实现类的ClassLoader; - 需要实现的接口数组,至少需要传入一个接口进去;
 - 用来处理接口方法调用的 
InvocationHandler实例。 
 - 使用的 
 -  
将返回的
Object强制转型为接口。 
示例实现代码:
public interface RequestInterface {
    void request();
}
public class RequestInstance implements RequestInterface {
    @Override
    public void request() {
        System.out.println("print request.");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JdkProxyHandler implements InvocationHandler {
    private Object instance;
    public JdkProxyHandler(Object instance) {
        this.instance = instance;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before instance execution");
        System.out.println("Method:" + method);
        method.invoke(instance, args);
        System.out.println("after instance execution");
        return null;
    }
}
public class Main {
    public static void main(String[] args) {
        RequestInstance instance = new RequestInstance();
        JdkProxyHandler proxyHandler = new JdkProxyHandler(instance);
        ClassLoader classLoader = instance.getClass().getClassLoader();
        RequestInterface requestInst = (RequestInterface) Proxy.newProxyInstance(
                classLoader, new Class[]{RequestInterface.class}, proxyHandler);
        requestInst.request();
    }
}
 
运行上述代码之后,控制台打印出:
before instance execution
Method:public abstract void jdk.instance.RequestInterface.request()
print request.
after instance execution
 
可以看出,通过代理的方式,已经成功增强了 RequestInstance 的 request() 方法。
如果想通过 JDK 动态代理实现一个通用的代理,那么可以在代理处理器中实现一个创建代理的静态方法,通过传入不同的实现类来实现对同一接口、不同实现的通用代理。假设实现一个统计接口耗时的代理:
public class JdkProxyRuntimeHandler implements InvocationHandler {
    private Object target;
    public JdkProxyRuntimeHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.target, args);//@1
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + " 方法耗时(纳秒): " + (endTime - starTime));
        return result;
    }
    /**
     * 用来创建targetInterface接口的代理对象
     *
     * @param target          被代理的对象
     * @param targetInterface 被代理的接口
     * @param <T> 实现类类名
     * @return 代理对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface()) {
            throw new IllegalStateException("targetInterface 不属于接口类型!");
        } else if (!targetInterface.isAssignableFrom(target.getClass())) {
            throw new IllegalStateException("target 不是 targetInterface 接口的实现类!");
        }
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkProxyRuntimeHandler(target));
    }
}
public class Main {
    public static void main(String[] args) {
        RequestInterface proxyInstance = JdkProxyRuntimeHandler.createProxy(new RequestInstance(), RequestInterface.class);
        proxyInstance.request();
    }
}
 
上面当我们创建一个新的接口的时候,不需要再去新建一个代理类了,只需要使用 JdkProxyRuntimeHandler.createProxy 创建一个新的代理对象就可以了,方便了很多。
使用 JDK 动态代理需要注意几点:
- JDK 中的 Proxy 只能为接口生成代理类,如果想给某个类创建代理类,那么需要用到 CGLIB 了
 - 当调用通过 Proxy 创建代理对象的任意方法时候,会被 
InvocationHandler接口中的invoke方法进行处理 
CGLIB 动态代理
JDK 的动态代理只能为接口创建代理,在应用上具有局限性。在实际的场景中,我们的类不一定有接口,如果我们想为普通的类也实现代理功能,我们就需要用到CGLIB 来实现了。
CGLIB 是一个强大、高性能的字节码生成库,底层使用了 ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。它用于在运行时扩展 Java 类和实现接口,本质上它是通过动态生成一个子类去覆盖所要代理的类。Enhancer 可能是 CGLIB 中最常用的一个类,和 JDK 中的 Proxy 不同的是,Enhancer既能够代理一般的类,也能够代理接口。Enhancer 创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的 toString 和 hashCode 方法)。Enhancer 不能够拦截final方法,例如 Object.getClass() 方法,这是由 Java final 方法语义决定的。基于同样的道理,Enhancer 也不能对 final 类进行代理操作。
由于 Spring 已将第三方 CGLIB Jar 包中所有的类集成到 Spring 自己的 Jar 包中,因此可直接使用接口 MethodInterceptor 来实现代理。
CGLIB动态代理的基本实现代码:
public class RawRequestInstance {
    public String request() {
        System.out.println("print request.");
        return "print raw request.";
    }
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class CglibProxy {
    private Object target;
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 声明生成的代理类是某一个父类的子类
        enhancer.setSuperclass(this.target.getClass());
        // 指定回调方法,即代理中需要加强的内容
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            System.out.println("before instance execution");
            System.out.println("Method:" + method);
            Object result = methodProxy.invokeSuper(o, objects);
            System.out.println("after instance execution");
            return result;
        });
        return enhancer.create();
    }
}
public class Main {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        RawRequestInstance proxyInst = (RawRequestInstance) proxy.getInstance(new RawRequestInstance());
        System.out.println(proxyInst.request());
    }
}
 
运行上述代码之后,控制台打印出:
before instance execution
Method:public void instance.RawRequestInstance.request()
print request.
after instance execution
print raw request.
 
实际上,CGLIB 代理类在方法被调用时,里面的所有的方法都是会被拦截处理的,下面我们修改一下实现类 RequestInstance 的成员方法,然后再执行一次以上的代码:
public class RawRequestInstance {
    public String request() {
        System.out.println("print request.");
        return "print raw request." + request2();
    }
    public String request2() {
        System.out.println("print request2.");
        return "print raw request 2.";
    }
}
 
运行上述代码之后,控制台打印出:
before instance execution
Method:public java.lang.String instance.RawRequestInstance.request()
print request.
before instance execution
Method:public java.lang.String instance.RawRequestInstance.request2()
print request2.
after instance execution
after instance execution
print raw request.print raw request 2.
 
可见,即便 request2() 是在 request1() 中被调用,但它仍然被代理拦截进行处理。实际上这就是 Spring 中的 @Configuration 注解的实现方式,使用 CGLIB 代理拦截 @Bean 注解的方法,从而确保注入的被依赖 Bean 都是同一个。
同样地,我们实现一个通用的、用于统计接口耗时的代理:
public class CglibProxyRuntime implements MethodInterceptor {
    private Object target;
    public CglibProxyRuntime(Object target) {
        this.target = target;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(target, objects);
        long endTime = System.nanoTime();
        System.out.println(method + " 方法耗时(纳秒): " + (endTime - starTime));
        return result;
    }
    /**
     * 创建任意类的代理对象
     *
     * @param target 被代理的对象
     * @param <T> 实现类类名
     * @return 代理对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        CglibProxyRuntime costTimeProxy = new CglibProxyRuntime(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(costTimeProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}
public class Main {
    public static void main(String[] args) {
        RawRequestInstance proxy = CglibProxyRuntime.createProxy(new RawRequestInstance());
        System.out.println(proxy.request());
    }
}
 
输出结果为:
print request.
print request2.
public java.lang.String instance.RawRequestInstance.request() 方法耗时(纳秒): 403800
print raw request.print raw request 2.
 
从上面的示例代码可知,CGLIB 代理拦截方法后的处理逻辑,主要是通过自定义一个 MethodInterceptor 接口并重写其方法,将该接口赋值给 enhancer 作为 callback 回调方法。这里的 MethodInterceptor 其实也是继承了 Callback 接口,因此我们还可以定义其他继承了 Callback 的接口,从而增强代理类的功能。
FixedValue 返回固定值
enhancer.setCallback(new FixedValue() {
    @Override
    public Object loadObject() throws Exception {
        return "**** Fixed Value ****";
    }
});
 
在上面的代码中直接修改 setCallback() 方法的赋值,最终输出结果为:
**** Fixed Value ****
 
NoOp.INSTANCE 拦截无操作
直接上代码:
enhancer.setCallback(NoOp.INSTANCE);
 
最终输出结果为:
print request.
print request2.
print raw request.print raw request 2.
 
CallbackFilter 不同方法实现不同拦截策略
定义一个新的实现类 MultiMethodInstance:
public class MultiMethodInstance {
    public String runtime() {
        System.out.println("print runtime.");
        return "print runtime.";
    }
    public String fixedValue() {
        System.out.println("fixed value");
        return "fixed value";
    }
    public String doNothing() {
        System.out.println("----------- do nothing -----------");
        return "----------- do nothing -----------";
    }
}
 
然后定义一个新的代理拦截类:
import org.springframework.cglib.proxy.*;
import java.lang.reflect.Method;
public class CglibProxyFilter {
    private Object target;
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        //创建多个Callback
        Callback[] callbacks = {
                // 拦截所有方法名含有 run 的方法
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        long starTime = System.nanoTime();
                        Object result = methodProxy.invokeSuper(o, objects);
                        long endTime = System.nanoTime();
                        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
                        return result;
                    }
                },
                // 拦截所有方法名含有 fixed 的方法,返回固定值的
                new FixedValue() {
                    @Override
                    public Object loadObject() throws Exception {
                        return "**** Fixed Value ****";
                    }
                },
                // 拦截后无操作
                NoOp.INSTANCE
        };
        enhancer.setCallbacks(callbacks);
        // 设置过滤器 CallbackFilter,判断调用方法时使用哪个 Callback 来处理,返回的是 callbacks 数组的下标
        enhancer.setCallbackFilter(new CallbackFilter() {
            @Override
            public int accept(Method method) {
                String methodName = method.getName();
                return methodName.contains("run") ? 0
                        : (methodName.contains("fixed") ? 1 : 2);
            }
        });
        return enhancer.create();
    }
}
 
通过定义多个 Callback 接口的实现,以及提供判断每个方法调用时所选用的回调策略(拦截处理),来完成对同一个实现类中不同方法进行不同的代理拦截策略。具体执行哪个 Callback,会通过 CallbackFilter 中的 accept 方法来判断,这个方法返回 callbacks 数组的索引。上述代码的输出结果如下:
print runtime.
public java.lang.String instance.MultiMethodInstance.runtime(),耗时(纳秒):29824600
print runtime.
**** Fixed Value ****
----------- do nothing -----------
----------- do nothing -----------
 
多策略拦截代理还能进一步优化,使用 CallbackHelper 替代 CallbackFilter 能使代码结构更加清晰,扩展性更强。具体实现代码如下:
import org.springframework.cglib.proxy.*;
import java.lang.reflect.Method;
public class CglibProxyCallbackHelper {
    private Object target;
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        // 定义所有可用的 Callback 接口方法
        Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
            long starTime = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            long endTime = System.nanoTime();
            System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
            return result;
        };
        Callback fixedValueCallback = (FixedValue) () -> "**** Fixed Value ****";
        Callback noOpCallback = NoOp.INSTANCE;
        // 定义拦截策略的判断逻辑
        CallbackHelper callbackHelper = new CallbackHelper(target.getClass(), null) {
            @Override
            protected Object getCallback(Method method) {
                String methodName = method.getName();
                return methodName.contains("run") ? costTimeCallback
                        : (methodName.contains("fixed") ? fixedValueCallback : noOpCallback);
            }
        };
        enhancer.setCallbacks(callbackHelper.getCallbacks());
        enhancer.setCallbackFilter(callbackHelper);
        return enhancer.create();
    }
}
 
优化后的输出结果不变,这里就不再展示了。
小结
Java 标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例。动态代理是通过 Proxy 创建代理对象,然后将接口方法“代理”给 InvocationHandler 完成的。动态代理实际上是 JVM 在运行期动态创建 class 字节码并加载的过程
JDK 动态代理和 CGLIB 动态代理的区别在于:
- JDK 动态代理是面向接口的,必须要实现了接口的业务类才生成代理对象;而 CGLIB 动态代理则是通过生成业务类的子类作为代理类,无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。
 - Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
 - Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB 使用 ASM 框架直接对字节码进行操作,在类的执行过程中比较高效。
 
在 SpringBoot 中,有很多地方应用了代理模式,比如 AOP、事务管理、缓存等。例如,当我们使用 @Transactional 注解来开启事务时,SpringBoot 会根据目标类是否实现了接口来选择使用 JDK 动态代理还是 CGLIB 动态代理来创建一个带有事务功能的代理对象。
            




U8W/U8W-Mini使用与常见问题解决
QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结