本文参考

在上一篇"Netty + Spring + ZooKeeper搭建轻量级RPC框架"文章中涉及到了Java动态代理和CGLib代理,在这篇文章中对这两种代理方式做详解

下面是本文参考:

https://www.baeldung.com/cglib

https://blog.csdn.net/shallynever/article/details/103351299

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html

环境

Oracle jdk 1.8.0_241 + CGLib 3.3.0

Java动态代理 —— 简单案例

首先声明一个接口

public interface FooInterface {

  String sayHello();
}

然后实现这个接口

public class FooImpl implements FooInterface {

  @Override

  public String sayHello() {

    return "hello world!";
  }
}

最后是Java动态代理的实现

@Test
public void helloProxy() {

  FooInterface foo = (FooInterface) Proxy.newProxyInstance(

    FooInterface.class.getClassLoader(),

    new Class<?>[]{FooInterface.class},

    new InvocationHandler() {

      @Override

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("proxy invoked");

        return method.invoke(new FooImpl(), args);
      }
    }
  );

  System.out.println(foo.sayHello());
}

还有另外一种分步的写法,上面单步写法的newProxyInstance()方法实际上是将分步写法的代码统一到了一起,并且已经在方法内捕获了异常

a proxy instance can be also be created by calling the Proxy.newProxyInstance method, which combines the actions of calling Proxy.getProxyClass with invoking the constructor with an invocation handler.

但是分步写法能够帮助我们更好地理解Java动态代理

@Test
public void helloProxy() throws NoSuchMethodException, IllegalAccessException,

InvocationTargetException, InstantiationException {

  Class<?> proxyClass = Proxy.getProxyClass(

    FooInterface.class.getClassLoader(),

    FooInterface.class);

  InvocationHandler handler = new InvocationHandler() {

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

      System.out.println("proxy invoked");

      return method.invoke(new FooImpl(), args);
    }
  };

  FooInterface foo = (FooInterface) proxyClass.getConstructor(InvocationHandler.class)
                                          .newInstance(handler);

  System.out.println(foo.sayHello());
}

执行结果如下

Java动态代理 —— 基本概念

动态代理类 -> 在运行时创建,并实现了一系列指定的接口,对应分步写法中Proxy.getProxyClass()方法获得的proxyClass实例

A dynamic proxy class (simply referred to as a proxy class below) is a class that implements a list of interfaces specified at runtime when the class is created

代理接口 -> 由动态代理类实现的接口,对应Proxy.getProxyClass()方法内指明的FooInterface

A proxy interface is such an interface that is implemented by a proxy class.

代理实例 -> 动态代理类创建的实例,对应分步写法中proxyClass.getConstructor(InvocationHandler.class).newInstance()方法返回的对象,并且该对象可以被强制类型转换为代理接口的实例,即分步写法中的foo

每一个代理实例都有一个相关联的InvocationHandler实现,并且代理实例的方法调用会经过InvocationHandler的invoke方法,我们可以看到在输出"hello world!"之前先输出了"proxy invoke",说明经过了invoke()方法

A proxy instance is an instance of a proxy class. Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler.

A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the invoke method of the instance's invocation handler, passing the proxy instance

invoke() -> 第一个参数Object proxy代表相关联的代理实例,第二个参数Method method代表代理实例调用的方法,这个方法也可以来自于代理接口的父接口,第三个参数Object[] args代表方法传入的参数,基类型(如int,double)会升级为对应的包装类(如Integer和Double),若未传参,则为null

invoke()方法的返回值就是代理实例调用方法后的返回值

proxy – the proxy instance that the method was invoked on

method – the Method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the Method object will be the interface that the method was declared in, which may be a superinterface of the proxy interface that the proxy class inherits the method through.

args – an array of objects containing the values of the arguments passed in the method invocation on the proxy instance, or null if interface method takes no arguments. Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper class, such as java.lang.Integer or java.lang.Boolean.

Java动态代理 —— 代理类的特性

