MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java代理模式中代理对象的生命周期管理

2022-05-082.2k 阅读

1. 代理模式基础回顾

在深入探讨Java代理模式中代理对象的生命周期管理之前,我们先来回顾一下代理模式的基本概念。代理模式是一种结构型设计模式,它允许通过代理对象来控制对另一个对象(目标对象)的访问。代理对象起到了中介的作用,在访问目标对象前后可以执行一些额外的操作,例如日志记录、权限验证等。

在Java中,代理模式主要通过两种方式实现:静态代理和动态代理。

1.1 静态代理

静态代理是指在编译时就已经确定代理类的代码。代理类和目标类实现相同的接口,代理类内部持有目标类的实例,通过调用目标类实例的方法来实现代理功能。

下面是一个简单的静态代理示例代码:

// 定义接口
interface Subject {
    void request();
}

// 目标类实现接口
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 代理类实现接口并持有目标类实例
class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("ProxySubject: Before request.");
        realSubject.request();
        System.out.println("ProxySubject: After request.");
    }
}

public class StaticProxyExample {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.request();
    }
}

在上述代码中,ProxySubjectRealSubject 的代理类。通过代理类,我们在调用 request 方法前后执行了额外的操作。

1.2 动态代理

动态代理是指在运行时动态生成代理类的字节码,并创建代理对象。Java提供了两种动态代理方式:JDK动态代理和CGLIB动态代理。

1.2.1 JDK动态代理

JDK动态代理通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口来实现。目标对象必须实现至少一个接口。

以下是JDK动态代理的示例代码:

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

// 定义接口
interface Subject {
    void request();
}

// 目标类实现接口
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 实现InvocationHandler接口
class DynamicProxyHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("DynamicProxyHandler: Before request.");
        Object result = method.invoke(target, args);
        System.out.println("DynamicProxyHandler: After request.");
        return result;
    }
}

public class JDKDynamicProxyExample {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        InvocationHandler handler = new DynamicProxyHandler(realSubject);
        Subject proxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                handler);
        proxy.request();
    }
}

在上述代码中,DynamicProxyHandler 实现了 InvocationHandler 接口,Proxy.newProxyInstance 方法动态生成代理类并创建代理对象。

1.2.2 CGLIB动态代理

CGLIB(Code Generation Library)是一个强大的,高性能的代码生成库。它通过继承目标类来生成代理类,因此目标类不需要实现接口。

以下是CGLIB动态代理的示例代码:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 目标类
class RealSubject {
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 实现MethodInterceptor接口
class CglibProxyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CglibProxyInterceptor: Before request.");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CglibProxyInterceptor: After request.");
        return result;
    }
}

public class CGLIBDynamicProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new CglibProxyInterceptor());
        RealSubject proxy = (RealSubject) enhancer.create();
        proxy.request();
    }
}

在上述代码中,CglibProxyInterceptor 实现了 MethodInterceptor 接口,Enhancer 类用于动态生成代理类并创建代理对象。

2. 代理对象的生命周期概述

代理对象的生命周期涉及到从代理对象的创建、使用到销毁的整个过程。理解代理对象的生命周期对于正确使用代理模式以及优化程序性能至关重要。

2.1 创建阶段

在代理模式中,代理对象的创建是第一步。对于静态代理,代理类在编译时已经确定,在运行时通过实例化代理类并传入目标对象实例来创建代理对象,如 ProxySubject proxySubject = new ProxySubject(realSubject);

对于动态代理,JDK动态代理通过 Proxy.newProxyInstance 方法在运行时动态生成代理类的字节码并创建代理对象。该方法需要传入目标对象的类加载器、目标对象实现的接口数组以及 InvocationHandler 实例。CGLIB动态代理则通过 Enhancer 类的 create 方法来创建代理对象,Enhancer 需要设置目标类的父类以及 MethodInterceptor 实例。

2.2 使用阶段

代理对象创建完成后,就进入使用阶段。在使用阶段,客户端通过代理对象调用目标对象的方法。代理对象在调用目标对象方法前后可以执行额外的逻辑,例如权限检查、日志记录等。

以静态代理为例,客户端调用 proxySubject.request() 方法,代理类 ProxySubject 会先执行前置逻辑,然后调用目标类 RealSubjectrequest 方法,最后执行后置逻辑。

对于动态代理,无论是JDK动态代理还是CGLIB动态代理,代理对象的方法调用都会被转发到对应的 InvocationHandlerMethodInterceptorinvoke 方法中,在这个方法中执行额外逻辑并调用目标对象的方法。

2.3 销毁阶段

当代理对象不再被使用时,就进入销毁阶段。在Java中,垃圾回收机制会自动回收不再被引用的对象。当代理对象不再有任何强引用指向它时,垃圾回收器会在合适的时机回收代理对象所占用的内存。

然而,在某些情况下,我们可能需要手动管理代理对象的生命周期,例如在使用资源密集型的代理对象时,需要及时释放资源,避免内存泄漏。

3. 静态代理对象的生命周期管理

3.1 创建过程细节

静态代理对象的创建相对简单直接。首先,我们需要定义代理类,代理类与目标类实现相同的接口。在代理类的构造函数中,传入目标对象的实例并保存。

例如,在之前的静态代理示例中:

class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
    //...
}

在创建代理对象时,我们直接实例化代理类并传入目标对象:

RealSubject realSubject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(realSubject);

3.2 使用过程中的注意事项

在使用静态代理对象时,由于代理类和目标类实现相同的接口,客户端代码可以像使用目标对象一样使用代理对象。但是,需要注意的是,代理类的方法实现中不仅包含目标对象方法的调用,还可能包含额外的逻辑。

例如,在 ProxySubjectrequest 方法中:

@Override
public void request() {
    System.out.println("ProxySubject: Before request.");
    realSubject.request();
    System.out.println("ProxySubject: After request.");
}

如果额外逻辑中涉及到资源的获取和释放,如数据库连接、文件句柄等,需要确保资源的正确管理,避免资源泄漏。

3.3 销毁过程与资源释放

在Java中,静态代理对象和其他普通对象一样,当不再有强引用指向它们时,垃圾回收器会自动回收它们所占用的内存。然而,如果代理对象在使用过程中获取了一些外部资源,如打开了文件、建立了网络连接等,我们需要手动释放这些资源。

