ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

深入解析代理模式:原理、实现与实战应用

深入解析代理模式:原理、实现与实战应用 1. 代理模式的核心价值与应用场景代理模式Proxy Pattern是23种经典设计模式中结构型模式的代表之一。它的核心思想非常简单却异常强大通过一个代理对象来控制对原始对象的访问。这种模式在日常开发中随处可见特别是在需要控制对象访问权限、延迟加载或远程调用等场景下。我最早接触代理模式是在处理第三方支付接口对接时。当时需要调用支付宝的远程接口但直接在每个业务模块中硬编码远程调用不仅耦合度高而且难以统一处理签名验证、重试机制等问题。通过引入代理模式我们将所有远程调用封装在本地代理类中业务代码只需调用本地接口即可这就是典型的本地接口代理远程接口调用场景。2. 代理模式的三种实现方式2.1 静态代理最直观的实现静态代理是最基础的实现方式需要手动创建代理类。假设我们有一个远程服务接口public interface RemoteService { String getData(String param); }对应的静态代理实现如下public class RemoteServiceProxy implements RemoteService { private RemoteService realService; public RemoteServiceProxy() { // 实际项目中这里可能是初始化网络连接 this.realService new RemoteServiceImpl(); } Override public String getData(String param) { // 前置处理日志、参数校验等 System.out.println(调用远程服务参数 param); // 实际调用 String result realService.getData(param); // 后置处理结果转换、异常处理等 System.out.println(远程调用完成结果长度 result.length()); return result; } }静态代理的优点是简单直接但缺点也很明显每个接口都需要手动创建代理类当接口方法很多时会非常繁琐。2.2 动态代理Java内置的灵活方案Java的动态代理机制可以运行时生成代理类大大简化了开发工作。继续使用上面的RemoteService接口public 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(准备调用方法 method.getName()); // 实际调用 Object result method.invoke(target, args); // 后置处理 System.out.println(方法调用完成返回类型 result.getClass()); return result; } } // 使用方式 RemoteService realService new RemoteServiceImpl(); RemoteService proxy (RemoteService) Proxy.newProxyInstance( RemoteService.class.getClassLoader(), new Class?[] {RemoteService.class}, new DynamicProxyHandler(realService) );动态代理的优势在于一个处理器可以代理多个接口但缺点是只能代理接口不能代理类。2.3 CGLIB代理无需接口的方案对于没有接口的类可以使用CGLIB库实现代理。Spring框架就大量使用了这种技术public class CglibProxy implements MethodInterceptor { 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; } } // 使用方式 Enhancer enhancer new Enhancer(); enhancer.setSuperclass(RealService.class); enhancer.setCallback(new CglibProxy()); RealService proxy (RealService) enhancer.create();CGLIB通过继承目标类来实现代理因此需要注意final方法和类的问题。3. 远程接口代理的实战应用3.1 RPC框架中的代理模式现代RPC框架如Dubbo、gRPC都大量使用代理模式。以Dubbo为例服务消费者的本地调用实际上是通过代理完成的// 配置引用远程服务 Reference private UserService userService; // 实际调用看起来是本地调用实际是远程 User user userService.getUserById(123);Dubbo在背后为我们生成了UserService的代理类处理了包括服务发现负载均衡网络通信序列化/反序列化超时重试3.2 实现一个简易RPC代理让我们实现一个简单的RPC代理展示代理模式如何封装远程调用public class RpcProxy implements InvocationHandler { private String host; private int port; public RpcProxy(String host, int port) { this.host host; this.port port; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 建立网络连接 try (Socket socket new Socket(host, port); ObjectOutputStream out new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream in new ObjectInputStream(socket.getInputStream())) { // 2. 发送调用信息 out.writeObject(method.getDeclaringClass().getName()); // 接口名 out.writeObject(method.getName()); // 方法名 out.writeObject(method.getParameterTypes()); // 参数类型 out.writeObject(args); // 参数值 // 3. 获取返回结果 return in.readObject(); } } SuppressWarnings(unchecked) public static T T create(ClassT interfaceClass, String host, int port) { return (T) Proxy.newProxyInstance( interfaceClass.getClassLoader(), new Class?[] {interfaceClass}, new RpcProxy(host, port) ); } }使用方式UserService userService RpcProxy.create(UserService.class, 127.0.0.1, 8080); User user userService.getUserById(123); // 实际是远程调用3.3 代理模式在微服务中的实践在Spring Cloud生态中FeignClient就是代理模式的典型应用FeignClient(name user-service) public interface UserService { GetMapping(/users/{id}) User getUserById(PathVariable(id) Long id); } // 实际调用 Autowired private UserService userService; // 注入的是代理实例 public User getUser(Long id) { return userService.getUserById(id); // 通过HTTP调用远程服务 }Feign的代理实现处理了服务发现与负载均衡通过RibbonHTTP请求构造响应处理熔断降级通过Hystrix4. 代理模式的高级应用与优化4.1 延迟加载代理代理模式可以实现延迟加载这在处理大对象时特别有用public class ImageProxy implements Image { private String filePath; private RealImage realImage; public ImageProxy(String filePath) { this.filePath filePath; } Override public void display() { if (realImage null) { realImage new RealImage(filePath); // 实际使用时才加载 } realImage.display(); } }4.2 缓存代理通过代理可以实现方法级别的缓存public class CacheProxy implements InvocationHandler { private Object target; private MapString, Object cache new HashMap(); public CacheProxy(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String cacheKey generateCacheKey(method, args); if (cache.containsKey(cacheKey)) { return cache.get(cacheKey); } Object result method.invoke(target, args); cache.put(cacheKey, result); return result; } private String generateCacheKey(Method method, Object[] args) { // 生成唯一的缓存键 return method.getName() Arrays.deepHashCode(args); } }4.3 代理链与责任链模式结合多个代理可以形成链式调用实现更复杂的功能组合public class LoggingProxy implements InvocationHandler { private Object target; public LoggingProxy(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(开始执行方法 method.getName()); Object result method.invoke(target, args); System.out.println(方法执行完成 method.getName()); return result; } } public class RetryProxy implements InvocationHandler { private Object target; private int maxRetries; public RetryProxy(Object target, int maxRetries) { this.target target; this.maxRetries maxRetries; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { int retries 0; while (true) { try { return method.invoke(target, args); } catch (Exception e) { if (retries maxRetries) throw e; System.out.println(调用失败准备重试 ( retries / maxRetries )); Thread.sleep(1000); } } } } // 组合使用 RemoteService realService new RemoteServiceImpl(); RemoteService retryProxy (RemoteService) Proxy.newProxyInstance( RemoteService.class.getClassLoader(), new Class?[] {RemoteService.class}, new RetryProxy(realService, 3) ); RemoteService loggingProxy (RemoteService) Proxy.newProxyInstance( RemoteService.class.getClassLoader(), new Class?[] {RemoteService.class}, new LoggingProxy(retryProxy) );5. 代理模式的性能考量与最佳实践5.1 代理模式的性能开销代理模式会引入一定的性能开销主要来自方法调用的额外层级反射调用动态代理/CGLIB额外的对象创建在性能敏感的场景可以考虑缓存代理对象减少不必要的代理层级对于简单场景使用静态代理5.2 代理模式的最佳实践接口设计原则代理类应该与被代理类实现相同的接口保持透明性单一职责每个代理类应该只负责一个横切关注点如日志、缓存等命名规范代理类通常以Proxy结尾如UserServiceProxy文档说明明确记录代理类添加了哪些额外功能5.3 常见问题排查问题1代理对象方法调用不生效检查是否正确地获取了代理对象而非原始对象确认代理逻辑没有被意外绕过问题2动态代理抛出UndeclaredThrowableException这通常是因为被代理方法抛出了检查异常解决方案在接口方法上声明可能抛出的异常或者在代理中处理这些异常问题3CGLIB代理失败确认目标类和方法不是final的检查是否有默认构造函数确保CGLIB依赖版本兼容6. 代理模式在现代框架中的应用6.1 Spring AOP中的代理Spring AOP默认使用JDK动态代理针对接口或CGLIB针对类来实现切面编程Aspect Component public class LoggingAspect { Before(execution(* com.example.service.*.*(..))) public void logBefore(JoinPoint joinPoint) { System.out.println(准备执行: joinPoint.getSignature()); } }Spring会根据目标对象自动选择代理方式可以通过EnableAspectJAutoProxy(proxyTargetClass true)强制使用CGLIB。6.2 MyBatis的Mapper代理MyBatis的Mapper接口也是通过动态代理实现的public class MapperProxyT implements InvocationHandler { private final SqlSession sqlSession; private final ClassT mapperInterface; Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 将方法调用转换为SQL执行 return sqlSession.selectOne( mapperInterface.getName() . method.getName(), args ! null args.length 1 ? args[0] : args ); } }6.3 Hibernate的延迟加载Hibernate使用代理来实现关联对象的延迟加载User user session.load(User.class, 1L); // 返回代理对象 // 实际访问属性时才触发数据库查询 System.out.println(user.getName());这种代理实现了透明化的延迟加载对使用者完全无感知。
返回列表