更多详细的特性介绍请参考Oracle的JDK官方文档

  • 代理类保留以字符串" $ Proxy"开头的类名称的空间,所以当我们看到含"$ Proxy"的报错信息时,很有可能就是代理的错误

The space of class names that begin with the string "$Proxy" should be, however, reserved for proxy classes.

  • 我们可以在代理类上调用getInterface()方法获它的取代理接口,调用getMethods()方法获取所有代理接口的方法,调用getMethod()方法获取指定的代理接口的方法

Since a proxy class implements all of the interfaces specified at its creation, invoking getInterfaces on its Class object will return an array containing the same list of interfaces (in the order specified at its creation), invoking getMethods on its Class object will return an array of Method objects that include all of the methods in those interfaces, and invoking getMethod will find methods in the proxy interfaces as would be expected.

  • 我们可以通过Proxy.isProxyClass()方法判断一个类是否是代理类

The Proxy.isProxyClass method will return true if it is passed a proxy class-- a class returned by Proxy.getProxyClass or the class of an object returned by Proxy.newProxyInstance-- and false otherwise.

  • 每一个代理类都有一个公共的构造函数接受一个InvocationHandler实例,这也是代理实例和InvocationHandler建立关联的过程

Each proxy class has one public constructor that takes one argument, an implementation of the interface InvocationHandler, to set the invocation handler for a proxy instance.

Java动态代理 —— 代理实例的特性

  • 我们可以通过Proxy.getInvocationHandler()静态方法获得与代理对象相关联的InvocationHandler

The static Proxy.getInvocationHandler method will return the invocation handler associated with the proxy instance passed as its argument.

  • 如前所述,代理对象的方法调用会被转发到invoke方法,包括java.lang.Object类中的hashCode()、equals()和toString()方法( 注意Object的其它方法不会经过invoke() )

An invocation of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above.

The declaring class of the Method object passed to invoke will be java.lang.Object. Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.lang.Object.

Java动态代理 —— 方法调用的注意点

我们可能会碰到,在不同的接口中存在同名方法的情况,此时invoke()方法调用哪个代理接口的方法会按照传递给代理类的代理接口的顺序,即getProxyClass()方法内的接口的顺序,而不是实现类继承接口的顺序

when a duplicate method is invoked on a proxy instance, the Method object for the method in the foremost interface that contains the method (either directly or inherited through a superinterface) in the proxy class's list of interfaces is passed to the invocation handler's invoke method, regardless of the reference type through which the method invocation occurred.

例如我们现在有两个接口,他们都有相同的方法

public interface FirstInterface {

  String sayHello();
}

public interface SecondInterface {

  String sayHello();
}

实现类的implements顺序为SecondInterface,FirstInterface

public class TwoInterfaceImpl implements SecondInterface, FirstInterface {

  @Override

  public String sayHello() {

    return "hello world!";
  }
}

下面是测试方法

@Test
public void helloProxy2() throws Exception {

  Class<?> proxyClass = Proxy.getProxyClass(

    SecondInterface.class.getClassLoader(),

    FirstInterface.class, SecondInterface.class);

  InvocationHandler handler = new InvocationHandler() {

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

      System.out.println(method.getDeclaringClass().getName());

      return method.invoke(new TwoInterfaceImpl(), args);
    }
  };

  SecondInterface foo = (SecondInterface) proxyClass.getConstructor(InvocationHandler.class)
                                                  .newInstance(handler);

  System.out.println(foo.sayHello());
}

输出结果为

特别的,对于hashCode(),equals()和toString()方法,他们的"代理接口"为java.lang.Object

If a proxy interface contains a method with the same name and parameter signature as the hashCode, equals, or toString methods of java.lang.Object, when such a method is invoked on a proxy instance, the Method object passed to the invocation handler will have java.lang.Object as its declaring class.

CGLib代理 —— Enhancer增强处理

Java的动态代理要求实实现类有对应的接口,而CGLib代理不要求一定有接口

如有一个服务实现类PersonService

public class PersonService{

  public String sayHello(String name) {

    return "hello " + name;
  }

