掃描文末二維碼或者微信搜索公衆號
菜鳥飛呀
飛,便可關注微信公衆號,閱讀更多Spring源碼分析文章java
在閱讀本文以前,能夠先思考一下下面幾個問題。數組
public interface UserService {
int insert();
String query();
}
複製代碼
public class UserServiceImpl implements UserService{
@Override
public int insert() {
System.out.println("insert");
return 0;
}
@Override
public String query() {
System.out.println("query");
return null;
}
}
複製代碼
public class UserServiceInvocationHandler implements InvocationHandler {
// 持有目標對象
private Object target;
public UserServiceInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invocation handler");
// 經過反射調用目標對象的方法
return method.invoke(target,args);
}
}
複製代碼
public class MainApplication {
public static void main(String[] args) {
// 指明一個類加載器,要操做class文件,怎麼少得了類加載器呢
ClassLoader classLoader = MainApplication.class.getClassLoader();
// 爲代理對象指定要是實現哪些接口,這裏咱們要爲UserServiceImpl這個目標對象建立動態代理,因此須要爲代理對象指定實現UserService接口
Class[] classes = new Class[]{UserService.class};
// 初始化一個InvocationHandler,並初始化InvocationHandler中的目標對象
InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
// 建立動態代理
UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
// 執行代理對象的方法,經過觀察控制檯的結果,判斷咱們是否對目標對象(UserServiceImpl)的方法進行了加強
userService.insert();
}
}
複製代碼
閱讀到這裏,咱們基本知道了JDK動態代理的寫法,功能雖然實現了,可是對於喜歡研究源碼的同窗,可能會思考:爲何調用Proxy.newProxyInstance()方法後,就產生了代理對象,對目標對象的方法進行了加強?原理是什麼呢?微信
public static void main(String[] args) throws IOException {
String proxyName = "com.tiantang.study.$Proxy0";
Class[] interfaces = new Class[]{UserService.class};
int accessFlags = Modifier.PUBLIC;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
// 將字節數組寫出到磁盤
File file = new File("/Users/liujinkun/Downloads/dynamic/$Proxy0.class");
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(proxyClassFile);
}
複製代碼
/Users/liujinkun/Downloads/dynamic/$Proxy0.class
找到生成的文件,因爲是一個class文件,因此咱們須要把它有反編譯器編譯一下,例如:在idea中,將文件放到target目錄下,打開文件就能看到反編譯後的代碼了。System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")
,就能實現將程序運行過程當中產生的動態代理對象的class文件寫入到磁盤。以下示例:public class MainApplication {
public static void main(String[] args) {
// 讓代理對象的class文件寫入到磁盤
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 指明一個類加載器,要操做class文件,怎麼少得了類加載器呢
ClassLoader classLoader = MainApplication.class.getClassLoader();
// 爲代理對象指定要是實現哪些接口,這裏咱們要爲UserServiceImpl這個目標對象建立動態代理,因此須要爲代理對象指定實現UserService接口
Class[] classes = new Class[]{UserService.class};
// 初始化一個InvocationHandler,並初始化InvocationHandler中的目標對象
InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
// 建立動態代理
UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
// 執行代理對象的方法,經過觀察控制檯的結果,判斷咱們是否對目標對象(UserServiceImpl)的方法進行了加強
userService.insert();
}
}
複製代碼
$Proxy0.class
。在idea打開,發現就是所產生代理類的源代碼。經過上面兩種方法獲取到代理對象的源代碼以下:異步
package com.sun.proxy;
import com.tiantang.study.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int insert() throws {
try {
return (Integer)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String query() throws {
try {
return (String)super.h.invoke(this, m4, (Object[])null);
} 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);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.tiantang.study.UserService").getMethod("insert");
m4 = Class.forName("com.tiantang.study.UserService").getMethod("query");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
複製代碼
經過源碼咱們發現,$Proxy0
類繼承了Proxy類,同時實現了UserService接口。到這裏,咱們的問題一就能解釋了,爲何JDK的動態代理只能基於接口實現,不能基於繼承來實現?由於Java中不支持多繼承,而JDK的動態代理在建立代理對象時,默認讓代理對象繼承了Proxy類,因此JDK只能經過接口去實現動態代理。ide
$Proxy0
實現了UserService接口,因此重寫了接口中的兩個方法($Proxy0
同時還重寫了Object類中的幾個方法)。因此當咱們調用query()方法時,先是調用到$Proxy0.query()
方法,在這個方法中,直接調用了super.h.invoke()方法,父類是Proxy,父類中的h
就是咱們定義的InvocationHandler,因此這兒會調用到UserServiceInvocationHandler.invoke()方法。所以當咱們經過代理對象去執行目標對象的方法時,會先通過InvocationHandler的invoke()方法,而後在經過反射method.invoke()去調用目標對象的方法,所以每次都會先打印invocation handler
這句話。工具
public class UserServiceInvocationHandler implements InvocationHandler {
// 持有目標對象
private Object target;
public UserServiceInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invocation handler");
// 經過反射調用目標對象的方法
return method.invoke(target,args);
}
}
複製代碼
public class UserServiceImpl implements UserService{
@Override
public int insert() {
System.out.println("insert");
query();
return 0;
}
@Override
public String query() {
System.out.println("query");
return null;
}
}
複製代碼
public class MainApplication {
public static void main(String[] args) {
// 讓代理對象的class文件寫入到磁盤
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 指明一個類加載器,要操做class文件,怎麼少得了類加載器呢
ClassLoader classLoader = MainApplication.class.getClassLoader();
// 爲代理對象指定要是實現哪些接口,這裏咱們要爲UserServiceImpl這個目標對象建立動態代理,因此須要爲代理對象指定實現UserService接口
Class[] classes = new Class[]{UserService.class};
// 初始化一個InvocationHandler,並初始化InvocationHandler中的目標對象
InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
// 建立動態代理
UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
// 執行代理對象的方法,經過觀察控制檯的結果,判斷咱們是否對目標對象(UserServiceImpl)的方法進行了加強
userService.insert();
}
}
複製代碼
$Proxy0
代理類對UserServiceImpl中的方法都進行了加強,每次調用UserServiceImpl中類的方法時,應該都會通過InvocationHandler中的invoke()方法,每次都會打印invocation handler
這句話。因此當咱們調用userService.insert()
時,打印結果應該是:invocation handler
insert
invocation handler
query
複製代碼
invocation handler
。在調用query()方法時,並無去執行InvocationHandler中的invoke()方法。爲何呢?緣由就在這個地方:public int insert() {
System.out.println("insert");
query();
return 0;
}
複製代碼
$Proxy0
這個代理對象,只有在調用代理對象的query()方法時,纔會通過InvocationHandler.invoke()方法,因此此時只會打印一次invocation handler
。因此第二個問題的答案就是:在調用過程當中,this的指向發生了變化。掃描下方二維碼便可關注微信公衆號
菜鳥飛呀飛
,一塊兒閱讀更多Spring源碼。源碼分析