面经手册 · 第13篇《除了JDK、CGLIB,还有3种类代理方式?面试又卡住!》

bugstack虫洞栈 2020-11-06 01:17:35
经手 手册 jdk cglib 种类


作者:小傅哥
博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

编程学习,先铺宽度还是挖深度?

其实技术宽度与技术深度是相辅相成的,你能了解多少技术是和你对一个技术的理解深度有关,而你能对一个技术探究的多深又是需要你有一定的广度认知。否则如果只去了解皮毛或者死磕一段代码,收获都不一定有多大,或者要付出很大的成本。

技术瓶颈,与年龄相关还是大厂?

亲身当过面试官很久,也面试过很多人。有时候不一定年龄很大就技术好,也不一定刚工作2年左右就不行。往往我们说的一些面试造火箭,但是在这些求职者的回答中,都能给出非常准确的答案。也就是他能回答到点上,这是懂了才能做到的。

工作时长与是否在大厂,这些都是能接触到资源的多少,看到技术见识的高度。但真的想把这些东西吸收给自己,还是需要个人的拼搏。否则很多东西即使摆在你面前,你也很难看到。你能看到的多数时候只是标题

二、面试题

谢飞机,小记,10.1假期玩嗨了的飞机,似乎已经放假前给自己定的学习目标了!但一想到还有一场面试,不由得临时抱佛脚,开始看小傅哥的博客:bugstack.cn

面试官:飞机,看你慌里慌张的呢?

谢飞机:没有,没有,刚才怕来不及跑上楼的。

面试官:好!我看你的简历也没更新,那我们这次聊聊动态代理和反射吧,你了解怎么代理一个类吗?

谢飞机:这个我知道,使用JDK自带的类Proxy可以代理一个类,也可以使用CGLIB代理。

面试官:嗯,那这两个代理有什么区别呢?

谢飞机:好像一个是JDK的需要有接口,CGLIB的不需要。

面试官:为什么呢?

谢飞机:为什么?这…

面试官:那你自己开发时,用代理做什么业务吗?

谢飞机:… 好像也没有!

飞机只能溜溜的回家了,技术深度不足,也没有实际应用过,还需要很多补全的内容!

三、五种类代理的方式

不出意外,你可能只知道两种类代理的方式。一种是JDK自带的,另外一种是CGLIB。

我们先定义出一个接口和相应的实现类,方便后续使用代理类在方法中添加输出信息。

定义接口

public interface IUserApi {
String queryUserInfo();
}

实现接口

public class UserApi implements IUserApi {
public String queryUserInfo() {
return "小傅哥,公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!";
}
}

好!接下来我们就给这个类方法使用代理加入一行额外输出的信息。

0. 先补充一点反射的知识

@Test
public void test_reflect() throws Exception {
Class<UserApi> clazz = UserApi.class;
Method queryUserInfo = clazz.getMethod("queryUserInfo");
Object invoke = queryUserInfo.invoke(clazz.newInstance());
System.out.println(invoke);
}

  • 指数:
  • 点评:有代理地方几乎就会有反射,他们是一套互相配合使用的功能类。在反射中可以调用方法、获取属性、拿到注解等相关内容。这些都可以与接下来的类代理组合使用,完成各种框架中的技术场景。

1. JDK代理方式

public class JDKProxy {
public static <T> T getProxy(Class clazz) throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return (T) Proxy.newProxyInstance(classLoader, new Class[]{clazz}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " 你被代理了,By JDKProxy!");
return "小傅哥,公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!";
}
});
}
}
@Test
public void test_JDKProxy() throws Exception {
IUserApi userApi = JDKProxy.getProxy(IUserApi.class);
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}
/**
* 测试结果:
*
* queryUserInfo 你被代理了,By JDKProxy!
* 19:55:47.319 [main] INFO org.itstack.interview.test.ApiTest - 测试结果:小傅哥,公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!
*
* Process finished with exit code 0
*/

  • 指数:
  • 场景:中间件开发、设计模式中代理模式和装饰器模式应用
  • 点评:这种JDK自带的类代理方式是非常常用的一种,也是非常简单的一种。基本会在一些中间件代码里看到例如:数据库路由组件、Redis组件等,同时我们也可以使用这样的方式应用到设计模式中。

