引言
Java 動態代理機制的出現,使得 Java 開發人員不用手工編寫代理類,只要簡單地指定一組接口及委託類對象,便能動態地獲得代理類。代理類會負責將所有的方法調用分派到委託對象上反射執行,在分派執行的過程中,開發人員還可以按需調整委託類對象及其功能,這是一套非常靈活有彈性的代理框架。通過閱讀本文,讀者將會對 Java 動態代理機制有更加深入的理解。本文首先從 Java 動態代理的運行機制和特點出發,對其代碼進行了分析,推演了動態生成類的內部實現。
代理:設計模式
代理模式是Java常見的設計模式之一。所謂代理模式是指客戶端並不直接調用實際的對象,而是通過調用代理,來間接的調用實際的對象。
為什麼要採用這種間接的形式來調用對象呢?一般是因為客戶端不想直接訪問實際的對象,或者訪問實際的對象存在困難,因此通過一個代理對象來完成間接的訪問。
在現實生活中,這種情形非常的常見,比如請一個律師代理來打官司。
![詳細解讀JAVA動態代理](http://p2.ttnews.xyz/loading.gif)
從UML圖中,可以看出代理類與真正實現的類都是繼承了抽象的主題類,這樣的好處在於代理類可以與實際的類有相同的方法,可以保證客戶端使用的透明性。
代理類實現:靜態代理
代理模式的實現方式有兩種,分別是靜態代理和動態代理,我們先看靜態代理的實現;
針對上圖中的UML圖,我們看下Subject 接口的code:
public interface Subject {
void visit();
}
RealSubject 的實現:
1public class RealSubject implements Subject{
2 @Override
3 public void visit() {
4 System.out.println("real visit....");
5 }
6}
ProxySubject 代理類的實現:
1public class ProxySubject implements Subject{
2
3 private Subject subject;
4
5 public ProxySubject(Subject subject){
6 this.subject = subject;
7 }
8
9 @Override
10 public void visit() {
11 System.out.println("before......");
12 subject.visit();
13 System.out.println("end.........");
14 }
15}
Client的實現:
1public class Client {
2 public static void main(String[] args) {
3 Subject realSubject = new RealSubject();
4 ProxySubject proxySubject = new ProxySubject(realSubject);
5 proxySubject.visit();
6 }
7}
執行結果如下:
1before......
2real visit....
3end.........
為了保持行為的一致性,代理類和委託類通常會實現相同的接口,所以在訪問者看來兩者沒有絲毫的區別。通過代理類這中間一層,能有效控制對委託類對象的直接訪問,也可以很好地隱藏和保護委託類對象,同時也為實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。靜態代理的缺點也很明顯,如果需要代理若干Subject,那麼就需要implement若干Subject接口方法,而Java 動態代理機制以巧妙的方式近乎完美地實踐了代理模式的設計理念。
代理實現:JAVA動態代理
動態代理有別於靜態代理,是根據代理的對象,動態創建代理類。這樣,就可以避免靜態代理中代理類接口過多的問題。動態代理是實現方式,是通過反射來實現的,藉助Java自帶的java.lang.reflect.Proxy,通過固定的規則生成。
其步驟如下:
- 編寫一個委託類的接口,即靜態代理的(Subject接口)
- 實現一個真正的委託類,即靜態代理的(RealSubject類)
- 創建一個動態代理類,實現InvocationHandler接口,並重寫該invoke方法
- 在測試類中,生成動態代理的對象。
- 第一二步驟,和靜態代理一樣。我們主要看第三步,代碼如下:
1public class JavaProxy implements InvocationHandler {
2
3 private Object object;
4
5 public JavaProxy(Object object) {
6 this.object = object;
7 }
8
9 @Override
10 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
11 System.out.println("before >>>>>>>>");
12 Object result = method.invoke(object, args);
13 System.out.println("end >>>>>>>>>>>");
14 return result;
15 }
16}
第四步,創建動態代理的對象
1 Subject realSubject = new RealSubject();
2 JavaProxy proxy = new JavaProxy(realSubject);
3 ClassLoader classLoader = realSubject.getClass().getClassLoader();
4 Subject subject = (Subject) Proxy.newProxyInstance(classLoader,new Class[]{Subject.class},proxy);
5 subject.visit();
運行結果如下:
1before >>>>>>>>
2real visit....
3end >>>>>>>>>>>
創建動態代理的對象,需要藉助Proxy.newProxyInstance。該方法的三個參數分別是:
- ClassLoader loader表示當前使用到的appClassloader。
- Class[] interfaces表示目標對象實現的一組接口。
- InvocationHandler h表示當前的InvocationHandler實現實例對象。
java 自帶的動態代理為什麼一定要實現接口
java自帶的動態代理為什麼一定要實現接口,其實這個問題也一直困擾著我,我們從創建代理函數看起,即
1public static Object newProxyInstance(ClassLoader loader,
2 Class>[] interfaces, InvocationHandler h)
3 throws IllegalArgumentException
通過源碼可以看到,這個類第一步生成一個代理類(注意,這裡的參數就是接口列表),
1Class cl = getProxyClass(loader, interfaces);
然後通過代理類找到構造參數為InvocationHandler的構造函數並生成一個新類。
1 final Constructor> cons = cl.getConstructor(constructorParams);
2 final InvocationHandler ih = h;
3if (!Modifier.isPublic(cl.getModifiers())) {
4 AccessController.doPrivileged(new PrivilegedAction
5 public Void run() {
6 cons.setAccessible(true);
7 return null;
8 }
9 });
10 }
11 return cons.newInstance(new Object[]{ h });
接口起什麼作用呢,於是又看getProxyClass方法的代碼,這個源碼很長,就不細說了。大致分為三段:
第一:驗證
第二:緩存創建新類的結構,如果創建過,則直接返回。(注意:這裡的KEY就是接口列表)
第三:如果沒有創建過,則創建新類
創建代碼的過程代碼如下:
1 /*
2 * //獲得代理類數字標識
3 */
4 long num = nextUniqueNumber.getAndIncrement();
5 String proxyName = proxyPkg + proxyClassNamePrefix + num;
6
7 /*
8 * 調用class處理文件生成類的字節碼,根據接口列表創建一個新類,這個類為代理類,
9
10 */
11 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
12 proxyName, interfaces, accessFlags);
13 try {
14//通過JNI接口,將Class字節碼文件定義一個新類
15 return defineClass0(loader, proxyName,
16 proxyClassFile, 0, proxyClassFile.length);
17 } catch (ClassFormatError e) {
18 /*
19 * A ClassFormatError here means that (barring bugs in the
20 * proxy class generation code) there was some other
21 * invalid aspect of the arguments supplied to the proxy
22 * class creation (such as virtual machine limitations
23 * exceeded).
24 */
25 throw new IllegalArgumentException(e.toString());
26 }
27 }
可以猜測到接口創建的新類proxyClassFile 不管採用什麼接口,都是以下結構
1public class $Proxy1 extends Proxy implements 傳入的接口{ }
生成新類的看不到源代碼,不過猜測它的執行原理很有可能是如果類是Proxy的子類,則調用InvocationHandler進行方法的Invoke. 因為新生成的類已經extends 了Proxy,而且JAVA不能多繼承,因此接口是最好的實現方式了。
JDK動態代理的原理是根據定義好的規則,用傳入的接口創建一個新類,這就是為什麼採用動態代理時為什麼只能用接口引用指向代理,而不能用傳入的類引用執行動態類。
動態代理實現:Cglib
cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。
1、首先定義業務類,無需實現接口(當然,實現接口也可以,不影響的)
1public class MyBook {
2
3 void addBook(){
4 System.out.println("add book...");
5 }
6}
2、實現 MethodInterceptor方法代理接口,創建代理類
1public class MyCglibProxy implements MethodInterceptor {
2
3 @Override
4 public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
5
6 System.out.println("before——————");
7 Object result = methodProxy.invokeSuper(o, args); //調用業務類(父類中)的方法
8 System.out.println("end——————");
9
10 return result;
11 }
12}
3、創建業務類和代理類對象
1 MyBook myBook = new MyBook();
2
3 Enhancer enhancer = new Enhancer();
4 enhancer.setSuperclass(myBook.getClass());
5 //設置回調:對於代理類上所有方法的調用,都會調用CallBack,而Callback則需要實現intercept()方法進行攔
6 MyCglibProxy myCglibProxy = new MyCglibProxy();
7 enhancer.setCallback(myCglibProxy);
8 // 創建動態代理類對象並返回
9 MyBook book = (MyBook) enhancer.create();
10
11 book.addBook();
執行結果如下:
1before——————
2add book...
3end——————
總結
1、靜態代理是通過在代碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,用戶通過代理類調用被包裝過的業務方法;
2、JDK動態代理是通過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;
3、CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;
閱讀更多 科技伍小黑 的文章