畢玄老師發表了一篇公眾號文章:來測試下你的Java編程能力,本系列文章為其中問題的個人解答。
第四個問題:
CGLib和Java的動態代理相比,具體有什麼不同?
還是從簡單的開始。
性能優化的場景
假設我們的代碼寫完後,發現性能很差,現在需要進行優化,優化之前需要得到代碼中的方法執行耗時,用於輔助分析性能瓶頸。當然我們可以用Jprofiler等工具來可視化分析,這裡我們暫且用最原始的方法,就是在代碼中打印方法的執行耗時,相信很多人都這樣幹過。
簡單粗暴直接法
package com.xetlab.javatest.question2; import java.util.Random; public class HelloWorldServiceImpl implements HelloWorldService { public void sayhi() { long start = System.currentTimeMillis(); try { Thread.sleep(new Random().nextInt(5000)); } catch (InterruptedException e) { } System.out.println("hello world"); long end = System.currentTimeMillis(); System.out.println("consume:" + (end - start) + "ms"); } }
乾淨一點的方法
簡單粗暴直接法,雖然快,但添加的代碼在性能優化完成後,如果想刪除時得手動刪除,哪天又出現問題可能又得加回來。下面是乾淨點的方法。
package com.xetlab.javatest.question2; import java.util.Random; public class HelloWorldServiceImpl implements HelloWorldService { public void sayhi() { try { Thread.sleep(new Random().nextInt(5000)); } catch (InterruptedException e) { } System.out.println("hello world"); } } package com.xetlab.javatest.question2; public class JavaStaticProxy { static class ProxyHelloWorldServiceImpl implements HelloWorldService { private HelloWorldService helloWorldService; public ProxyHelloWorldServiceImpl(HelloWorldService helloWorldService) { this.helloWorldService = helloWorldService; } public void sayhi() { long start = System.currentTimeMillis(); helloWorldService.sayhi(); long end = System.currentTimeMillis(); System.out.println("consume:" + (end - start) + "ms"); } } public static void main(String[] args) { HelloWorldService helloWorldService = new ProxyHelloWorldServiceImpl(new HelloWorldServiceImpl()); helloWorldService.sayhi(); } }
這裡更乾淨的方法,HelloWorldServiceImpl只包含了和自己業務相關的代碼,不用擔心代碼被不必要的邏輯汙染,
ProxyHelloWorldServiceImpl實現了和HelloWorldServiceImpl一樣的接口,但是
ProxyHelloWorldServiceImpl的sayhi方法執行的時候並沒執行實際的sayhi邏輯,而是把sayhi邏輯委託給HelloWorldServiceImpl去執行,同時在方法執行前後加上了方法耗時統計的代碼。這個就是代理了,具體來說上面的方法實現了代理模式,是靜態代理。
使用代理的優點是:
可以在不改變原有類的情況下,對類的功能進行擴展,如果原有類是第三方庫中的,不能直接修改,就可以通過這種方式來擴展功能。
更進一步
目前我們的靜態代理,只能分析HelloWorldServiceImpl中的sayhi方法的執行耗時,能不能更通用一點可以應用到別的類呢,Java自帶的動態代理就可以達到目的。
package com.xetlab.javatest.question2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JavaDynamicProxy {
static class ProfilerInvocation implements InvocationHandler {
private T target;
public ProfilerInvocation(T target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object result;
try {
result = method.invoke(target, args);
} finally {
long end = System.currentTimeMillis();
System.out.println("consume:" + (end - start) + "ms");
}
return result;
}
}
public static void main(String[] args) {
HelloWorldService helloWorldService = (HelloWorldService) Proxy.newProxyInstance(HelloWorldServiceImpl.class.getClassLoader(),
new Class[]{HelloWorldService.class},
new ProfilerInvocation(new HelloWorldServiceImpl()));
helloWorldService.sayhi();
}
}
這次並沒有直接編寫一個代理類,而是編寫了一個實現了InvocationHandler的ProfilerInvocation類(ProfilerInvocation除了可應用於實現了HelloWorldService的類中,也可以用在別的實現了接口的類中),然後利用Java中的動態代理Proxy直接生成了代理對象。其基本原理還是和靜態代理一樣的:
- Proxy在生成代理對象之前會先動態創建一個實現HelloWorldService接口的代理類(使用反射直接在內存中創建代理類的class文件並加載到虛擬機中)。
- 動態創建的代理類中的sayhi方法通過調用ProfilerInvocation的invode方法,也是委託給真正的HelloWorldServiceImpl去執行。
- 和靜態代理的區別是代理類是動態生成的,所以叫動態代理。
Java的動態代理是用實現接口的方式,只適用於有實現接口的類的代理,如下圖所示:
{% asset_img javaproxy.png Java動態代理 %}
CGLib
如果是普通類的情況,就需要用CGLib了,CGLib是使用繼承的方式實現動態代理,如下圖所示:
{% asset_img cglibproxy.png CGLib動態代理 %}
使用CGLib實現的動態代理代碼:
package com.xetlab.javatest.question2; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxy { static class ProfilerInterceptor implements MethodInterceptor { private T target; public ProfilerInterceptor(T target) { this.target = target; } public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { long start = System.currentTimeMillis(); Object result; try { result = method.invoke(target, objects); } finally { long end = System.currentTimeMillis(); System.out.println("consume:" + (end - start) + "ms"); } return result; } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HelloWorldServiceImpl.class); enhancer.setCallback(new ProfilerInterceptor(new HelloWorldServiceImpl())); HelloWorldService helloWorldService = (HelloWorldServiceImpl) enhancer.create(); helloWorldService.sayhi(); } }
可以看出,MethodInterceptor和InvocationHandler很類似,區別只是在生成代理對象的寫法不一樣,另外CGLib動態生成的代理類是直接繼承被代理類,然後重寫其中的方法(也是在內存中動態生成代理類的class文件,並加載到jvm中)。
動態代理在spring中可以說應用非常廣泛,如:
- Transaction事務註解
- Cache緩解註解
- 其它aop
所以針對性能優化的場景,還可以添加一個叫Profiler的註解,然後在有需要統計執行耗時的方法上加上註解,如果想靈活控制開關,可以再添加一個配置項,按需全局開啟關閉profiler,這樣是最方便的。
源代碼
https://github.com/huangyemin/javatest https://gitee.com/huangyemin/javatest
關鍵字: public implements 性能