文檔更新說明
2018年09月24日 v1.0 初稿
代理在生活中很常見,比如說婚介網站,其實就是找對象的代理;還有社保代理、人事代理;還有找黃牛搶票,其實也是一種代理;而這些代理,在java中也是有對應實現的。
1、為什么要動態代理
動態代理的作用其實就是在不修改原代碼的前提下,對已有的方法進行增強。
關鍵點:
不修改原來已有的代碼(滿足設計模式的要求)
對已有方法進行增強
2、舉個栗子
我們用一個很簡單的例子來說明:hello
類,有一個introduction
方法。
現在我們的需求就是不修改hello
類的introduction
方法,在introduction
之前先sayhello
,在introduction
之后再saygoodbye
3、實現方式
java中,實現動態代理有兩種方式,一種是jdk提供的,一種是第三方庫cglib提供的。特點如下:
jdk動態代理:被代理的目標類需要實現接口
cglib
方式:可以對任意類實現動態代理
3.1、jdk動態代理
jdk動態代理需要實現接口,然后通過對接口方法的增強來實現動態代理
所以要使用jdk動態代理的話,我們首先要創建一個接口,并且被代理的方法要在這個接口里面
3.1.1、創建一個接口
我們創建一個接口如下:
personal.java
1
2
3
4
5
6
7
8
|
public interface personal { /** * 被代理的方法 */ void introduction(); } |
3.1.2、實現接口
創建接口實現類,并且完成introduction
方法
personalimpl.java
1
2
3
4
5
6
|
public class personalimpl implements personal { @override public void introduction() { system.out.println( "我是程序員!" ); } } |
3.1.3、創建代理類
jdk代理的關鍵就是這個代理類了,需要實現invocationhandler
在代理類中,所有方法的調用都好分發到invoke
方法中。我們在invoke
方法完成對方法的增強即可
jdkproxyfactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import java.lang.reflect.invocationhandler; import java.lang.reflect.method; import java.lang.reflect.proxy; public class jdkproxyfactory<t> implements invocationhandler { /** * 目標對象 */ private t target; /** * 構造函數傳入目標對象 * * @param target 目標對象 */ public jdkproxyfactory(t target) { this .target = target; } /** * 獲取代理對象 * * @return 獲取代理 */ public t getproxy() { return (t) proxy.newproxyinstance(target.getclass().getclassloader(), target.getclass().getinterfaces(), this ); } @override public object invoke(object proxy, method method, object[] args) throws throwable { // 對方法增強 system.out.println( "大家好!" ); // 調用原方法 object result = method.invoke(target, args); // 方法增強 system.out.println( "再見!" ); return result; } } |
就這樣,jdk動態代理的代碼就完成了,接下來寫一份測試代碼
3.1.4、編寫測試代碼
為了方便測試,我們編寫一個test
方法
同時為了查看class
文件,還添加了一個generatorclass
方法,這個方法可以將動態代理生成的.class
輸出到文件
proxytest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
import org.junit.test; import sun.misc.proxygenerator; import java.io.fileoutputstream; import java.io.ioexception; public class proxytest { @test public void testjdkproxy() { // 生成目標對象 personal personal = new personalimpl(); // 獲取代理對象 jdkproxyfactory<personal> proxyfactory = new jdkproxyfactory<>(personal); personal proxy = proxyfactory.getproxy(); // 將proxy的class字節碼輸出到文件 generatorclass(proxy); // 調用代理對象 proxy.introduction(); } /** * 將對象的class字節碼輸出到文件 * * @param proxy 代理類 */ private void generatorclass(object proxy) { fileoutputstream out = null ; try { byte [] generateproxyclass = proxygenerator.generateproxyclass(proxy.getclass().getsimplename(), new class []{proxy.getclass()}); out = new fileoutputstream(proxy.getclass().getsimplename() + ".class" ); out.write(generateproxyclass); } catch (exception e) { e.printstacktrace(); } finally { if (out != null ) { try { out.close(); } catch (ioexception e) { // todo auto-generated catch block } } } } } |
3.1.5、查看運行結果
可以看到,運行test
方法之后,控制臺打印出如下:
大家好!
我是程序員!
再見!
我們在introduction
方法前和后都成功增加了功能,讓這個程序員的自我介紹瞬間變得更加有禮貌了。
3.1.6、探探動態代理的秘密
動態代理的代碼并不多,那么jdk
底層是怎么幫我們實現的呢?
在測試的時候我們將動態生成的代理類的class字節碼輸出到了文件,我們可以反編譯看看。
結果有點長,就不全部貼出來了,不過我們可以看到,里面有一個introduction
方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/** * the invocation handler for this proxy instance. * @serial */ protected invocationhandler h; protected proxy(invocationhandler h) { objects.requirenonnull(h); this .h = h; } public final void introduction() throws { try { super .h.invoke( this , m3, (object[]) null ); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } |
原來,生成的代理對象里面,引用了我們的invocationhandler
,然后在將introduction
方法里面調用了invocationhandler
的introduction
,而invocationhandler
是由我們編寫的代理類,在這里我們增加了sayhello
和saygoodbye
操作,然后還調用了原對象的introduction
方法,就這樣完成了動態代理。
3.2、cglib動態代理
cglib動態
3.2.1、創建被代理對象
由于cglib
不需要實現接口,所以我們不需要創建接口文件了(當然,你要有接口也沒有問題)
直接創建目標類,實現introduction
方法
personalimpl.java
1
2
3
4
5
|
public class personalimpl { public void introduction() { system.out.println( "我是程序員!" ); } } |
3.2.2、創建代理類
同樣,我們也需要創建代理類,并且在這里實現增強的邏輯,這次我們不是實現invocationhandler
接口了,而是實現cglib
提供的接口methodinterceptor
,都是類似的,methodinterceptor
中,全部方法調用都會交給intercept
處理,我們在intercept
添加處理邏輯即可。
cglibproxyfactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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 cglibproxyfactory<t> implements methodinterceptor { /** * 獲取代理對象 * * @param tclass 被代理的目標對象 * @return 代理對象 */ public t getproxybycglib( class <t> tclass) { // 創建增強器 enhancer enhancer = new enhancer(); // 設置需要增強的類的類對象 enhancer.setsuperclass(tclass); // 設置回調函數 enhancer.setcallback( this ); // 獲取增強之后的代理對象 return (t) enhancer.create(); } /** * 代理類方法調用回調 * * @param obj 這是代理對象,也就是[目標對象]的子類 * @param method [目標對象]的方法 * @param args 參數 * @param proxy 代理對象的方法 * @return 返回結果,返回給調用者 * @throws throwable */ @override public object intercept(object obj, method method, object[] args, methodproxy proxy) throws throwable { system.out.println( "大家好!" ); object result = proxy.invokesuper(obj, args); system.out.println( "再見!" ); return result; } } |
3.2.3、編寫測試代碼
在剛才的測試方法中,我們添加一個cglib
的測試方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@test public void testcglibproxy() { // 生成被代理的目標對象 personalimpl personal = new personalimpl(); // 獲取代理類 cglibproxyfactory<personalimpl> proxyfactory = new cglibproxyfactory<>(); personalimpl proxy = proxyfactory.getproxybycglib(( class <personalimpl>) personal.getclass()); // 將proxy的class字節碼輸出到文件 generatorclass(proxy); // 調用代理對象 proxy.introduction(); } |
3.2.4、查看運行結果
運行測試用例,可以看到跟jdk
的實現一樣的效果
大家好!
我是程序員!
再見!
3.2.5、探探動態代理的秘密
跟jdk
的測試一樣,我們也來看看生成的class
文件
1
2
3
4
5
6
7
8
9
|
public final void introduction() throws { try { super .h.invoke( this , m7, (object[]) null ); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } |
可以發現,與jdk
的動態代理并沒有區別。
4、如何選擇
既然有兩種實現方式,那么到底應該怎么選擇呢?
就兩個原則:
目標類有接口實現的,jdk
和cglib
都可以選擇,你開心就好
目標類沒有實現任何接口,那只能用cglib
了
5、后記
其實在第一次看到動態代理的時候,我就想不明白,我們都把目標類new出來了,為什么還要將目標類丟給代理類呢?為什么不直接調用目標類對應的方法呢?
后來才發現,原來我沒搞清楚動態代理的使用場景,場景很清晰,就是:
不修改原來已有的代碼(滿足設計模式的要求)
對已有方法進行增強
關鍵是增強,代理類里面我們是可以添加很多處理邏輯的,從而實現增強效果。就像黃牛搶票比我們厲害些一樣。
以上所述是小編給大家介紹的java動態代理詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:https://www.fengqiangboy.com/15377761043880.html