  public int lengthOfName(String name) {

    return name.length();
  }
}

我们可以通过Enhancer拦截方法调用

@Test
public void helloProxy2() {

  Enhancer enhancer = new Enhancer();

  enhancer.setSuperclass(PersonService.class);
  enhancer.setCallback(new MethodInterceptor() {

    @Override

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

      if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {

        return "hello Tom!";
      } else {

        return proxy.invokeSuper(obj, args);
      }
    }
  });

  PersonService proxy = (PersonService) enhancer.create();

  assertEquals("hello Tom!", proxy.sayHello(null));

  assertEquals(4, proxy.lengthOfName("Mary"));
}

如果被调用方法的声明类不是Object并且返回类型是String,则返回"hello Tom!",否则正常调用lengthOfName()方法

CGLib代理 —— FastClass

CGLib既然能够应对没有接口的情况,自然也能应对存在接口时的情况,我们为上面的PersonService抽取一个接口

public interface PersonInterface {

  String sayHello(String name);

  int lengthOfName(String name);
}

下面的写法,我们已经在上一篇RPC框架的文章中已经有了初步认识

@Test
public void FastTest() throws InvocationTargetException {

  FastClass fastClass = FastClass.create(PersonInterface.class);

  FastMethod fastMethod = fastClass.getMethod("sayHello", new Class<?>[]{String.class});

  String res = (String) fastMethod.invoke(new PersonService(), new Object[]{"Tom"});

  assertEquals("hello Tom", res);
}

CGLib代理 —— Bean Creator

我们可以根据自己的需要生成一个临时的Bean实例,向Bena中添加的字段会自动生成setter()和getter()方法

@Test
public void BeanCreateTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

  BeanGenerator generator = new BeanGenerator();

  generator.addProperty("name", String.class);

  Object myBean = generator.create();

  Method setter = myBean.getClass().getMethod("setName", String.class);

  setter.invoke(myBean, "Mary");

  Method getter = myBean.getClass().getMethod("getName");

  assertEquals("Mary", getter.invoke(myBean));
}

CGLib —— Mixin

我们可以根据需要,将多个接口的功能混合到一个的接口中,但是要求每个接口都有它的实现类

第一个接口

public interface FirstInterface {

  String getFirst();
}

第一个接口的实现类如下

public class FirstImpl implements FirstInterface {

  @Override

  public String getFirst() {

    return "first behavior";
  }
}

然后是第二个接口

public interface SecondInterface {

  String getSecond();
}

第二个接口的实现类如下

public class SecondImpl implements SecondInterface {

  @Override

  public String getSecond() {

    return "second behavior";
  }
}

创建一个新的接口将上面两个接口进行组合

public interface MixInterface extends FirstInterface, SecondInterface {
}

最后设置代理,我们可以看到MixInterface的代理实例同时具备了两个接口的实现类的功能

@Test
public void MixinTest() {

  Mixin mixin = Mixin.create(

    new Class<?>[]{FirstInterface.class, SecondInterface.class, MixInterface.class},

    new Object[]{new FirstImpl(), new SecondImpl()}
  );

  MixInterface mixInterface = (MixInterface) mixin;

  assertEquals("first behavior", mixInterface.getFirst());

  assertEquals("second behavior", mixInterface.getSecond());
}

