摘要
相比于靜態代理,動態代理避免了開發人員編寫各個繁鎖的靜態代理類,只需簡單地指定一組接口及目標類對象就能動態的獲得代理對象。
代理模式
使用代理模式必須要讓代理類和目標類實現相同的接口,客戶端通過代理類來調用目標方法,代理類會將所有的方法調用分派到目標對象上反射執行,還可以在分派過程中添加"前置通知"和后置處理(如在調用目標方法前校驗權限,在調用完目標方法后打印日志等)等功能。
使用動態代理的五大步驟
1.通過實現InvocationHandler接口來自定義自己的InvocationHandler;
2.通過Proxy.getProxyClass獲得動態代理類
3.通過反射機制獲得代理類的構造方法,方法簽名為getConstructor(InvocationHandler.class)
4.通過構造函數獲得代理對象并將自定義的InvocationHandler實例對象傳為參數傳入
5.通過代理對象調用目標方法
動態代理的使用
例1(方式一)
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
public class MyProxy { public interface IHello{ void sayHello(); } static class Hello implements IHello{ public void sayHello() { System.out.println( "Hello world!!" ); } } //自定義InvocationHandler static class HWInvocationHandler implements InvocationHandler{ //目標對象 private Object target; public HWInvocationHandler(Object target){ this .target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "------插入前置通知代碼-------------" ); //執行相應的目標方法 Object rs = method.invoke(target,args); System.out.println( "------插入后置處理代碼-------------" ); return rs; } } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetExc eption, InstantiationException { //生成$Proxy0的class文件 System.getProperties().put( "sun.misc.ProxyGenerator.saveGeneratedFiles" , "true" ); //獲取動態代理類 Class proxyClazz = Proxy.getProxyClass(IHello. class .getClassLoader(),IHello. class ); //獲得代理類的構造函數,并傳入參數類型InvocationHandler.class Constructor constructor = proxyClazz.getConstructor(InvocationHandler. class ); //通過構造函數來創建動態代理對象,將自定義的InvocationHandler實例傳入 IHello iHello = (IHello) constructor.newInstance( new HWInvocationHandler( new Hello())); //通過代理對象調用目標方法 iHello.sayHello(); } } |
輸出:
------插入前置通知代碼-------------
Hello world!!
------插入后置處理代碼-------------
Proxy類中還有個將2~4步驟封裝好的簡便方法來創建動態代理對象,其方法簽名為:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h),如下例:
(方式二)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //生成$Proxy0的class文件 System.getProperties().put( "sun.misc.ProxyGenerator.saveGeneratedFiles" , "true" ); IHello ihello = (IHello) Proxy.newProxyInstance(IHello. class .getClassLoader(), //加載接口的類加載器 new Class[]{IHello. class }, //一組接口 new HWInvocationHandler( new Hello())); //自定義的InvocationHandler ihello.sayHello(); } |
輸出結果一樣.
下面以newProxyInstance方法為切入點來剖析代理類的生成及代理方法的調用
(為了篇幅整潔去掉了次要的代碼)
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
51
|
public static Object newProxyInstance(ClassLoader loader, Class<!--?-->[] interfaces, InvocationHandler h) throws IllegalArgumentException { if (h == null ) { //如果h為空直接拋出異常,所以InvocationHandler實例對象是必須的 throw new NullPointerException(); } //對象的拷貝,暫不知道這里拷貝下的意義是啥? final Class<!--?-->[] intfs = interfaces.clone(); //一些安全的權限檢查 final SecurityManager sm = System.getSecurityManager(); if (sm != null ) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } //產生代理類 Class<!--?--> cl = getProxyClass0(loader, intfs); //獲取代理類的構造函數對象 //參數constructorParames為常量值:private static final Class<!--?-->[] constructorParams = { InvocationHandler.class }; final Constructor<!--?--> cons = cl.getConstructor(constructorParames); final InvocationHandler ih = h; //根據代理類的構造函數對象來創建代理類對象 return newInstance(cons, ih); } |
這段代碼就是對代理類對象的創建,就是對例1中34~38行封裝,其中getProxyClass0就是生成代理類的方法
getProxyClass0方法剖析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private static Class<!--?--> getProxyClass0(ClassLoader loader, Class<!--?-->... interfaces) { //接口數不得超過65535個 if (interfaces.length > 65535 ) { throw new IllegalArgumentException( "interface limit exceeded" ); } //代理類緩存,如果緩存中有代理類了直接返回,否則將由ProxyClassFactory創建代理類 return proxyClassCache.get(loader, interfaces); } |
看看ProxyClassFactory是怎樣生成代理類的?
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
private static final class ProxyClassFactory implements BiFunction<classloader, class <?= "" >[], Class<!--?-->> { //統一代理類的前綴名都以$Proxy開關 private static final String proxyClassNamePrefix = "$Proxy" ; //使用唯一的編號給作為代理類名的一部分,如$Proxy0,$Proxy1等 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<!--?--> apply(ClassLoader loader, Class<!--?-->[] interfaces) { Map< class <?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<!--?--> intf : interfaces) { //驗證指定的類加載器(loader)加載接口所得到的Class對象(interfaceClass)是否與intf對象相同 Class<!--?--> interfaceClass = null ; try { interfaceClass = Class.forName(intf.getName(), false , loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader" ); } //驗證該Class對象是不是接口 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface" ); } // 驗證該接口是否重復了 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null ) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } //聲明代理類所在包 String proxyPkg = null ; /*驗證你傳入的接口中是否有非public接口,只要有一個接口是非public的,那么這些接口都必須在同一包中 這里的接口修飾符直接影響到System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")所生成 的代理類的路徑,往下看!!*/ for (Class<!--?--> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { String name = intf.getName(); int n = name.lastIndexOf('.'); //截取完整包名 String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { /*如果都是public接口,那么生成的代理類就在com.sun.proxy包下如果報java.io.FileNotFoundException: com\sun\proxy\$Proxy0.c lass (系統找不到指定的路徑。)的錯誤,就先在你項目中創建com.sun.proxy路徑*/ proxyPkg = ReflectUtil.PROXY_PACKAGE + "." ; } //將當前nextUniqueNumber的值以原子的方式的加1,所以第一次生成代理類的名字為$Proxy0.class long num = nextUniqueNumber.getAndIncrement(); //代理類的完全限定名,如com.sun.proxy.$Proxy0.calss, String proxyName = proxyPkg + proxyClassNamePrefix + num; //生成代理類字節碼文件 byte [] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { return defineClass0(loader, proxyName, proxyClassFile, 0 , proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }</ class <?></classloader,> |
而生成代理類字節碼文件又主要通過ProxyGenerate的generateProxyClass(proxyName,interfaces)
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
|
public static byte [] generateProxyClass( final String var0, Class[] var1) { ProxyGenerator var2 = new ProxyGenerator(var0, var1); //生成代理類字節碼文件的真正方法 final byte [] var3 = var2.generateClassFile(); //保存文件 if (saveGeneratedFiles) { AccessController.doPrivileged( new PrivilegedAction() { public Void run() { try { FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class" ); var1.write(var3); var1.close(); return null ; } catch (IOException var2) { throw new InternalError( "I/O exception saving generated file: " + var2); } } }); } return var3; } |
層層調用后,最終generateClassFile才是真正生成代理類字節碼文件的方法,注意開頭的三個addProxyMethod方法是只將Object的hashcode,equals,toString方法添加到代理方法容器中,代理類除此之外并沒有重寫其他Object的方法,所以除這三個方法外,代理類調用其他方法的行為與Object調用這些方法的行為一樣不通過Invoke
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
private byte [] generateClassFile() { /addProxyMethod系列方法就是將接口的方法和Object的hashCode,equals,toString方法添加到代理方法容器(proxyMethods), 其中方法簽名作為key,proxyMethod作為value*/ /*hashCodeMethod方法位于靜態代碼塊中通過Object對象獲得,hashCodeMethod=Object.class.getMethod("hashCode",new Class[0]), 相當于從Object中繼承過來了這三個方法equalsMethod,toStringMethod*/ this.addProxyMethod(hashCodeMethod, Object.class); --> this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); int var1; int var3; //獲得所有接口中的所有方法,并將方法添加到代理方法中 for(var1 = 0; var1 < this.interfaces.length; ++var1) { Method[] var2 = this.interfaces[var1].getMethods(); for(var3 = 0; var3 < var2.length; ++var3) { this.addProxyMethod(var2[var3], this.interfaces[var1]); } } Iterator var7 = this.proxyMethods.values().iterator(); List var8; while(var7.hasNext()) { var8 = (List)var7.next(); checkReturnTypes(var8); //驗證具有相同方法簽名的的方法的返回值類型是否一致,因為不可能有兩個方法名相同,參數相同,而返回值卻不同的方法 }; //接下來就是寫代理類文件的步驟了 Iterator var11 try { //生成代理類的構造函數 this.methods.add(this.generateConstructor()); var7 = this.proxyMethods.values().iterator(); while(var7.hasNext()) { var8 = (List)var7.next(); var11 = var8.iterator(); while(var11.hasNext()) { ProxyGenerator.ProxyMethod var4 = (ProxyGenerator.ProxyMethod)var11.next(); /將代理字段聲明為Method,10為ACC_PRIVATE和ACC_STATAIC的與運算,表示該字段的修飾符為private static 所以代理類的字段都是private static Method XXX*/ this .fields.add( new ProxyGenerator.FieldInfo(var4.methodFieldName, "Ljava/lang/reflect/Method;" , 10 )); //生成代理類的代理方法 this .methods.add(var4.generateMethod()); } } //為代理類生成靜態代碼塊,對一些字段進行初始化 this .methods.add( this .generateStaticInitializer()); } catch (IOException var6) { throw new InternalError( "unexpected I/O Exception" ); } if ( this .methods.size() > '\uffff' ) { //代理方法超過65535將拋出異常 throw new IllegalArgumentException( "method limit exceeded" ); } else if ( this .fields.size() > '\uffff' ) { //代理類的字段超過65535將拋出異常 throw new IllegalArgumentException( "field limit exceeded" ); } else { //這里開始就是一些代理類文件的過程,此過程略過 this .cp.getClass(dotToSlash( this .className)); this .cp.getClass( "java/lang/reflect/Proxy" ); for (var1 = 0 ; var1 < this .interfaces.length; ++var1) { this .cp.getClass(dotToSlash( this .interfaces[var1].getName())); } this .cp.setReadOnly(); ByteArrayOutputStream var9 = new ByteArrayOutputStream(); DataOutputStream var10 = new DataOutputStream(var9); try { var10.writeInt(- 889275714 ); var10.writeShort( 0 ); var10.writeShort( 49 ); this .cp.write(var10); var10.writeShort( 49 ); var10.writeShort( this .cp.getClass(dotToSlash( this .className))); var10.writeShort( this .cp.getClass( "java/lang/reflect/Proxy" )); var10.writeShort( this .interfaces.length); for (var3 = 0 ; var3 < this .interfaces.length; ++var3) { var10.writeShort( this .cp.getClass(dotToSlash( this .interfaces[var3].getName()))); } var10.writeShort( this .fields.size()); var11 = this .fields.iterator(); while (var11.hasNext()) { ProxyGenerator.FieldInfo var12 = (ProxyGenerator.FieldInfo)var11.next(); var12.write(var10); } var10.writeShort( this .methods.size()); var11 = this .methods.iterator(); while (var11.hasNext()) { ProxyGenerator.MethodInfo var13 = (ProxyGenerator.MethodInfo)var11.next(); var13.write(var10); } var10.writeShort( 0 ); return var9.toByteArray(); } catch (IOException var5) { throw new InternalError( "unexpected I/O Exception" ); } } } |
addProxyMethod方法剖析
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
51
52
53
54
55
56
57
|
private void addProxyMethod(Method var1, Class var2) { String var3 = var1.getName(); //方法名 Class[] var4 = var1.getParameterTypes(); //方法參數類型數組 Class var5 = var1.getReturnType(); //返回值類型 Class[] var6 = var1.getExceptionTypes(); //異常類型 String var7 = var3 + getParameterDescriptors(var4); //方法簽名 Object var8 = (List) this .proxyMethods.get(var7); //根據方法簽名卻獲得proxyMethods的Value if (var8 != null ) { //處理多個代理接口中重復的方法的情況 Iterator var9 = ((List)var8).iterator(); while (var9.hasNext()) { ProxyGenerator.ProxyMethod var10 = (ProxyGenerator.ProxyMethod)var9.next(); if (var5 == var10.returnType) { /*歸約異常類型以至于讓重寫的方法拋出合適的異常類型,我認為這里可能是多個接口中有相同的方法,而這些相同的方法拋出的異常類 型又不同,所以對這些相同方法拋出的異常進行了歸約*/ ArrayList var11 = new ArrayList(); collectCompatibleTypes(var6, var10.exceptionTypes, var11); collectCompatibleTypes(var10.exceptionTypes, var6, var11); var10.exceptionTypes = new Class[var11.size()]; //將ArrayList轉換為Class對象數組 var10.exceptionTypes = (Class[])var11.toArray(var10.exceptionTypes); return; } } } else { var8 = new ArrayList(3); this.proxyMethods.put(var7, var8); } ((List)var8).add(new ProxyGenerator.ProxyMethod(var3, var4, var5, var6, var2, null)); /*24~27行的意思就是如果var8為空,就創建一個數組,并以方法簽名為key,proxymethod對象數組為value添加到proxyMethods*/ } |
InvocationHandler的作用
在動態代理中InvocationHandler是核心,每個代理實例都具有一個關聯的調用處理程序(InvocationHandler)。對代理實例調用方法時,將對方法調用進行編碼并將其指派到它的調用處理程序(InvocationHandler)的 invoke 方法。所以對代理方法的調用都是通InvocationHadler的invoke來實現中,而invoke方法根據傳入的代理對象,方法和參數來決定調用代理的哪個方法
invoke方法簽名:invoke(Object Proxy,Method method,Object[] args)
$Proxy0.class
來看看例1(MyProxy)的代理類是怎樣的?
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
public final class $Proxy0 extends Proxy implements IHello { //繼承了Proxy類和實現IHello接口 //變量,都是private static Method XXX private static Method m3; private static Method m1; private static Method m0; private static Method m2; //代理類的構造函數,其參數正是是InvocationHandler實例,Proxy.newInstance方法就是通過通過這個構造函數來創建代理實例的 public $Proxy0(InvocationHandler var1) throws { super (var1); } //接口代理方法 public final void sayHello() throws { try { super .h.invoke( this , m3, (Object[]) null ); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } //以下Object中的三個方法 public final boolean equals(Object var1) throws { try { return ((Boolean) super .h.invoke( this , m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final int hashCode() throws { try { return ((Integer) super .h.invoke( this , m0, (Object[]) null )).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String) super .h.invoke( this , m2, (Object[]) null ); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } //對變量進行一些初始化工作 static { try { m3 = Class.forName( "com.mobin.proxy.IHello" ).getMethod( "sayHello" , new Class[ 0 ]); m1 = Class.forName( "java.lang.Object" ).getMethod( "equals" , new Class[]{Class.forName( "java.lang.Object" )}); m0 = Class.forName( "java.lang.Object" ).getMethod( "hashCode" , new Class[ 0 ]); m2 = Class.forName( "java.lang.Object" ).getMethod( "toString" , new Class[ 0 ]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } |
以上就是對代理類如何生成,代理類方法如何被調用的分析!在很多框架都使用了動態代理如Spring,HDFS的RPC調用等等,分析過程中收獲很多,如果想深入的了解JDK動態代理機制一定要深入到源碼去剖析!!希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://www.cnblogs.com/MOBIN/p/5597215.html