深入理解Java代理模式:从静态到动态的实现与应用

1、引言

在Java编程中,代理模式是一种常见的设计模式,用于在不修改原始代码的情况下,为对象添加额外的功能。代理模式有两种主要类型:静态代理和动态代理。本文将全面探讨这两种代理模式,包括它们的基本概念、实现方式、应用场景及其优缺点。

2、静态代理

静态代理是在编译时确定代理类的。代理类和目标类都需要实现相同的接口,代理类持有对目标对象的引用,并在调用目标对象的方法前后插入额外的逻辑。

先给大家看一张基础设计代理的原理图

2.1、静态代理的示例

定义一个接口

java 复制代码
public interface ISubject{
    void eat();
}

实际类

java 复制代码
public class RealSubject implements ISubject{
    @Override
    public void eat() {
        System.out.println("RealSubject do eat...");
    }
}

代理对象

java 复制代码
public class ProxyObject implements ISubject{

    private ISubject iSubject;

    public ProxyObject(ISubject iSubject) {
        this.iSubject = iSubject;
    }

    private void preSomething() {
        System.out.println("preEat......");
    }

    private void afterSomething() {
        System.out.println("afterEat......");
    }

    @Override
    public void eat() {
        preSomething();
        iSubject.eat();
        afterSomething();
    }
}

工厂类

java 复制代码
public class ObjectFactory {

    public static ISubject getInstance(){
        return new ProxyObject(new RealSubject());
    }
    
}

调用

java 复制代码
public class TestProxy {

    public static void main(String[] args) {
        testProxy();
    }

    public static void testProxy(){
        ISubject iSubject = ObjectFactory.getInstance();
        iSubject.eat();
    }
}

上面这个是最简单的例子,我们一般在使用的代理模式的时候,我们只关注代理是谁,真实对象是谁即可,如何根据这两个参数(也就是类的全限定名称)进行代理呢?我们可以使用反射,下来我们重构一下工厂类和main方法,示例代码如下:

java 复制代码
public class ObjectFactory { 

    public ObjectFactory() {}

    /**
     * 通过类名字符串创建并返回指定类型的实例
     * 
     * @param className 要创建的类的全限定名字符串
     * @return 创建的实例
     * @throws RuntimeException 如果指定的类不存在,或者实例化过程中出现错误,则抛出运行时异常
     */
    public static <T> T getInstance(String className){
        T t = null;
        try {
            // 通过反射机制创建指定类的实例
            t =  (T) Class.forName(className).newInstance();
        } catch (InstantiationException e) {
            // 如果类无法被实例化,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            // 如果类的构造方法不可访问,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            // 如果类不存在,则抛出运行时异常
            throw new RuntimeException(e);
        }
        return t;
    }

    /**
     * 根据代理名和真实对象名创建并返回一个代理对象实例
     * 
     * @param proxyName 代理类的全限定名,用于动态加载和实例化代理对象
     * @param realName 真实对象类的全限定名,用于获取真实对象的实例
     * @param <T> 泛型标记,表示返回的代理对象类型
     * @return T 返回一个实现了真实对象接口的代理对象实例
     * @throws RuntimeException 如果在实例化代理对象过程中发生错误,则抛出运行时异常
     * 
     * 此方法主要用于在运行时动态创建代理对象,通过反射机制加载并实例化指定的代理类
     * 代理类需要通过构造方法接收真实对象作为参数,以便代理对象能够调用真实对象的方法
     */
    public static <T> T getInstance(String proxyName, String realName){
        // 初始化代理对象为null
        T t = null;
        // 获取真实对象的实例
        T obj = getInstance(realName);
        // 尝试通过反射机制实例化代理对象
        try {
            // 使用代理类名和接口类型来创建代理对象实例
            t = (T) Class.forName(proxyName).getConstructor(obj.getClass().getInterfaces()[0]).newInstance(obj);
        } catch (InstantiationException e) {
            // 如果代理类无法实例化,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            // 如果构造方法非法访问,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            // 如果调用构造方法时目标方法抛出异常,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            // 如果未找到符合要求的构造方法,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            // 如果未找到代理类,则抛出运行时异常
            throw new RuntimeException(e);
        }
        // 返回创建的代理对象实例
        return t;
    }
}

调用

java 复制代码
public class TestProxy {

    public static void main(String[] args) {
        testReflect();
    }

    public static void testReflect(){
        ISubject iSubject = ObjectFactory.getInstance("com.hy.proxy.ProxyObject", "com.hy.proxy.RealSubject");
        iSubject.eat();
    }
    
}

2.2 静态代理的优缺点

优点:

  1. 代码清晰,易于理解。
  2. 适合于需要明确代理逻辑的场景。

缺点:

  1. 每个代理都需要一个对应的目标类,代码冗余。
  2. 不够灵活,代理类和目标类的关系在编译时确定。

3、动态代理

动态代理是在运行时创建代理对象的,能够在运行时决定具体的代理逻辑。Java的动态代理分为两种主要实现方式:

  • JDK动态代理
  • CGLIB动态代理

对于动态代理,代理对象可以代理一个类中的多个实现方法,即一个真实类实现多个接口,一个代理类就可以完全代理

3.1、JDK动态代理

JDK动态代理基于反射机制,可以在运行时创建代理对象。要使用JDK动态代理,目标类必须实现一个或多个接口。通过 Proxy 类和 InvocationHandler 接口,可以创建代理对象,并在调用方法时插入额外的逻辑。

3.1.1、JDK动态代理示例代码

接口

java 复制代码
public interface ITest {
    void play();
}

public interface ISubject{
    void eat();
}

真实类

java 复制代码
public class DynamicRealObject implements ISubject, ITest{
    @Override
    public void eat() {
        System.out.println("DynamicRealObject eat...");
    }

    @Override
    public void play() {
        System.out.println("DynamicRealObject play...");
    }
    
}

动态代理类

java 复制代码
public class DynamicProxy implements InvocationHandler {

    private Object target;

    /**
     * 绑定目标对象并生成代理对象
     * 本方法主要用于通过动态代理绑定一个目标对象,并返回该目标对象的代理实例
     * 
     * @param target 目标对象,即需要通过代理的对象
     * @return 返回目标对象的代理实例,该实例可以用于方法拦截和调用
     */
    public Object bind(Object target) {
        // 绑定目标对象到当前代理实例
        this.target = target;
        // 利用Java动态代理机制创建并返回目标对象的代理实例
        // 该代理实例将使用目标对象的类加载器,实现目标对象所实现的所有接口,并将当前代理实例作为调用处理程序
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    private void preSomething() {
        System.out.println("preEat......");
    }

    private void afterSomething() {
        System.out.println("afterEat......");
    }

    /**
     * 该方法通过Java动态代理机制,拦截调用目标方法的请求
     * 在调用目标方法之前和之后执行特定操作,实现环绕通知的功能
     * 这种方式可以用于实现事务管理、日志记录、权限控制等横切关注点
     *
     * @param proxy 代理对象,通常不用直接操作
     * @param method 被调用的目标方法的信息
     * @param args 调用目标方法时传递的参数
     * @return 目标方法的返回值
     * @throws Throwable 目标方法抛出的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用目标方法之前执行的操作,如开启事务、日志记录等
        preSomething();
        // 调用目标方法,并传递参数
        Object obj = method.invoke(target, args);
        // 在调用目标方法之后执行的操作,如关闭事务、日志记录等
        afterSomething();
        // 返回目标方法的执行结果
        return obj;
    }
    
}

调用

java 复制代码
public class TestProxy {

    public static void main(String[] args) {
        testDynamicProxy();
    }

    public static void testDynamicProxy(){
    	// 结合静态代理的反射,可以这样写
    	// ISubject iSubject = (ISubject) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
    	// ITest iTest = (ITest) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
        ISubject iSubject = (ISubject) new DynamicProxy().bind(new DynamicRealObject());
        iSubject.eat();
        ITest iTest = (ITest) new DynamicProxy().bind(new DynamicRealObject());
        iTest.play();
    }
    
}

3.2、CGLIB动态代理类

CGLIB(Code Generation Library)是一个功能强大的代码生成库,允许在运行时动态创建代理类。与JDK动态代理不同,CGLIB不要求目标类实现接口,而是通过继承目标类来创建代理。

3.2.1 CGLIB动态代理示例代码

目标类

java 复制代码
public class CglibProxy {

    public void doSomething(){
        System.out.println("Cglib do something");
    }
    
}

代理类

java 复制代码
public class CglibInterceptor implements MethodInterceptor {

    private final Object target;

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

    private void preDoSomething() {
        System.out.println("preDoSomething......");
    }

    private void afterDoSomething() {
        System.out.println("afterDoSomething......");
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        preDoSomething();
        Object obj = method.invoke(target, objects);
        afterDoSomething();
        return obj;
    }
}

调用

java 复制代码
public class TestProxy {

    public static void main(String[] args) {
        testCglib();
    }

    /**
     * 使用CGLIB进行动态代理的测试方法
     * 该方法通过CGLIB框架创建一个CglibProxy的子类实例,以实现方法的拦截和调用
     */
    public static void testCglib(){
        // 创建CGLIB增强器实例
        Enhancer enhancer = new Enhancer();
        // 设置代理类的父类为CglibProxy类,这样CGLIB会生成该类的子类
        enhancer.setSuperclass(CglibProxy.class);
        // 设置拦截器,传入CglibProxy实例作为拦截器的关键信息
        enhancer.setCallback(new CglibInterceptor(new CglibProxy()));
        // 通过增强器创建代理类实例
        CglibProxy cglibProxy = (CglibProxy) enhancer.create();
        // 通过代理实例调用目标方法,此调用将被CglibInterceptor拦截并处理
        cglibProxy.doSomething();
    }
}

3.3、动态代理的优缺点

优点:

  1. 灵活性高,可以在运行时创建代理对象。
  2. 不需要修改目标类代码。

缺点:

  1. JDK动态代理只能代理实现了接口的类,不能代理具体类。
  2. CGLIB动态代理在生成代理类时会继承目标类,这可能导致一些问题,如不能代理final类或方法。

3.4 CGLIB和JDK动态代理区别

具体区别如下:

  1. 实现原理: JDK动态代理是基于Java反射机制实现的,它要求目标类必须实现一个或多个接口,代理对象在运行时动态创建,通过实现目标类接口的方式来代理目标类。 CGLIB代理则是基于ASM字节码框架实现的,它可以代理没有实现接口的目标类。CGLIB在运行时通过动态生成目标类的子类来实现代理。

  2. 性能表现: JDK动态代理因为需要实现目标类接口,所以它的性能相对较低,但是它的应用场景更为广泛,适用于大多数情况下的代理需求。 CGLIB代理则因为不需要实现目标类接口,所以它的性能相对较高,但是它不能代理final类和final方法,以及一些无法生成子类的类。

  3. 应用场景: JDK动态代理适用于代理接口的场景,例如Spring中的事务处理、日志记录等。 CGLIB代理适用于代理类的场景,例如Spring中的AOP切面编程等。

相关推荐
专注API从业者43 分钟前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
摇滚侠1 小时前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY1 小时前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克32 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠3 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌3 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局3 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源3 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
呱牛do it3 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19434 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解