深入理解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切面编程等。

相关推荐
李慕婉学姐5 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆6 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin7 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20057 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉7 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国7 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882487 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈8 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_998 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹8 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理