指尖代码 潜水
  • 6发帖数
  • 6主题数
  • 0关注数
  • 0粉丝
开启左侧

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

[复制链接]
指尖代码 发表于 2021-9-18 00:00:00 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
在Java中动态代理黑白常重要也黑白常有效的一个技术点,如果没有动态代理技术几乎也就不会有各种优秀框架的出现,包括Spring。 其着实动态代理的利用中,除了我们平常用的Spring还有许多中间件和服务都用了动态代理,例如;

  • RPC通信框架Dubbo,在通信的时候由服务端提供一个接口描述信息的Jar,调用端进行引用,之后在调用端引用后生成了对应的代理类,当实行方法调用的时候,实际需要走到代理类向服务提供端发送请求信息,直至内容回传。
  • 别的在利用Mybatis时候可以知道只需要定义一个接口,不需要实现详细方法就可以调用到Mapper中定义的数据库操作信息了。这样极大地简化了代码的开发,又加强了服从。
代理方式

动态代理可以利用Jdk方式也可以利用CGLB,他们的区别,如下;
类型
机制
回调方式
适用场景
服从
JDK
委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法
反射
目标类是接口类
服从瓶颈在反射调用稍慢
CGLIB
继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法实行原始逻辑
通过FastClass方法索引调用
非接口类,非final类,非final方法
第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switch case过多其服从还需测试
案例工程

itstack-demo-test└── src    ├── main    │   └── java    │       └── org.itstack.demo    │           ├── proxy    │           │        └── cglib    │           │            └── CglibProxy.java    │           ├── jdk            │           │        ├── reflect    │           │        │   ├── JDKInvocationHandler.java    │           │        │   └── JDKProxy.java                    │           │   └── util    │           │            └── ClassLoaderUtils.java            │           └── service    │                   ├── IUserService.java    │                   └── UserService.java            └── test        └── java            └── org.itstack.demo.test                └── ApiTest.java基础接口和方法便于验证

service/IUserService.java
public interface IUserService {    String queryUserNameById(String userId);}
service/UserService.java
public class UserService implements IUserService {    public String queryUserNameById(String userId) {        return "hi user " + userId;    }}JDK动态代理

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


  • 实现InvocationHandler.invoke,用于方法加强{监控、实行其他业务逻辑、远程调用等}
  • 如果有需要额外的参数可以提供构造方法
public class JDKInvocationHandler implements InvocationHandler {    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println(method.getName());        return "我被JDKProxy代理了";    }}
reflect/JDKProxy.java & 定义一个代理类获取的服务


  • Proxy.newProxyInstance 来实际生成代理类,过程如下;
  • Class cl = getProxyClass0(loader, intfs); 查找或生成指定的代理类
  • proxyClassCache.get(loader, interfaces); 代理类的缓存中获取
  • subKeyFactory.apply(key, parameter) 继续下一层
  • byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 生成代理类的字节码
public class JDKProxy {    public static  T getProxy(Class interfaceClass) throws Exception {        InvocationHandler handler = new JDKInvocationHandler();        ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader();        T result = (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, handler);        return result;    }}
ApiTest.test_proxy_jdk() & 实行调用并输出反射类的字节码


  • 代理后调用方法验证
  • 通过利用ProxyGenerator.generateProxyClass获取实际的字节码,查看代理类的内容
@Testpublic void test_proxy_jdk() throws Exception {        IUserService proxy = (IUserService) JDKProxy.getProxy(ClassLoaderUtils.forName("org.itstack.demo.service.IUserService"));        String userName = proxy.queryUserNameById("10001");        System.out.println(userName);        String name = "ProxyUserService";        byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{IUserService.class});        // 输出类字节码        FileOutputStream out = null;        try {                out = new FileOutputStream(name + ".class");                System.out.println((new File("")).getAbsolutePath());                out.write(data);        } catch (FileNotFoundException e) {                e.printStackTrace();        } catch (IOException e) {                e.printStackTrace();        } finally {                if (null != out) try {                        out.close();                } catch (IOException e) {                        e.printStackTrace();                }        }}输出结果
queryUserNameById我被JDKProxy代理了将生成的代理类进行反编译jd-gui
部分内容抽取,可以看到比较核心的方法,也就是我们在调用的时候走到了这里
public final String queryUserNameById(String paramString) throws {    try {      return (String)this.h.invoke(this, m3, new Object[] { paramString });    } catch (Error|RuntimeException localError) {      throw localError;    } catch (Throwable localThrowable) {      throw new UndeclaredThrowableException(localThrowable);    }}  static {    try {      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      m3 = Class.forName("org.itstack.demo.service.IUserService").getMethod("queryUserNameById", new Class[] { Class.forName("java.lang.String") });      return;    } catch (NoSuchMethodException localNoSuchMethodException) {      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());    } catch (ClassNotFoundException localClassNotFoundException) {      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());    }}CGLIB动态代理

cglib/CglibProxy.java


  • 提供构造方法,生成CGLIB的代理类,回调this
  • intercept可以进行方法的加强,处置惩罚相关业务逻辑
  • CGLIB是通过ASM来操作字节码生成类
public class CglibProxy implements MethodInterceptor {    public Object newInstall(Object object) {        return Enhancer.create(object.getClass(), this);    }    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {        System.out.println("我被CglibProxy代理了");        return methodProxy.invokeSuper(o, objects);    }}
ApiTest.test_proxy_cglib() & 调用代理类
@Testpublic void test_proxy_cglib() {    CglibProxy cglibProxy = new CglibProxy();    UserService userService = (UserService) cglibProxy.newInstall(new UserService());    String userName = userService.queryUserNameById("10001");    System.out.println(userName);}输出结果
我被CglibProxy代理了hi user 10001综上总结


  • 在我们实际利用中两种方式都用所有利用,也可以依照不同的诉求进行选择
  • 往往动态代剖析和注解共同利用,代理类拿到以后获取方法的注解,并做相应的业务操作
  • 有时候你是否会遇到增加AOP不生效,因为有时候有些类是被代理操作的,并没有实行你的自定义注解也就是切面
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

猜你喜欢
在线客服邮箱
wxcy#wkgb.net

邮箱地址#换为@

Powered by 创意电子 ©2018-现在 专注资源实战分享源码下载站联盟商城