乐趣区

关于java:面经手册-第13篇除了JDKCGLIB还有3种类代理方式面试又卡住


作者:小傅哥
博客: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 那样操作字节码导致危险,同时它的性能也十分齐全。另外,这个框架即可应用它所提供的形式间接编写插装代码,也能够应用字节码指令进行管制生成代码,所以综合来看也是一个十分不错的字节码框架。

四、总结

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

五、系列举荐

  • 字节码编程系列专题
  • HashMap 外围常识,扰动函数、负载因子、扩容链表拆分
  • ThreadLocal 技术栈深度学习
  • 握草,你居然在代码里下毒!
退出移动版