阿里畢玄-測試Java編程能力-我的回答(二)

畢玄老師發表了一篇公眾號文章:來測試下你的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直接生成了代理對象。其基本原理還是和靜態代理一樣的:

  1. Proxy在生成代理對象之前會先動態創建一個實現HelloWorldService接口的代理類(使用反射直接在內存中創建代理類的class文件並加載到虛擬機中)。
  2. 動態創建的代理類中的sayhi方法通過調用ProfilerInvocation的invode方法,也是委託給真正的HelloWorldServiceImpl去執行。
  3. 和靜態代理的區別是代理類是動態生成的,所以叫動態代理。

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中可以說應用非常廣泛,如:

  1. Transaction事務註解
  2. Cache緩解註解
  3. 其它aop

所以針對性能優化的場景,還可以添加一個叫Profiler的註解,然後在有需要統計執行耗時的方法上加上註解,如果想靈活控制開關,可以再添加一個配置項,按需全局開啟關閉profiler,這樣是最方便的。

源代碼

https://github.com/huangyemin/javatest
https://gitee.com/huangyemin/javatest


分享到:


相關文章: