1. 代理模式在Java中的核心价值
代理模式是Java开发者必须掌握的核心设计模式之一,它通过引入代理对象来控制对原始对象的访问。这种模式在日常开发中有着广泛的应用场景,特别是在需要控制对象访问权限、添加额外功能或延迟加载资源的情况下。
我在实际项目中遇到过这样一个典型场景:系统需要记录所有数据库查询的执行时间,但又不希望在每个DAO方法中都手动添加计时逻辑。这时候代理模式就派上了大用场。通过动态代理,我们可以在不修改原有代码的情况下,统一为所有数据库操作添加性能监控功能。
重要提示:理解代理模式的关键在于区分"代理对象"和"被代理对象"。代理对象持有被代理对象的引用,并在调用前后执行额外操作,但最终的业务逻辑还是由被代理对象完成。
Java中的代理主要分为两种实现方式:
- 静态代理:在编译期就确定代理关系,需要为每个被代理类创建对应的代理类
- 动态代理:在运行时动态生成代理类,更加灵活高效
2. 静态代理的实现与局限
2.1 静态代理的基本实现
静态代理是最基础的代理形式,我们需要手动创建代理类。下面是一个典型的静态代理示例:
// 接口定义 public interface UserService { void addUser(String username); } // 被代理类 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("添加用户: " + username); } } // 代理类 public class UserServiceProxy implements UserService { private UserService target; // 持有被代理对象 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { System.out.println("前置处理: 验证用户权限"); target.addUser(username); // 调用被代理对象的方法 System.out.println("后置处理: 记录操作日志"); } }使用方式:
UserService service = new UserServiceProxy(new UserServiceImpl()); service.addUser("张三");2.2 静态代理的优缺点分析
静态代理的主要优点在于:
- 实现简单直观,容易理解
- 可以在不修改目标对象的前提下扩展功能
- 代理类和被代理类都实现相同接口,符合开闭原则
但静态代理也存在明显缺点:
- 代理类和被代理类是一对一的关系,如果有多个类需要代理,就需要创建多个代理类
- 接口增加方法时,代理类和被代理类都需要修改,维护成本高
- 代码冗余,每个代理类都需要重复编写类似的代理逻辑
在实际项目中,静态代理通常只适用于代理少量固定类的情况。对于需要代理多个类或者代理关系可能变化的场景,动态代理是更好的选择。
3. Java动态代理深度解析
3.1 JDK动态代理实现原理
JDK动态代理是Java标准库提供的代理机制,它利用反射API在运行时动态生成代理类。下面是典型实现:
public class LoggingInvocationHandler implements InvocationHandler { private Object target; // 被代理对象 public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("调用方法: " + method.getName()); long start = System.currentTimeMillis(); Object result = method.invoke(target, args); // 调用被代理对象的方法 long duration = System.currentTimeMillis() - start; System.out.println("方法执行耗时: " + duration + "ms"); return result; } }使用方式:
UserService userService = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, new LoggingInvocationHandler(userService) ); proxy.addUser("李四");3.2 JDK动态代理的核心机制
JDK动态代理的实现基于以下几个关键点:
Proxy类:提供创建动态代理的静态方法InvocationHandler接口:代理逻辑的具体实现- 运行时生成的代理类:继承
Proxy并实现指定接口
JDK动态代理有一些重要特性需要注意:
- 只能代理接口,不能代理类
- 代理类会覆盖Object类的equals、hashCode和toString方法
- 代理类的类名通常以
$Proxy开头 - 代理实例的性能比普通类实例稍差,因为涉及反射调用
我在实际项目中曾遇到一个性能问题:在高频调用的场景下,动态代理的方法调用比直接调用慢3-5倍。解决方案是尽量减少代理方法中的反射调用,或者对热点方法进行缓存优化。
4. CGLIB动态代理技术
4.1 CGLIB与JDK动态代理的对比
当我们需要代理没有实现接口的类时,JDK动态代理就无法满足需求了。这时可以使用CGLIB(Code Generation Library):
| 特性 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理对象 | 必须实现接口 | 可以代理普通类 |
| 性能 | 中等 | 较快 |
| 实现方式 | 反射 | 字节码生成 |
| 依赖 | JDK自带 | 需要额外jar包 |
| 方法过滤 | 只能代理接口方法 | 可以过滤特定方法 |
4.2 CGLIB实现示例
下面是使用CGLIB创建代理的典型代码:
public class CglibProxy implements MethodInterceptor { private Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLIB前置处理"); Object result = proxy.invokeSuper(obj, args); System.out.println("CGLIB后置处理"); return result; } }使用方式:
UserService userService = (UserService) new CglibProxy().getInstance(new UserServiceImpl()); userService.addUser("王五");CGLIB通过生成目标类的子类来实现代理,因此需要注意:
- 被代理的类和方法不能是final的
- 构造函数会被调用两次(CGLIB的实现机制导致)
- 比JDK动态代理更消耗内存
5. 代理模式的高级应用场景
5.1 Spring AOP中的代理实现
Spring框架广泛使用代理模式来实现AOP(面向切面编程)。根据目标对象是否实现接口,Spring会自动选择JDK动态代理或CGLIB:
@Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB public class AppConfig { // 配置类 }Spring AOP的代理机制有几个特点:
- 默认对接口使用JDK动态代理,对类使用CGLIB
- 可以通过配置强制使用CGLIB
- 代理对象由Spring容器管理,开发者通常无感知
5.2 MyBatis中的动态代理
MyBatis的Mapper接口也是通过JDK动态代理实现的:
public class MapperProxy<T> implements InvocationHandler { private final SqlSession sqlSession; private final Class<T> mapperInterface; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 将方法调用转换为SQL执行 return sqlSession.selectOne(method.getName(), args[0]); } }这种设计使得MyBatis的Mapper接口不需要实现类,极大简化了DAO层的开发。
5.3 RPC框架中的代理应用
在分布式系统中,RPC框架通常使用动态代理来隐藏远程调用的细节:
public class RpcClientProxy implements InvocationHandler { private String host; private int port; public RpcClientProxy(String host, int port) { this.host = host; this.port = port; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 构造请求 RpcRequest request = new RpcRequest(); request.setClassName(method.getDeclaringClass().getName()); request.setMethodName(method.getName()); request.setParameters(args); // 发送网络请求 return sendRequest(host, port, request); } }这种设计使得客户端可以像调用本地方法一样调用远程服务,大大简化了分布式编程。
6. 代理模式的性能优化
6.1 代理对象缓存
由于创建代理对象的开销较大,通常应该缓存代理实例:
public class ProxyFactory { private static final Map<Class<?>, Object> proxyCache = new ConcurrentHashMap<>(); public static <T> T getProxy(Class<T> interfaceClass) { return (T) proxyCache.computeIfAbsent(interfaceClass, clazz -> { InvocationHandler handler = new CustomInvocationHandler(); return Proxy.newProxyInstance( clazz.getClassLoader(), new Class[]{clazz}, handler ); }); } }6.2 方法调用优化
对于高频调用的代理方法,可以考虑以下优化手段:
- 缓存Method对象,避免重复获取
- 对简单方法直接调用,不经过反射
- 使用MethodHandle代替反射API
public class OptimizedInvocationHandler implements InvocationHandler { private final Object target; private final Map<Method, MethodHandle> methodHandleCache = new ConcurrentHashMap<>(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodHandle handle = methodHandleCache.computeIfAbsent(method, m -> { try { MethodHandles.Lookup lookup = MethodHandles.lookup(); return lookup.unreflect(method); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }); return handle.invokeWithArguments(args); } }6.3 选择性代理
不是所有方法都需要代理,可以通过注解或配置指定需要代理的方法:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ProxyMethod { } public class SelectiveInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.isAnnotationPresent(ProxyMethod.class)) { // 执行代理逻辑 System.out.println("代理方法: " + method.getName()); } return method.invoke(target, args); } }7. 常见问题与解决方案
7.1 代理对象无法序列化问题
当我们需要序列化代理对象时可能会遇到问题,解决方案是:
public class SerializableInvocationHandler implements InvocationHandler, Serializable { private static final long serialVersionUID = 1L; private Object target; // 实现writeReplace方法 protected Object writeReplace() { return Proxy.newProxyInstance( getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } }7.2 自调用问题
在代理对象的方法中调用自身的另一个代理方法时,第二个调用不会经过代理:
public interface Service { void methodA(); void methodB(); } public class ServiceImpl implements Service { @Override public void methodA() { methodB(); // 这个调用不会经过代理 } @Override public void methodB() { // 实现 } }解决方案:
- 通过AopContext获取当前代理对象
- 将代理对象作为参数传入
- 重构代码避免自调用
7.3 代理对象识别
有时需要判断一个对象是否是代理对象:
public static boolean isProxy(Object obj) { if (obj == null) { return false; } // JDK动态代理 if (Proxy.isProxyClass(obj.getClass())) { return true; } // CGLIB代理 return obj.getClass().getName().contains("$$EnhancerByCGLIB$$"); }8. 代理模式的最佳实践
根据我多年的项目经验,以下是使用代理模式的一些建议:
- 接口设计原则:为需要代理的类定义清晰的接口,保持接口单一职责
- 代理粒度控制:不要过度使用代理,只为真正需要增强或控制的方法添加代理
- 性能考量:在高性能场景下,考虑使用静态代理或手动编码代替动态代理
- 异常处理:在InvocationHandler中妥善处理异常,避免吞没原始异常
- 线程安全:确保InvocationHandler的实现是线程安全的
一个典型的综合应用案例是数据库事务管理:
public class TransactionInvocationHandler implements InvocationHandler { private Object target; private DataSource dataSource; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); Object result = method.invoke(target, args); conn.commit(); return result; } catch (Exception e) { if (conn != null) { conn.rollback(); } throw e; } finally { if (conn != null) { conn.close(); } } } }这种设计可以统一管理事务,而不需要在每个业务方法中重复编写事务代码。