做者:小傅哥
博客:https://bugstack.cnhtml
沉澱、分享、成長,讓本身和他人都能有所收穫!😄
編程學習,先鋪寬度仍是挖深度?
java
其實技術寬度與技術深度是相輔相成的,你能瞭解多少技術是和你對一個技術的理解深度有關,而你能對一個技術探究的多深又是須要你有必定的廣度認知。不然若是隻去了解皮毛或者死磕一段代碼,收穫都不必定有多大,或者要付出很大的成本。面試
技術瓶頸,與年齡相關仍是大廠?
數據庫
親身當過面試官好久,也面試過不少人。有時候不必定年齡很大就技術好,也不必定剛工做2年左右就不行。每每咱們說的一些面試造火箭,可是在這些求職者的回答中,都能給出很是準確的答案。也就是他能回答到點上,這是懂了才能作到的。編程
工做時長與是否在大廠,這些都是能接觸到資源的多少,看到技術見識的高度。但真的想把這些東西吸取給本身,仍是須要我的的拼搏。不然不少東西即便擺在你面前,你也很難看到。你能看到的多數時候只是標題設計模式
謝飛機,小記
,10.1假期玩嗨了的飛機,彷佛已經放假前給本身定的學習目標了!但一想到還有一場面試,不禁得臨時抱佛腳,開始看小傅哥的博客:bugstack.cn框架
面試官:飛機,看你慌里慌張的呢?ide
謝飛機:沒有,沒有,剛纔怕來不及跑上樓的。函數
面試官:好!我看你的簡歷也沒更新,那咱們此次聊聊動態代理和反射吧,你瞭解怎麼代理一個類嗎?工具
謝飛機:這個我知道,使用JDK自帶的類Proxy
能夠代理一個類,也可使用CGLIB代理。
面試官:嗯,那這兩個代理有什麼區別呢?
謝飛機:好像一個是JDK的須要有接口,CGLIB的不須要。
面試官:爲何呢?
謝飛機:爲何?這...
面試官:那你本身開發時,用代理作什麼業務嗎?
謝飛機:... 好像也沒有!
飛機只能溜溜的回家了,技術深度不足,也沒有實際應用過,還須要不少補全的內容!
不出意外
,你可能只知道兩種類代理的方式。一種是JDK自帶的,另一種是CGLIB。
咱們先定義出一個接口和相應的實現類,方便後續使用代理類在方法中添加輸出信息。
定義接口
public interface IUserApi { String queryUserInfo(); }
實現接口
public class UserApi implements IUserApi { public String queryUserInfo() { return "小傅哥,公衆號:bugstack蟲洞棧 | 沉澱、分享、成長,讓本身和他人都能有所收穫!"; } }
好!接下來咱們就給這個類方法使用代理加入一行額外輸出的信息。
@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); }
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 */
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 */
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 */
Opcodes.GETSTATIC
、Opcodes.INVOKEVIRTUAL
,除了這些還有小200個經常使用的指令。但這種最接近底層的方式,也是最快的方式。因此在一些使用字節碼插裝的全鏈路監控中,會很是常見。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 */
Byte Buddy
也是一個字節碼操做的類庫,但 Byte Buddy
的使用方式更加簡單。無需理解字節碼指令,便可使用簡單的 API 就能很容易操做字節碼,控制類和方法。比起JDK動態代理、cglib,Byte Buddy在性能上具備必定的優點。另外,2015年10月,Byte Buddy被 Oracle 授予了 Duke's Choice大獎。該獎項對Byte Buddy的「 Java技術方面的巨大創新 」表示讚揚。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 */
Javassist
是一個使用很是廣的字節碼插裝框架,幾乎一大部分非入侵的全鏈路監控都是會選擇使用這個框架。由於它不想ASM那樣操做字節碼致使風險,同時它的功能也很是齊全。另外,這個框架便可使用它所提供的方式直接編寫插裝代碼,也可使用字節碼指令進行控制生成代碼,因此綜合來看也是一個很是不錯的字節碼框架。