在Java开发中,设计模式是提升代码复用性与结构清晰度的利器。代理模式,作为结构型模式的代表,其核心思想可以理解为:为某个对象提供一种“替身”或“中介”,以控制对这个对象的访问。这种模式广泛应用于诸如Spring AOP、MyBatis拦截器等主流框架中,是实现功能增强、延迟加载、访问控制的底层基石。
那么,代理模式在Java中具体如何实现呢?主要有三种经典方式:静态代理、JDK动态代理和CGLib动态代理。它们各有千秋,适用于不同场景。
一、静态代理
静态代理是最直观的实现方式,其核心特征是:代理类在编译前就已经写好,并且与目标类实现了同一个接口。这种方式就像租房时对接你的专属中介,一对一服务,职责分明。
代码示例(以租房场景为例)
首先,定义一个代表租房行为的统一接口。
public interface HireHouse {
void hire(); // 租房的核心方法
}
接着,是专注核心业务的房东(目标类)。
public class HireHouseReal implements HireHouse {
@Override
public void hire() {
System.out.println("房东完成房屋出租");
}
}
最后,是负责处理额外事务的中介(代理类)。它持有房东的引用,并在核心方法调用前后添加增强逻辑。
public class HireHouseProxy implements HireHouse {
private HireHouseReal landlord; // 持有目标对象(房东)的引用
@Override
public void hire() {
// 懒加载:需要时才创建目标对象
if (landlord == null) {
landlord = new HireHouseReal();
}
System.out.println("中介:收取租金、中介费及押金"); // 前置增强
landlord.hire(); // 执行核心业务
System.out.println("中介:登记租房信息,留存押金凭证"); // 后置增强
}
}
静态代理的优劣势分析
优点:
- 执行效率高:代理类在编译期生成,没有运行时开销。
- 代码直观:结构清晰,易于理解和调试。
- 职责解耦:将核心业务与增强逻辑(如日志、事务)分离,符合单一职责原则。
- 类型安全:编译时进行严格的接口校验。
缺点:
- 代码冗余:一个接口就需要对应一个代理类,系统接口多时会产生大量“样板代码”。
- 维护成本高:一旦接口方法发生变动,所有相关的目标类和代理类都必须同步修改。
- 灵活性差:代理类只能服务于特定接口,无法通用。
二、JDK 动态代理
为了解决静态代理的代码冗余问题,Java 标准库自带了动态代理机制。它无需手动编写代理类,而是在程序运行时,动态地生成代理类的字节码并实例化。
JDK 动态代理依赖于Java反射机制,其核心组件有两个:
java.lang.reflect.Proxy 类:用于创建代理实例。
java.lang.reflect.InvocationHandler 接口:所有增强逻辑都写在其 invoke 方法中。
代码示例
假设已有业务接口 InterDemo 及其实现类 InterDemoImpl,我们重点关注代理处理器和客户端的调用。
// 代理处理器(InvocationHandler实现类,包含通用增强逻辑)
public class DynamicProxyImpl implements InvocationHandler {
private Object target; // 可以适配任何实现了接口的目标对象
public DynamicProxyImpl(Object target) {
this.target = target; // 绑定目标对象
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强:日志记录、权限校验"); // 通用前置逻辑
// 通过反射调用目标对象的原始方法
Object result = method.invoke(target, args);
System.out.println("后置增强:事务提交、资源释放"); // 通用后置逻辑
return result;
}
}
// 客户端调用
public class Client {
public static void main(String[] args) {
// 1. 创建目标对象
InterDemo target = new InterDemoImpl();
// 2. 创建代理处理器,并传入目标对象
InvocationHandler handler = new DynamicProxyImpl(target);
// 3. 通过Proxy类动态生成代理对象
InterDemo proxy = (InterDemo) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 使用目标类的类加载器
target.getClass().getInterfaces(), // 代理类需要实现的接口
handler // 调用处理器
);
// 4. 通过代理对象调用方法
proxy.dosomething();
}
}
JDK 动态代理的优劣势
优点:
- 减少冗余代码:一个
InvocationHandler 可以服务多个不同接口的目标类。
- 符合开闭原则:新增功能不修改原有目标类代码。
- 灵活性强:运行时可以动态切换目标对象和增强逻辑。
- 无需额外依赖:JDK 原生支持。
局限性:
- 必须基于接口:目标类必须至少实现一个接口,否则无法代理。
- 性能开销:方法调用基于反射,在高频调用场景下有一定性能损耗。
- 方法限制:只能代理接口中声明的公共方法,无法代理私有方法、final方法等。
三、CGLib 动态代理
CGLib 是一个强大的、高性能的字节码生成库,它通过继承目标类的方式创建代理。这完美解决了JDK动态代理必须基于接口的限制,可以对普通的、无接口的类进行代理。
其核心原理是:在运行时动态生成目标类的一个子类作为代理类,并重写其中的非final方法,在重写方法中植入增强逻辑。主要使用 Enhancer 类创建代理,MethodInterceptor 接口定义拦截逻辑。
核心使用步骤
首先,需要引入 CGLib 依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
定义一个无需实现任何接口的目标类。
public class DemoService {
// 注意:被代理的方法不能是final的
public void dosomething() {
System.out.println("执行核心业务逻辑");
}
}
实现 MethodInterceptor 接口,编写方法拦截逻辑。
public class CglibProxyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置增强:参数校验、缓存查询");
// 调用目标类(父类)的原始方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置增强:结果缓存、日志归档");
return result;
}
}
客户端通过 Enhancer 创建并使用代理对象。
public class Client {
public static void main(String[] args) {
// 1. 创建增强器Enhancer对象
Enhancer enhancer = new Enhancer();
// 2. 设置父类(即目标类)
enhancer.setSuperclass(DemoService.class);
// 3. 设置回调,即方法拦截器
enhancer.setCallback(new CglibProxyInterceptor());
// 4. 创建代理对象
DemoService proxy = (DemoService) enhancer.create();
// 5. 通过代理对象调用方法
proxy.dosomething();
}
}
CGLib 动态代理的优劣势
优点:
- 无需接口:可直接代理普通类,适用性更广。
- 性能较好:方法调用通过FastClass机制直接进行,通常比JDK反射调用更快。
- 功能强大:可以代理除final方法外的更多方法(如构造器、静态方法等)。
缺点:
- 第三方依赖:需要引入CGLib库。
- 目标类限制:无法代理final类或final方法。
- 初始化开销:首次创建代理对象时,字节码生成和加载过程有一定开销。
- 复杂度较高:涉及底层字节码操作,深入定制门槛较高。
总结与选型建议
三种代理模式各有其适用场景:
- 静态代理:适用于代理类较少、接口稳定的简单场景,追求极致的编译时安全和运行时性能。
- JDK动态代理:适用于基于接口编程的项目,Spring默认对实现了接口的Bean使用此方式。它是Java标准,无额外依赖。
- CGLib动态代理:适用于代理没有实现接口的类,或者需要更高性能的场景。Spring AOP在目标类没有接口时会自动使用CGLib。
理解它们的原理和区别,能帮助我们在实际开发中,尤其是在使用像Spring这类高度集成代理技术的框架时,做出更合理的设计与问题排查。如果你想深入探讨更多关于Java或架构设计的话题,欢迎来云栈社区与更多开发者交流。