Java动态代理:优化静态代理模式的灵活解决方案

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Java动态代理:优化静态代理模式的灵活解决方案

代理模式


定义


代理模式,为其他对象提供一种代理以控制对这个对象的访问



具体实现


代理模式的具体实现描述可以分为以下几个步骤:


创建抽象对象接口(Subject Interface):

定义抽象对象接口,包含原始对象和代理对象共同实现的方法。

/**
 * 抽象对象接口
 **/
public interface UserManager {
  public void addUser(String userId, String userName);
  public void delUser(String userId);
  public void modifyUser(String userId, String userName);
  public String findUser(String userId);
}


2. 创建原始对象(Real Object):


实现抽象对象接口的具体类,即原始对象。

public class UserManagerImpl implements UserManager {
  public void addUser(String userId, String userName) {
  try {
    System.out.println("UserManagerImpl.addUser() userId-->>" + userId);
  }catch(Exception e) {
    e.printStackTrace();
    throw new RuntimeException();
  } 
  }
  public void delUser(String userId) {
  System.out.println("UserManagerImpl.delUser() userId-->>" + userId);
  }
  public String findUser(String userId) {
  System.out.println("UserManagerImpl.findUser() userId-->>" + userId);
  return "张三";
  }
  public void modifyUser(String userId, String userName) {
  System.out.println("UserManagerImpl.modifyUser() userId-->>" + userId);
  }
}


3. 创建代理对象(Proxy Object):


实现抽象对象接口的具体类,即代理对象。

在代理对象中持有一个对原始对象的引用。

/**
 * 代理类
 **/
public class UserManagerImplProxy implements UserManager {
  private UserManager userManager;
  public UserManagerImplProxy(UserManager userManager) {
  this.userManager = userManager;
  }
  public void addUser(String userId, String userName) {
  try {
    System.out.println("start-->>addUser() userId--代理类-->>" + userId);
    userManager.addUser(userId, userName);
    System.out.println("success--代理类-->>addUser()");
  }catch(Exception e) {
    e.printStackTrace();
    System.out.println("error--代理类-->>addUser()");
  } 
  }
  public void delUser(String userId) {
  }
  public String findUser(String userId) {
  return null;
  }
  public void modifyUser(String userId, String userName) {
  }
}


在代理对象中添加额外逻辑:

在代理对象的方法中,可以在调用原始对象之前或之后执行一些额外的逻辑。

这些额外逻辑可以是权限验证、日志记录、缓存操作等。

在客户端中使用代理对象:

在客户端代码中,通过代理对象来访问原始对象的方法。

客户端不直接访问原始对象,而是通过代理对象进行间接访问。

public class Client {
  /**
  * @param args
  */
  public static void main(String[] args) {
  //创建代理对象,同时将被代理的对象通过构造函数传入到代理对象中
  UserManager userManager = new UserManagerImplProxy(new UserManagerImpl());
  //调用代理对象的方法
  userManager.addUser("0001", "张三");
  }
}

分析优缺点


上面写的代码都是静态代理模式的,所以这里罗列优缺点的基础也是基于静态代理的:

优点:


额外功能:静态代理可以在代理对象中添加额外的功能,而无需修改真实对象的代码。这使得我们可以在不影响真实对象的情况下,对其进行功能扩展,例如添加日志记录、性能监控等。


控制访问:静态代理可以在代理对象中控制对真实对象的访问。代理对象可以验证参数、权限等,并决定是否允许客户端访问真实对象。


解耦合:静态代理可以将真实对象与客户端代码解耦合。客户端只需要通过代理对象与真实对象进行交互,而无需关注真实对象的具体实现。


缺点:


代码重复:静态代理需要手动创建代理对象,并在代理对象中实现与真实对象相同的接口。这可能导致代理对象和真实对象之间的代码重复,增加了维护的工作量。


增加类的数量:静态代理需要为每个真实对象创建一个代理对象,这可能导致类的数量增加。如果需要代理多个真实对象,就需要为每个真实对象创建一个代理对象,这可能导致类的爆炸增长。


编译时确定:静态代理在编译时就确定了代理对象和真实对象的关系,无法动态改变。如果需要在运行时动态决定代理对象的行为,静态代理就无法满足需求。


优化使用动态代理解决


优化


使用动态代理来优化上述的静态代理代码,可以解决静态代理中的一些不足之处,如代码重复和类数量增加。下面是使用动态代理进行优化的示例代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
    public static void main(String[] args) {
        UserManager userManager = new UserManagerImpl();
        UserManager proxy = (UserManager) getProxyInstance(userManager);
        proxy.addUser("0001", "张三");
    }
    public static Object getProxyInstance(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new DynamicProxyHandler(target)
        );
    }
}
class DynamicProxyHandler implements InvocationHandler {
    private Object target;
    public DynamicProxyHandler(Object target) {
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result;
        try {
            System.out.println("start-->>" + method.getName() + "() userId--代理类-->>" + args[0]);
            result = method.invoke(target, args);
            System.out.println("success--代理类-->>" + method.getName() + "()");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("error--代理类-->>" + method.getName() + "()");
            throw new RuntimeException();
        }
        return result;
    }
}


通过使用动态代理,我们可以避免手动创建代理对象和实现代理接口的繁琐工作。在上述代码中,我们创建了一个DynamicProxyHandler类实现了InvocationHandler接口,它负责处理代理对象的方法调用。在getProxyInstance方法中,我们使用Proxy.newProxyInstance动态生成代理对象,并将DynamicProxyHandler作为参数传入。最后,我们可以直接调用代理对象的方法,实现对真实对象的代理。


使用动态代理优化静态代理的不足之处包括:


代码重复问题:使用动态代理,我们不需要为每个真实对象编写一个单独的代理类,而是可以在运行时动态生成代理对象。这样可以减少重复的代理类代码。


类数量增加问题:静态代理需要为每个真实对象创建一个对应的代理类,从而导致类的数量增加。而使用动态代理,我们只需要一个通用的代理类,可以代理多个真实对象。


动态性:动态代理在运行时动态生成代理对象,可以灵活地控制代理对象的行为。相比之下,静态代理在编译时就确定了代理对象和真实对象的关系,无法在运行时动态改变。


相关知识


动态代理种类


在Java中,有两种常见的动态代理方式:基于接口的动态代理和基于类的动态代理。


基于接口的动态代理(Interface-based Dynamic Proxy):

这种动态代理方式是基于接口进行代理的,使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。首先定义一个接口,然后创建一个实现InvocationHandler接口的代理处理器类,在处理器类中实现代理对象方法的增强逻辑。使用Proxy.newProxyInstance方法创建代理对象,传入类加载器、接口数组和代理处理器对象。代理对象可以调用接口中定义的方法,并在调用前后执行代理处理器中的逻辑。


基于类的动态代理(Class-based Dynamic Proxy):

基于类的动态代理是使用第三方库,如CGLib,来实现的。CGLib是一个强大的字节码增强库,它可以在运行时生成代理类,无需接口。通过继承目标类并重写其方法,CGLib能够在方法调用前后注入增强逻辑。使用CGLib的Enhancer类可以创建代理对象,并设置代理的父类、回调对象(实现MethodInterceptor接口),以及其他属性。代理对象可以调用父类中的方法,并在调用前后执行回调对象中的逻辑。


在上面的优化代码,使用的是JDK提供的动态代理实现方式,这种实现方式是基于接口的。这种基于接口方式的在uml类图上更加符合我们上面提供的类图结构。而cglib的代理方式是继承被代理类的方式,作为子类里式替换进行代理的。


场景应用


动态代理模式在开发中有许多场景应用,下面列举了几个常见的应用场景:


日志记录:通过使用动态代理,可以在方法调用前后记录日志信息,包括方法名、参数、执行时间等。这样可以方便地进行系统日志记录和调试,提高代码的可维护性和可调试性。


权限控制:动态代理可以在方法调用前进行权限验证,判断用户是否有权限执行该方法。这样可以实现细粒度的权限控制,保护系统中重要的功能不被非授权用户访问。


缓存操作:动态代理可以在方法调用前后进行缓存操作,例如查询数据时先检查缓存中是否存在该数据,如果存在则直接返回缓存数据,否则再查询数据库并将结果存入缓存中。这样可以提高系统的访问速度和性能。


事务管理:通过使用动态代理,可以在方法调用前后进行事务管理,包括开启事务、提交事务或回滚事务等操作。这样可以保证数据库操作的一致性和可靠性,防止数据丢失或不一致的情况发生。比如在spring框架下的@Transactional注解就是通过动态代理的方式进行管理事务的


性能监控:动态代理可以在方法调用前后进行性能监控,统计方法的执行时间和调用次数等信息。这样可以对系统的性能进行监控和优化,发现潜在的性能问题并进行针对性的优化措施。


远程调用:动态代理可以在方法调用时将请求发送到远程服务器,并接收远程服务器的响应结果。这样可以实现分布式系统之间的通信和协作,提供远程服务调用的能力。


总的来说,动态代理模式在开发中的应用非常广泛,它可以通过在方法调用前后注入逻辑来实现额外的功能,而不需要修改原始对象的代码。这种灵活性使得动态代理成为解决许多横切关注点(cross-cutting concerns)的有效方式,如日志记录、权限控制、缓存操作、事务管理等。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
21天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
26 0
|
24天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
22天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
13天前
|
存储 Java
Java 11 的String是如何优化存储的?
本文介绍了Java中字符串存储优化的原理和实现。通过判断字符串是否全为拉丁字符,使用`byte`代替`char`存储,以节省空间。具体实现涉及`compress`和`toBytes`方法,前者用于尝试压缩字符串,后者则按常规方式存储。代码示例展示了如何根据配置决定使用哪种存储方式。
|
22天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
48 5
|
20天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
23天前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
34 4
|
27天前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
41 5
|
9天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
38 6
|
24天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####