有点干货 | JDK、CGLIB动态代理使用以及源码分析

简介: 在Java中动态代理是非常重要也是非常有用的一个技术点,如果没有动态代理技术几乎也就不会有各种优秀框架的出现,包括Spring。其实在动态代理的使用中,除了我们平时用的Spring还有很多中间件和服务都用了动态代理

前言介绍

在Java中动态代理是非常重要也是非常有用的一个技术点,如果没有动态代理技术几乎也就不会有各种优秀框架的出现,包括Spring。

其实在动态代理的使用中,除了我们平时用的Spring还有很多中间件和服务都用了动态代理,例如;

  1. RPC通信框架Dubbo,在通信的时候由服务端提供一个接口描述信息的Jar,调用端进行引用,之后在调用端引用后生成了对应的代理类,当执行方法调用的时候,实际需要走到代理类向服务提供端发送请求信息,直至内容回传。
  2. 另外在使用Mybatis时候可以知道只需要定义一个接口,不需要实现具体方法就可以调用到Mapper中定义的数据库操作信息了。这样极大的简化了代码的开发,又增强了效率。
  3. 最后不知道你自己是否尝试过开发一些基于代理类的框架,以此来优化业务代码。也就是将业务代码中非业务逻辑又通用性的功能抽离出来,开发为独立的组件。推荐个案例,方便知道代理类的应用:手写RPC框架第三章《RPC中间件》

代理方式

动态代理可以使用Jdk方式也可以使用CGLB,他们的区别,如下;

类型 机制 回调方式 适用场景 效率
JDK 委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法 反射 目标类是接口类 效率瓶颈在反射调用稍慢
CGLIB 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 通过FastClass方法索引调用 非接口类,非final类,非final方法 第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switch case过多其效率还需测试

案例工程

1itstack-demo-test
 2└── src
 3    ├── main
 4    │   └── java
 5    │       └── org.itstack.demo
 6    │           ├── proxy
 7    │           │    └── cglib
 8    │           │        └── CglibProxy.java
 9    │           ├── jdk    
10    │           │    ├── reflect
11    │           │    │   ├── JDKInvocationHandler.java
12    │           │    │   └── JDKProxy.java       
13    │           │   └── util
14    │           │        └── ClassLoaderUtils.java   
15    │           └── service
16    │               ├── IUserService.java
17    │               └── UserService.java    
18    └── test
19        └── java
20            └── org.itstack.demo.test
21                └── ApiTest.java

基础接口和方法便于验证

service/IUserService.java

1public interface IUserService {
2
3    String queryUserNameById(String userId);
4
5}

service/UserService.java

1public class UserService implements IUserService {
2
3    public String queryUserNameById(String userId) {
4        return "hi user " + userId;
5    }
6
7}

JDK动态代理

reflect/JDKInvocationHandler.java & 代理类反射调用

  • 实现InvocationHandler.invoke,用于方法增强{监控、执行其他业务逻辑、远程调用等}
  • 如果有需要额外的参数可以提供构造方法
1public class JDKInvocationHandler implements InvocationHandler {
2
3    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
4        System.out.println(method.getName());
5        return "我被JDKProxy代理了";
6    }
7
8}

reflect/JDKProxy.java & 定义一个代理类获取的服务

  • Proxy.newProxyInstance 来实际生成代理类,过程如下;
  1. Class cl = getProxyClass0(loader, intfs); 查找或生成指定的代理类
  2. proxyClassCache.get(loader, interfaces); 代理类的缓存中获取
  3. subKeyFactory.apply(key, parameter) 继续下一层
  4. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 生成代理类的字节码
1public class JDKProxy {
 2
 3    public static <T> T getProxy(Class<T> interfaceClass) throws Exception {
 4        InvocationHandler handler = new JDKInvocationHandler();
 5        ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader();
 6        T result = (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, handler);
 7        return result;
 8    }
 9
10}