Java动态代理和CGLib代理的更多相关文章

  1. Spring中AOP的两种代理方式(Java动态代理和CGLIB代理)

    第一种代理即Java的动态代理方式上一篇已经分析,在这里不再介绍,现在我们先来了解下GCLIB代理是什么?它又是怎样实现的?和Java动态代理有什么区别? cglib(Code Generation ...

  2. Spring中AOP的两种代理方式(Java动态代理和CGLIB代理-转载

    内容是摘抄的,不知最初的原作者,见谅 Java 动态代理.具体有如下四步骤: 通过实现 InvocationHandler 接口创建自己的调用处理器: 通过为 Proxy 类指定 ClassLoade ...

  3. java面试-java动态代理和cglib代理

      代理模式就是为了提供额外或不同的操作,而插入的用来替代实际对象的对象,这些操作涉及到与实际对象的通信,因此代理通常充当中间人角色 一.java动态代理   java动态代理可以动态地创建代理并动态 ...

  4. SpringAOP-JDK 动态代理和 CGLIB 代理

    在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类. 1.JDK 动态代理 那么接 ...

  5. 静态代理、动态代理和cglib代理

    转:https://www.cnblogs.com/cenyu/p/6289209.html 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处 ...

  6. 设计模式---JDK动态代理和CGLIB代理

    Cglig代理设计模式 /*测试类*/ package cglibProxy; import org.junit.Test; public class TestCglib { @Test public ...

  7. JDK动态代理和 CGLIB 代理

    JDK动态代理和 CGLIB 代理 JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期期间创建一个接口的实现类来完成对目标对象的代理. 代码示例 接口 public interface ...

  8. Java三种代理模式:静态代理、动态代理和cglib代理

    一.代理模式介绍 代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能. 简言之,代理模式就是 ...

  9. JDK动态代理和CGLIB代理的区别

    一.原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件 ...

随机推荐

  1. C# new操作符的作用

    CLR要求所有对象(实例)都用new操作符创建,那么new操作符做了哪些事呢?1. 计算字节数    计算类型及其所有基类型(父类)中定义的所有实例字段需要的字节数.堆上每个对象都需要一些额外的成员, ...

  2. C#如何在安全的上下文中使用不安全的代码?

    文章原文:https://www.cnblogs.com/2Yous/p/4887904.html 从通常情况下来看,为了保持类型安全,默认情况C# 不支持指针算法. 不过,当你需要使用指针的时候,请 ...

  3. P4-可编程语言代码学习

    (1).behavioral-model 简称bmv2 P4程序首先经过p4c-bm模块编译成JSON格式的配置文件,然后将配置文件载入到bmv2中,转化成能实现交换机功能的数据结构. behavio ...

  4. GAN实战笔记——第六章渐进式增长生成对抗网络(PGGAN)

    渐进式增长生成对抗网络(PGGAN) 使用 TensorFlow和 TensorFlow Hub( TFHUB)构建渐进式增长生成对抗网络( Progressive GAN, PGGAN或 PROGA ...

  5. CV之各种不熟悉但比较重要的笔记

    解析: skip connection 就是一种跳跃式传递.在ResNet中引入了一种叫residual network残差网络结构,其和普通的CNN的区别在于从输入源直接向输出源多连接了一条传递线, ...

  6. JAVA 异常和异常处理

    目录 一.异常 1.基本概念 2.异常体系图 3.五大运行时异常 4.编译异常 二.异常处理 1.异常处理的方式 1.1try-catch异常处理 注意事项 课堂练习题 1.2throws异常处理 注 ...

  7. 调用Visual Studio的cl.exe编译C/C++程序

    @ 目录 调用Visual Studio的cl.exe编译C/C++程序 前言 1.查看VS的路径 2.添加环境变量 3.查看设置是否生效 4.配置Notepad++ 调用Visual Studio的 ...

  8. 当我们看到phpinfo时在谈论什么

    我们在渗透测试的过程中,如果存在phpinfo界面,我们会想到什么? 大部分内容摘抄自:https://www.k0rz3n.com/2019/02/12/PHPINFO 中的重要信息/ 关于phpi ...

  9. 能动的电脑配件「GitHub 热点速览 v.22.11」

    看到这个标题就知道硬核的 B 站 UP 主稚晖君又更新了,本次带来的是一个造型可爱的小机器人.除了稚晖君这个一贯硬核的软硬件项目之外,本周也有很多有意思的新项目,像 Linux 服务监控小工具 Ray ...

  10. 从零开始,开发一个 Web Office 套件(11):支持中文输入法(or 其它使用输入法的语言)

    这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office 套件(包括:文档.表格.幻灯片--等等). 博客园:<从零开始, 开发一 ...