2. CGLIB代理方式

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);
}
}
@Test
public void test_CglibProxy() throws Exception {
CglibProxy cglibProxy = new CglibProxy();
UserApi userApi = (UserApi) cglibProxy.newInstall(new UserApi());
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}
/**
* 测试结果:
*
* queryUserInfo 你被代理了,By CglibProxy!
* 19:55:47.319 [main] INFO org.itstack.interview.test.ApiTest - 测试结果:小傅哥,公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!
*
* Process finished with exit code 0
*/

  • 指数:
  • 场景:Spring、AOP切面、鉴权服务、中间件开发、RPC框架等
  • 点评:CGLIB不同于JDK,它的底层使用ASM字节码框架在类中修改指令码实现代理,所以这种代理方式也就不需要像JDK那样需要接口才能代理。同时得益于字节码框架的使用,所以这种代理方式也会比使用JDK代理的方式快1.5~2.0倍。

3. ASM代理方式

public class ASMProxy extends ClassLoader {
public static <T> T getProxy(Class clazz) throws Exception {
ClassReader classReader = new ClassReader(clazz.getName());
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
classReader.accept(new ClassVisitor(ASM5, classWriter) {
@Override
public MethodVisitor visitMethod(int access, final String name, String descriptor, String signature, String[] exceptions) {
// 方法过滤
if (!"queryUserInfo".equals(name))
return super.visitMethod(access, name, descriptor, signature, exceptions);
final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new AdviceAdapter(ASM5, methodVisitor, access, name, descriptor) {
@Override
protected void onMethodEnter() {
// 执行指令;获取静态属性
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 加载常量 load constant
methodVisitor.visitLdcInsn(name + " 你被代理了,By ASM!");
// 调用方法
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
super.onMethodEnter();
}
};
}
}, ClassReader.EXPAND_FRAMES);
byte[] bytes = classWriter.toByteArray();
return (T) new ASMProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
}
}
@Test
public void test_ASMProxy() throws Exception {
IUserApi userApi = ASMProxy.getProxy(UserApi.class);
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}
/**
* 测试结果:
*
* queryUserInfo 你被代理了,By ASM!
* 20:12:26.791 [main] INFO org.itstack.interview.test.ApiTest - 测试结果:小傅哥,公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!
*
* Process finished with exit code 0
*/

  • 指数:
  • 场景:全链路监控、破解工具包、CGLIB、Spring获取类元数据等
  • 点评:这种代理就是使用字节码编程的方式进行处理,它的实现方式相对复杂,而且需要了解Java虚拟机规范相关的知识。因为你的每一步代理操作,都是在操作字节码指令,例如:Opcodes.GETSTATICOpcodes.INVOKEVIRTUAL,除了这些还有小200个常用的指令。但这种最接近底层的方式,也是最快的方式。所以在一些使用字节码插装的全链路监控中,会非常常见。

4. Byte-Buddy代理方式