ApiTest.test_proxy_jdk() & 执行调用并输出反射类的字节码

  • 代理后调用方法验证
  • 通过使用ProxyGenerator.generateProxyClass获取实际的字节码,查看代理类的内容
1@Test
 2public void test_proxy_jdk() throws Exception {
 3
 4    IUserService proxy = (IUserService) JDKProxy.getProxy(ClassLoaderUtils.forName("org.itstack.demo.service.IUserService"));
 5    String userName = proxy.queryUserNameById("10001");
 6    System.out.println(userName);
 7
 8    String name = "ProxyUserService";
 9    byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{IUserService.class});
10
11    // 输出类字节码
12    FileOutputStream out = null;
13    try {
14        out = new FileOutputStream(name + ".class");
15        System.out.println((new File("")).getAbsolutePath());
16        out.write(data);
17    } catch (FileNotFoundException e) {
18        e.printStackTrace();
19    } catch (IOException e) {
20        e.printStackTrace();
21    } finally {
22        if (null != out) try {
23            out.close();
24        } catch (IOException e) {
25            e.printStackTrace();
26        }
27    }
28
29}

输出结果

1queryUserNameById
2我被JDKProxy代理了

将生成的代理类进行反编译jd-gui

部分内容抽取,可以看到比较核心的方法,也就是我们在调用的时候走到了这里

1public final String queryUserNameById(String paramString)
 2    throws 
 3{
 4try
 5{
 6  return (String)this.h.invoke(this, m3, new Object[] { paramString });
 7}
 8catch (Error|RuntimeException localError)
 9{
10  throw localError;
11}
12catch (Throwable localThrowable)
13{
14  throw new UndeclaredThrowableException(localThrowable);
15}
16}
17
18
19static
20{
21try
22{
23  m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
24  m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
25  m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
26  m3 = Class.forName("org.itstack.demo.service.IUserService").getMethod("queryUserNameById", new Class[] { Class.forName("java.lang.String") });
27  return;
28}
29catch (NoSuchMethodException localNoSuchMethodException)
30{
31  throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
32}
33catch (ClassNotFoundException localClassNotFoundException)
34{
35  throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
36}
37}

CGLIB动态代理

cglib/CglibProxy.java

  • 提供构造方法,生成CGLIB的代理类,回调this
  • intercept可以进行方法的增强,处理相关业务逻辑
  • CGLIB是通过ASM来操作字节码生成类
1public class CglibProxy implements MethodInterceptor {
 2
 3    public Object newInstall(Object object) {
 4        return Enhancer.create(object.getClass(), this);
 5    }
 6
 7    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
 8        System.out.println("我被CglibProxy代理了");
 9        return methodProxy.invokeSuper(o, objects);
10    }
11
12}

ApiTest.test_proxy_cglib() & 调用代理类

1@Test
2public void test_proxy_cglib() {
3    CglibProxy cglibProxy = new CglibProxy();
4    UserService userService = (UserService) cglibProxy.newInstall(new UserService());
5    String userName = userService.queryUserNameById("10001");
6    System.out.println(userName);
7}

输出结果

1我被CglibProxy代理了
2hi user 10001

综上总结

  • 在我们实际使用中两种方式都用所有使用,也可以依照不同的诉求进行选择
  • 往往动态代理会和注解共同使用,代理类拿到以后获取方法的注解,并做相应的业务操作
  • 有时候你是否会遇到增加AOP不生效,因为有时候有些类是被代理操作的,并没有执行你的自定义注解也就是切面
目录
相关文章
|
3月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
22天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
17 0
[Java]静态代理与动态代理(基于JDK1.8)
|
1月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
20 1
|
1月前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
23 1
|
3月前
|
Java API 开发者
Jdk动态代理为啥不能代理Class?
该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。
Jdk动态代理为啥不能代理Class?
|
3月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
212 0
|
3月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
174 0
|
5月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
30 0
|
5月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
40 0
|
2月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
314 3