例如,如果代理对象在 request 方法中打开了一个文件:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        FileOutputStream fos = null;
        try {
            System.out.println("ProxySubject: Before request.");
            File file = new File("log.txt");
            fos = new FileOutputStream(file);
            realSubject.request();
            fos.write("Request processed".getBytes());
            System.out.println("ProxySubject: After request.");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在上述代码中,我们在 finally 块中关闭了文件输出流,确保在代理对象使用完毕后资源被正确释放。

4. JDK动态代理对象的生命周期管理

4.1 创建过程的深入分析

JDK动态代理对象的创建依赖于 Proxy.newProxyInstance 方法,该方法的签名如下:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

其中,loader 是目标对象的类加载器,它用于加载动态生成的代理类的字节码。interfaces 是目标对象实现的接口数组,代理类会实现这些接口。hInvocationHandler 实例,它负责处理代理对象的方法调用。

当调用 Proxy.newProxyInstance 方法时,JDK会在运行时动态生成代理类的字节码。这个过程涉及到字节码生成和类加载机制。生成的代理类继承自 Proxy 类,并实现了 interfaces 数组中的所有接口。

例如,在之前的JDK动态代理示例中:

Subject proxy = (Subject) Proxy.newProxyInstance(
        realSubject.getClass().getClassLoader(),
        realSubject.getClass().getInterfaces(),
        handler);

这里,我们使用目标对象 realSubject 的类加载器,传入目标对象实现的 Subject 接口,以及 DynamicProxyHandler 实例来创建代理对象。

4.2 使用过程中的性能考虑

在使用JDK动态代理对象时,由于方法调用会被转发到 InvocationHandlerinvoke 方法中,存在一定的性能开销。尤其是在频繁调用代理对象方法的场景下,这种开销可能会比较明显。

为了优化性能,可以考虑以下几点:

  • 减少不必要的逻辑:在 invoke 方法中,尽量减少复杂的计算和I/O操作,将这些操作提前或推迟到其他合适的时机。
  • 缓存结果:如果 invoke 方法中的某些计算结果是不变的,可以考虑缓存这些结果,避免重复计算。

例如,如果 invoke 方法中需要进行权限验证,而权限验证的结果在一段时间内不会改变,可以将验证结果缓存起来:

class DynamicProxyHandler implements InvocationHandler {
    private Object target;
    private boolean hasPermission = false;

    public DynamicProxyHandler(Object target) {
        this.target = target;
        // 初始化权限验证结果
        hasPermission = checkPermission();
    }

    private boolean checkPermission() {
        // 实际的权限验证逻辑
        return true;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (hasPermission) {
            System.out.println("DynamicProxyHandler: Before request.");
            Object result = method.invoke(target, args);
            System.out.println("DynamicProxyHandler: After request.");
            return result;
        } else {
            throw new SecurityException("No permission");
        }
    }
}

4.3 销毁过程与内存管理

和静态代理对象一样,JDK动态代理对象在不再被引用时会被垃圾回收器回收。然而,由于动态代理对象的创建涉及到字节码生成和类加载,可能会对内存管理产生一些影响。

动态生成的代理类字节码会占用一定的内存空间。如果在短时间内大量创建JDK动态代理对象,可能会导致内存占用过高。为了避免这种情况,可以考虑使用对象池来复用代理对象,减少动态代理类的生成次数。

例如,可以实现一个简单的代理对象池:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

class ProxyPool {
    private static Map<Class<?>, Object> proxyPool = new HashMap<>();

    public static Object getProxy(Object target, InvocationHandler handler) {
        if (proxyPool.containsKey(target.getClass())) {
            return proxyPool.get(target.getClass());
        } else {
            Object proxy = Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    handler);
            proxyPool.put(target.getClass(), proxy);
            return proxy;
        }
    }
}

在使用时,可以通过 ProxyPool.getProxy 方法获取代理对象,避免重复创建:

RealSubject realSubject = new RealSubject();
InvocationHandler handler = new DynamicProxyHandler(realSubject);
Subject proxy = (Subject) ProxyPool.getProxy(realSubject, handler);

5. CGLIB动态代理对象的生命周期管理

5.1 创建过程的原理剖析

CGLIB动态代理通过继承目标类来生成代理类。在创建代理对象时,Enhancer 类起到了关键作用。Enhancer 类会设置目标类为父类,并设置 MethodInterceptor 实例用于处理方法调用。

例如,在之前的CGLIB动态代理示例中:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new CglibProxyInterceptor());
RealSubject proxy = (RealSubject) enhancer.create();

Enhancer 在创建代理对象时,会动态生成代理类的字节码。这个代理类继承自目标类,并覆盖目标类的方法。当代理对象的方法被调用时,会调用 MethodInterceptorintercept 方法,在这个方法中可以执行额外逻辑并调用目标类的方法。

5.2 使用过程中的特殊情况处理

在使用CGLIB动态代理对象时,需要注意一些特殊情况。由于代理类是继承自目标类,因此如果目标类中有 final 方法,这些方法无法被代理。

例如,如果 RealSubject 类中有一个 final 方法:

class RealSubject {
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }

    public final void finalMethod() {
        System.out.println("This is a final method.");
    }
}