public class ByteBuddyProxy {
public static <T> T getProxy(Class clazz) throws Exception {
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(clazz)
.method(ElementMatchers.<MethodDescription>named("queryUserInfo"))
.intercept(MethodDelegation.to(InvocationHandler.class))
.make();
return (T) dynamicType.load(Thread.currentThread().getContextClassLoader()).getLoaded().newInstance();
}
}
@RuntimeType
public static Object intercept(@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable) throws Exception {
System.out.println(method.getName() + " 你被代理了,By Byte-Buddy!");
return callable.call();
}
@Test
public void test_ByteBuddyProxy() throws Exception {
IUserApi userApi = ByteBuddyProxy.getProxy(UserApi.class);
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}
/**
* 测试结果:
*
* queryUserInfo 你被代理了,By Byte-Buddy!
* 20:19:44.498 [main] INFO org.itstack.interview.test.ApiTest - 测试结果:小傅哥,公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!
*
* Process finished with exit code 0
*/

  • 指数:
  • 场景:AOP切面、类代理、组件、监控、日志
  • 点评:Byte Buddy 也是一个字节码操作的类库,但 Byte Buddy 的使用方式更加简单。无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。比起JDK动态代理、cglib,Byte Buddy在性能上具有一定的优势。另外,2015年10月,Byte Buddy被 Oracle 授予了 Duke’s Choice大奖。该奖项对Byte Buddy的“ Java技术方面的巨大创新 ”表示赞赏。

5. Javassist代理方式

public class JavassistProxy extends ClassLoader {
public static <T> T getProxy(Class clazz) throws Exception {
ClassPool pool = ClassPool.getDefault();
// 获取类
CtClass ctClass = pool.get(clazz.getName());
// 获取方法
CtMethod ctMethod = ctClass.getDeclaredMethod("queryUserInfo");
// 方法前加强
ctMethod.insertBefore("{System.out.println(\"" + ctMethod.getName() + " 你被代理了,By Javassist\");}");
byte[] bytes = ctClass.toBytecode();
return (T) new JavassistProxy().defineClass(clazz.getName(), bytes, 0, bytes.length).newInstance();
}
}
@Test
public void test_JavassistProxy() throws Exception {
IUserApi userApi = JavassistProxy.getProxy(UserApi.class)
String invoke = userApi.queryUserInfo();
logger.info("测试结果:{}", invoke);
}
/**
* 测试结果:
*
* queryUserInfo 你被代理了,By Javassist
* 20:23:39.139 [main] INFO org.itstack.interview.test.ApiTest - 测试结果:小傅哥,公众号:bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获!
*
* Process finished with exit code 0
*/

  • 指数:
  • 场景:全链路监控、类代理、AOP
  • 点评:Javassist 是一个使用非常广的字节码插装框架,几乎一大部分非入侵的全链路监控都是会选择使用这个框架。因为它不想ASM那样操作字节码导致风险,同时它的功能也非常齐全。另外,这个框架即可使用它所提供的方式直接编写插装代码,也可以使用字节码指令进行控制生成代码,所以综合来看也是一个非常不错的字节码框架。

四、总结

  • 代理的实际目的就是通过一些技术手段,替换掉原有的实现类或者给原有的实现类注入新的字节码指令。而这些技术最终都会用到一些框架应用、中间件开发以及类似非入侵的全链路监控中。
  • 一个技术栈深度的学习能让你透彻的了解到一些基本的根本原理,通过这样的学习可以解惑掉一些似懂非懂的疑问,也可以通过这样技术的拓展让自己有更好的工作机会和薪资待遇。
  • 这些技术学起来并不会很容易,甚至可能还有一些烧脑。但每一段值得深入学习的技术都能帮助你突破一定阶段的技术瓶颈。

五、系列推荐

(转载本站文章请注明作者和出处 微信公众号:bugstack虫洞栈 | 作者:小傅哥

Show Disqus Comments
版权声明
本文为[bugstack虫洞栈]所创,转载请带上原文链接,感谢
https://bugstack.cn/interview/2020/10/14/%E9%9D%A2%E7%BB%8F%E6%89%8B%E5%86%8C-%E7%AC%AC13%E7%AF%87-%E9%99%A4%E4%BA%86JDK-CGLIB-%E8%BF%98%E6%9C%893%E7%A7%8D%E7%B1%BB%E4%BB%A3%E7%90%86%E6%96%B9%E5%BC%8F-%E9%9D%A2%E8%AF%95%E5%8F%88%E5%8D%A1%E4%BD%8F.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云