You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

一、为什么需要用代理

案例

!Snipaste_2023-02-15_10-06-15.png

!Snipaste_2023-02-15_10-06-55.png

!Snipaste_2023-02-15_10-07-17.png

!Snipaste_2023-02-15_10-08-10.png

突然领导有个需求调用IService接口中的任何方法的时候需要记录方法的 耗时。此时你会怎么做呢? IService接口有2个实现类ServiceA和ServiceB我们可以在这两个类的所有方法中加上统计耗时的代码如果IService接口有几十个实现是不是要修改很多代码所有被修改的方法需重新测试是不是非常痛苦不过上面这种修改代码的方式倒是可以解决问题只是增加了很多工作量编码 & 测试)。 突然有一天,领导又说,要将这些耗时统计发送到监控系统用来做监控报警使用。 此时是不是又要去一个修改上面的代码?又要去测试?此时的系统是难以维护。 还有假如上面这些类都是第三方以jar包的方式提供给我们的此时这些类都是class文件此时我们无法去修改源码。 比较好的方式可以为IService接口创建一个代理类通过这个代理类来间接访问IService接口的实现类在这个代理类中去做耗时及发送至监控的代码

!Snipaste_2023-02-15_10-10-37.png

!Snipaste_2023-02-15_10-11-39.png

二、JDK动态代理

jdk中为实现代理提供了支持主要用到2个类 java.lang.reflect.Proxy java.lang.reflect.InvocationHandler jdk自带的代理使用上面有个限制只能为接口创建代理类如果需要给具体的类创建代理类需要用cglib

Proxy

1.getProxyClass方法

为指定的接口创建代理类返回代理类的Class对象 !Snipaste_2023-02-15_10-19-06.png

  • loader定义代理类的类加载器
  • interfaces指定需要实现的接口列表创建的代理默认会按顺序实现interfaces指定的接口

2.newProxyInstance方法

创建代理类的实例对象 !Snipaste_2023-02-15_10-22-47.png 这个方法先为指定的接口创建代理类然后会生成代理类的一个实例最后一个参数比较特殊是InvocationHandler类型的

3.isProxy方法

!Snipaste_2023-02-15_10-31-30.png 判断指定的类是否是一个代理类

4.getInvocationHandler

!Snipaste_2023-02-15_10-32-38.png 获取代理对象的InvocationHandler对象

InvocationHandler

!Snipaste_2023-02-15_10-24-31.png 方法会返回一个代理对象,当调用代理对象的任何方法的时候,会就被 InvocationHandler 接口的 invoke 方法处理,所以主要代码需要卸载 invoke 方法中

创建代理

!Snipaste_2023-02-15_10-36-30.png

方式一

  1. 调用Proxy.getProxyClass方法获取代理类的Class对象
  2. 使用InvocationHandler接口创建代理类的处理器
  3. 通过代理类和InvocationHandler创建代理对象
  4. 上面已经创建好代理对象了,接着我们就可以使用代理对象了 !Snipaste_2023-02-15_10-36-52.png

方式二

  1. 使用InvocationHandler接口创建代理类的处理器
  2. 使用Proxy类的静态方法newProxyInstance直接创建代理对象
  3. 使用代理对象 !Snipaste_2023-02-15_10-37-42.png

案例改造

任意接口中的方法耗时统计通过jdk动态代理实现一个通用的代理解决统计所有接口方法耗时的问题。 !Snipaste_2023-02-15_10-42-10.png

!Snipaste_2023-02-15_10-43-07.png

三、cglib代理

什么是cglib

cglib是一个强大、高性能的字节码生成库它用于在运行时扩展Java类和实现接口本质上它是通过动态的生成一个子类去覆盖所要代理的类非final修饰的类和方法。Enhancer可能是CGLIB中最常用的一个类和jdk中的Proxy不同的是Enhancer既能够代理普通的class也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用包括从Object中继承的toString和hashCode方法。Enhancer不能够拦截final方法例如Object.getClass()方法这是由于Java final方法语义决定的。基于同样的道理Enhancer也不能对final类进行代理操作。

cglib组成结构

CGLIB底层使用了ASM一个短小精悍的字节码操作框架来操作字节码生成新的类。除了CGLIB库外脚本语言如Groovy和BeanShell也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM因为它需要对Java字节码的格式足够的了解。 spring已将第三方cglib jar包中所有的类集成到spring自己的jar包中。

基础使用

!Snipaste_2023-02-15_11-12-10.png

!Snipaste_2023-02-15_11-13-18.png !Snipaste_2023-02-15_11-13-38.png 上面代码中的注释很详细,列出了给指定的类创建代理的具体步骤,整个过程中主要用到了 Enhancer类和 MethodInterceptor 接口。 enhancer.setSuperclass 用来设置代理类的父类即需要给哪个类创建代理类此处是Service1。 enhancer.setCallback 传递的是 MethodInterceptor 接口类型的参数, MethodInterceptor 接口有个 intercept 方法,这个方法会拦截代理对象所有的方法调用。 还有一个重点是 Object result = methodProxy.invokeSuper(o, objects); 可以调用被代理 类也就是Service1类中的具体的方法从方法名称的意思可以看出是调用父类实际对某个类创建代理cglib底层通过修改字节码的方式为Service1类创建了一个子类。

实现通用的统计任意类方法耗时代理类

!Snipaste_2023-02-15_11-15-46.png !Snipaste_2023-02-15_11-16-10.png

!Snipaste_2023-02-15_11-16-39.png

四、CGLIB和JAVA动态代理的区别

  1. java动态代理只能对接口进行代理不能对普通类进行代理因为所有生成的代理类的父类为ProxyJava类继承机制不允许多重继承cglib能够代理普通类
  2. java动态代理使用Java原生的反射API进行操作在生成类比较高效cglib使用asm框架直接对字节码进行操作在类的执行过程中比较高效