在代理对象中,finalMethod 方法不会被代理,调用该方法时不会经过 MethodInterceptorintercept 方法。

另外,如果目标类的构造函数有参数,在创建代理对象时需要传入相应的参数。例如,如果 RealSubject 类的构造函数需要一个参数:

class RealSubject {
    private int value;

    public RealSubject(int value) {
        this.value = value;
    }

    public void request() {
        System.out.println("RealSubject: Handling request with value " + value);
    }
}

在创建代理对象时,需要这样做:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new CglibProxyInterceptor());
Object[] args = new Object[]{10};
Class<?>[] argTypes = new Class[]{int.class};
RealSubject proxy = (RealSubject) enhancer.create(argTypes, args);

5.3 销毁过程与资源回收

和其他代理对象一样,CGLIB动态代理对象在不再被引用时会被垃圾回收器回收。然而,CGLIB在动态生成代理类字节码时也会占用一定的内存。

为了优化内存使用,可以在不需要代理对象时及时释放相关资源。例如,如果代理对象在 intercept 方法中获取了一些资源,如数据库连接,需要在方法结束时释放这些资源。

class CglibProxyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
            System.out.println("CglibProxyInterceptor: Before request.");
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("CglibProxyInterceptor: After request.");
            return result;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

通过在 finally 块中释放数据库连接,确保了资源的正确回收,避免了内存泄漏。

6. 代理对象生命周期管理的最佳实践

6.1 资源管理原则

在代理对象的生命周期中,资源管理是关键。无论是静态代理、JDK动态代理还是CGLIB动态代理,只要代理对象在使用过程中获取了外部资源,如文件句柄、数据库连接、网络套接字等,都需要确保在不再使用这些资源时及时释放。

遵循“谁获取谁释放”的原则,在代理对象获取资源的方法中,应该有对应的逻辑来释放资源。通常可以使用 try - finally 块来确保资源的释放,即使在方法执行过程中发生异常也能保证资源被正确释放。

6.2 内存优化策略

为了优化代理对象的内存使用,除了及时释放外部资源外,还可以考虑以下策略:

  • 对象复用:对于频繁使用的代理对象,可以使用对象池技术来复用代理对象,减少代理对象的创建次数。如前面提到的JDK动态代理对象池的实现,通过缓存代理对象,避免了重复生成代理类字节码和创建代理对象的开销。
  • 减少不必要的代理对象创建:在设计系统时,要评估是否真的需要为每个目标对象都创建代理对象。如果某些目标对象的代理功能在整个系统中只需要执行一次或很少执行,可以考虑在需要时创建临时代理对象,而不是预先创建并一直持有。

6.3 结合依赖注入与生命周期管理

在企业级应用开发中,依赖注入(Dependency Injection,DI)是一种常用的设计模式。结合依赖注入可以更好地管理代理对象的生命周期。

例如,使用Spring框架时,可以通过配置将代理对象注入到需要使用它的组件中。Spring容器负责管理代理对象的创建、初始化和销毁。当一个组件依赖于代理对象时,Spring会在组件初始化时将代理对象注入进来,并且在组件销毁时,Spring会确保代理对象所占用的资源被正确释放。

以下是一个简单的Spring配置示例,假设我们有一个 Service 类依赖于一个代理对象:

<bean id="realSubject" class="com.example.RealSubject"/>

<bean id="proxySubject" class="com.example.ProxySubject">
    <constructor-arg ref="realSubject"/>
</bean>

<bean id="service" class="com.example.Service">
    <property name="proxySubject" ref="proxySubject"/>
</bean>

在上述配置中,Spring容器会创建 RealSubject 实例和 ProxySubject 实例,并将 ProxySubject 实例注入到 Service 实例中。当 Service 实例销毁时,Spring会负责管理相关资源的释放。

通过结合依赖注入与生命周期管理,可以使代理对象的生命周期管理更加规范化和自动化,提高系统的可维护性和稳定性。