静态代理与动态代理

简介:   代理,就是代替原来的角色去做事,下面这张图对代理的解释很生动形象:

  代理,就是代替原来的角色去做事,下面这张图对代理的解释很生动形象:


37.png


静态代理


  23种设计模式中,代理模式是一种很经典的模式,当我们想改变一个类的行为的时候(比如添加额外的服务像添加日志等),可以创造一个跟这个类实现相同接口的类,重写对应的方法。这就相当于为一个类创建了一个“替身”。


  代理模式的结构如下:

38.png


  比如要给原来的业务逻辑添加日志功能,则可以添加代理类,重写需要添加日志的业务类。

  用户管理接口

public interface UserManager {
  public void addUser(User user);
}


  用户管理实现类


public class UserManagerImpl implements UserManager {
  @Override
  public void addUser(User user) {
    System.out.println("---------addUser()---------");
  }
}

 用户管理代理类


public class UserManagerProxyImpl implements UserManager{
  private UserManager userManager;
  public UserManagerProxyImpl(UserManager userManager){
    this.userManager=userManager;
  }
  @Override
  public void addUser(User user) {
    userManager.addUser(user);
    //添加额外服务-日志功能
    logger.info("添加用户:"+user.getUserName());
  }
}


此时调用这个新的方法,不会改变系统原来的稳定性。





动态代理


 上面的静态代理,每个代理类只能为一个业务类服务,如果放在实际应用中,肯定需要使用多个代理,同时会带来大量的重复代码。动态代理可以为任何类动态生成一个代理类来实现全部的代理功能。


 静态代理由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

在程序运行时,运用反射机制动态创建而成。


 JDK和Cglib都实现了动态代理,但略有差异,下面分别说明。



 JDK动态代理


 动态代理类克服了上面静态代理类需要继承唯一接口,并且要实现相应的方法的缺陷。他可以为任何类创建代理类并且在运行时动态创建代理对象。


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogHandler implements InvocationHandler{
  //被代理的目标类
  private Object targetObject;
  //创建代理
  public Object createProxyInstance(Object targetObject){
    this.targetObject=targetObject;
    return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    //代理服务方法
    insertLog(method,args);
    //调用目标方法(原业务逻辑)
    Object ret=method.invoke(targetObject, args);
    return ret;
  }
  private void insertLog(Method method,Object[] args){
    System.out.println("logInfo:【methodName:"+method.getName()+";args:"+args+"】");
  }
}


 实现动态代理,利用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法获得代理类,loader就是被代理类的类加载器,interfaces 是被代理类实现的接口,它实现了几个接口,就会为其生成几个相应的代理类。此外必须要实现InvocationHandler接口,重写其invoke方法,用java.lang.reflect.Method.invoke()方法调用目标方法(可以在调用之前或者之后添加代理服务方法)。


 客户端调用如下:


public class Client {
  public static void main(String[] args) {
    LogHandler logHandler=new LogHandler();
    UserManager userManager=(UserManager)logHandler.createProxyInstance(new UserManagerImpl());
    userManager.addUser(new User("Danny","123456"));
    OrderManager orderManager=(OrderManager)logHandler.createProxyInstance(new OrderManagerImpl());
    orderManager.addOrder(new Order("Danny","B20160726085911223004"));
  }
}


 如上,调用动态代理的时候,new一个动态代理的实例,通过动态代理来创建原业务类的代理类,然后直接调用接口就行。这样不仅可以为UserManager这一个接口服务了,其他接口的方法如果需要添加日志,都可以通过这一个动态代理类来实现。




 Cglib动态代理


 添加jar包asm-commons-2.2.2.jar、asm-util-2.2.2.jar、asm-2.2.2.jar、cglib-nodep-2.1_3.jar。


 注意:如果用了cglib-nodep-2.1_3.jar,就不能用cglib-2.2.2.jar了,这两个jar包会冲突;而且Spring的asm-2.2.2.jar和Hibernate的asm.jar也会冲突,不能共存。否则会报如下的错:

java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.<init>(Z)V

  Cglib代理实现

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
  private Enhancer enhancer = new Enhancer();
  public Object getProxy(Class clazz) {
    // 设置需要创建子类的类
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(this);
    // 通过字节码技术动态创建子类实例
    return enhancer.create();
  }
  // 实现MethodInterceptor接口方法
  public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
    //代理服务方法
    insertLog(method,args);
    // 通过代理类调用父类中的方法
    Object result = proxy.invokeSuper(obj, args);
    return result;
  }
  private void insertLog(Method method,Object[] args){
    System.out.println("logInfo:【methodName:"+method.getName()+";args:"+args+"】");
  }
}


  客户端调用如下:


public class Client {
  public static void main(String[] args) {
    CglibProxy cglibProxy=new CglibProxy();
    UserManagerImpl userManager=(UserManagerImpl)cglibProxy.getProxy(UserManagerImpl.class);
    userManager.addUser(new User("Danny","123456"));
  }
}


 最后简单总结两点JDK和Cglib实现的动态代理的区别:


 1、JDK实现动态代理的代理对象和目标对象必须实现相同的接口;Cglib实现动态代理的原理则是为目标对象创建一个子类座位代理对象。如果目标类实现了接口,则必须用JDK动态代理,否则,两个都可以用。


 2、JDK在运算量小的时候性能优于Cglib,运算量大的时候Cglib性能较优。详参考《Cglib 与 JDK动态代理的运行性能比较》


相关文章
|
3月前
|
存储 数据可视化 小程序
搭建内容中台:打造高效可复用的企业内容管理基石
内容中台的本质,是将企业的 “内容资源” 转化为 “可复用、可运营的数字资产”,让内容从 “每个渠道的附属品”,变成 “驱动全渠道业务增长的核心能力”—— 它不仅是技术层面的平台,更是企业内容战略的 “落地载体”。
226 0
|
4月前
|
算法 调度 决策智能
【两阶段鲁棒优化】利用列-约束生成方法求解两阶段鲁棒优化问题(Python代码实现)
【两阶段鲁棒优化】利用列-约束生成方法求解两阶段鲁棒优化问题(Python代码实现)
115 0
|
安全 编译器 开发者
Python打包成.exe文件直接运行
Python打包成.exe文件直接运行
2149 1
|
存储 NoSQL 大数据
大数据存储:HBase与Cassandra的对比
【7月更文挑战第16天】HBase和Cassandra作为两种流行的分布式NoSQL数据库,在数据模型、一致性模型、数据分布、查询语言和性能等方面各有千秋。HBase适用于需要强一致性和与Hadoop生态系统集成的场景,如大规模数据处理和分析。而Cassandra则更适合需要高可用性和灵活查询能力的场景,如分布式计算、云计算和大数据应用等。在实际应用中,选择哪种数据库取决于具体的需求和场景。希望本文的对比分析能够帮助读者更好地理解这两种数据库,并做出明智的选择。
1160 1
|
对象存储 数据库
2026年 | 1月云大使推广奖励规则
云大使推广返利活动,企业新用户下单返佣加码5%,推广最高返佣45%,新老用户都可参与返利活动。
|
SQL 缓存 关系型数据库
MySQL常见问题解决和自动化安装脚本
这篇内容包含了两个主要部分:解决MySQL登录问题和处理GPG密钥问题。当MySQL密码正确但无法登录时,可以通过执行SQL命令`ALTER USER`和`flush privileges`来修改和重置密码。对于MySQL安装时的GPG密钥错误,首先需要强制删除旧的MySQL仓库包,导入新的GPG公钥,然后安装MySQL服务器。如果遇到GPG检查错误,可以使用`--nogpgcheck`参数忽略检查来安装。最后,提供了一个自动化安装MySQL的脚本,用于检查旧版本、卸载残留、安装MySQL8并启动服务。
1285 1
MySQL常见问题解决和自动化安装脚本
|
存储 机器学习/深度学习 数据采集
基于LightGBM的肺癌分类模型:从预测到个体化治疗
基于LightGBM的肺癌分类模型:从预测到个体化治疗
766 1
|
编解码 前端开发 JavaScript
渐进增强与优雅降级:提升用户体验的双重策略
渐进增强与优雅降级:提升用户体验的双重策略
渐进增强与优雅降级:提升用户体验的双重策略
|
存储 NoSQL atlas
2024年向量数据库推荐榜单之MongoDB
目前市面上有哪些向量数据库解决方案,可协助您存储和检索高维向量?在推荐优选的几款向量数据库和库之前,我们需要厘清以下这两种技术的差异。
4976 0