聊聊Java中的代理机制

代理在Java中有着广泛的应用,无论是spring的AOP还是注解对象的获取,又或者是事务处理,都少不了代理的存在。

代理模式

在Java中,代理是一种设计模式,它允许一个对象通过代理来控制对另一个对象的访问。

代理通常用于以下情况:

  1. 远程代理:允许客户端访问远程对象,就像它们在本地一样。

  2. 虚拟代理:允许根据需要创建昂贵的对象。例如,如果您有一个非常昂贵的对象,您可以使用虚拟代理来延迟其创建,直到需要使用它。

  3. 保护代理:允许控制对对象的访问。例如,您可以使用保护代理来限制对对象的访问,以确保只有授权用户可以访问它。

代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。

代理模式角色分为 3 种:

Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
RealSubject(真实主题角色):真正实现业务逻辑的类;
Proxy(代理主题角色):用来代理和封装真实主题;

在Java中,代理通常通过创建一个实现与被代理对象相同接口的代理类来实现。代理类持有对被代理对象的引用,并根据需要将方法调用传递给被代理对象。例如,以下是一个简单的代理示例:

public interface Image {
  void display();
}

public class RealImage implements Image {
  private String fileName;
  
  public RealImage(String fileName) {
    this.fileName = fileName;
    loadFromDisk(fileName);
  }
  
  public void display() {
    System.out.println("Displaying " + fileName);
  }
  
  private void loadFromDisk(String fileName) {
    System.out.println("Loading " + fileName);
  }
}

public class ProxyImage implements Image {
  private String fileName;
  private RealImage realImage;
  
  public ProxyImage(String fileName) {
    this.fileName = fileName;
  }
  
  public void display() {
    if (realImage == null) {
      realImage = new RealImage(fileName);
    }
    realImage.display();
  }
}

在这个例子中,RealImage类是实际的图像类,ProxyImage类是代理类。当客户端调用ProxyImagedisplay()方法时,ProxyImage会创建RealImage对象(如果它还没有被创建),然后将方法调用传递给RealImage对象。

使用代理模式可以带来多个好处,包括代码解耦和延迟对象创建等。

静态代理与动态代理

静态代理就是上面代码的例子,我们通过手动new一个RealImage来调用相应的方法。
通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。

静态代理的缺点
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类。
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

解决办法呢,就是动态代理。
以下是将上述代码改造为动态代理的示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public interface Image {
  void display();
}

public class RealImage implements Image {
  private String fileName;

  public RealImage(String fileName) {
    this.fileName = fileName;
    loadFromDisk(fileName);
  }

  public void display() {
    System.out.println("Displaying " + fileName);
  }

  private void loadFromDisk(String fileName) {
    System.out.println("Loading " + fileName);
  }
}

public class ProxyImage implements InvocationHandler {
  private Object realObject;
  
  public ProxyImage(Object realObject) {
    this.realObject = realObject;
  }
  
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result = null;
    if (method.getName().equals("display")) {
      System.out.println("Before displaying image...");
      result = method.invoke(realObject, args);
      System.out.println("After displaying image...");
    } else {
      result = method.invoke(realObject, args);
    }
    return result;
  }
  
  public static void main(String[] args) {
    RealImage realImage = new RealImage("test.png");
    Image proxyImage = (Image) Proxy.newProxyInstance(
        realImage.getClass().getClassLoader(),
        new Class[] { Image.class },
        new ProxyImage(realImage)
    );
    proxyImage.display();
  }
}

在上面的示例中,我们首先将 ProxyImage 类实现为 InvocationHandler 接口的代理类,这是动态代理的核心部分。然后,我们在 main 方法中创建了一个 RealImage 对象,然后使用 Proxy.newProxyInstance 方法创建了一个动态代理对象 proxyImage。在 Proxy.newProxyInstance 方法的第一个参数中,我们传入了 RealImage 类的类加载器,以确保能够正确加载类。在第二个参数中,我们传入了一个 Image 类型的数组,表示我们要为哪些接口创建代理类。在第三个参数中,我们传入了一个 ProxyImage 对象,表示我们要使用哪个代理类作为代理对象。最后,我们调用 proxyImage.display() 方法来显示图像。当我们调用 display() 方法时,代理对象会在方法调用前后执行一些额外的逻辑。在本例中,我们在显示图像前后分别打印了一些文本。

Java虚拟机类加载过程主要分为五个阶段:加载验证准备解析初始化。其中加载阶段需要完成以下3件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

  1. 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
  2. 从网络中获取,典型的应用是 Applet
  3. 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
    由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
  4. 从数据库中获取等等

所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。

动态代理的实现方式

1. jdk动态代理实现

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler,也就是我们上面动态代理的代码。这里就不再重复。

2. cglib动态代理实现

CGLIB 实现上述动态代理代码的示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class RealImage {
  private String fileName;

  public RealImage(String fileName) {
    this.fileName = fileName;
    loadFromDisk(fileName);
  }

  public void display() {
    System.out.println("Displaying " + fileName);
  }

  private void loadFromDisk(String fileName) {
    System.out.println("Loading " + fileName);
  }
}

public class ProxyImage implements MethodInterceptor {
  private Object realObject;
  
  public ProxyImage(Object realObject) {
    this.realObject = realObject;
  }
  
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    Object result = null;
    if (method.getName().equals("display")) {
      System.out.println("Before displaying image...");
      result = proxy.invoke(realObject, args);
      System.out.println("After displaying image...");
    } else {
      result = proxy.invoke(realObject, args);
    }
    return result;
  }
  
  public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(RealImage.class);
    enhancer.setCallback(new ProxyImage(new RealImage("test.png")));
    RealImage proxyImage = (RealImage) enhancer.create();
    proxyImage.display();
  }
}

首先定义一个 RealImage 类,它没有实现任何接口。然后,我们定义了一个 ProxyImage 类,它实现了 MethodInterceptor 接口。在 ProxyImage 类中,我们在 intercept 方法中实现了代理逻辑。当代理对象调用 display() 方法时,代理对象会在方法调用前后执行一些额外的逻辑。在本例中,我们在显示图像前后分别打印了一些文本。在 main 方法中,我们首先创建一个 Enhancer 对象,它会帮助我们创建动态代理对象。然后,我们设置了 RealImage 类作为代理对象的父类,并将 ProxyImage 对象设置为代理逻辑的回调实现。最后,我们调用 enhancer.create() 方法来创建动态代理对象,并将其转换为 RealImage 类型。当我们调用 display() 方法时,代理对象会执行我们定义的额外逻辑,并最终调用 RealImage 对象的 display() 方法来显示图像。

CGLIB 创建动态代理类的模式是:

  1. 查找目标类上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求
JDK动态代理与CGLIB动态代理对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy 的优势:

最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
代码实现简单。
基于类似 cglib 框架的优势:

无需实现接口,达到代理类无侵入
只操作我们关心的类,而不必为其他相关类增加工作量。
高性能

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值