反射 (Reflection) 是 Java 的特徵之一,它允許運行中的 Java 程序獲取自身的信息,並且可以操作類或對象的內部屬性。
簡而言之,通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而Java 反射機制可以動態地創建對象並調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。
反射有什麼用,其實最主要的用處就兩個:
- 根據類名在運行時創建實例(類名可以從配置文件讀取,不用new)
- 通過Method.invoke()執行方法
但是這些不難理解,反射的整個流程比較難理解。
假如你寫了一段代碼:
Object o = new Object();
運行了起來,首先JVM會啓動,你的代碼會編譯成一個.class文件,然後被類加載器加載進JVM的內存中,在方法區創建了Object類的Class對象,注意這個不是new出來的對象,而是類的類型對象,每個類都只有一個Class對象,作爲方法區類的數據結構的接口。JVM創建對象前,會先檢查類是否加載,尋找類對應的Class對象,若已經加載好,則爲你的對象分配內存,初始化也就是代碼執行Object類的無參構造函數。
上面的流程就是我們自己寫好了創建實例的代碼扔給JVM去跑,跑完了,實例也就生成成功了。那如果出現以下場景:服務器上遇到某個請求,這個請求要求運行過程中遇到某種情況時才創建某個類的實例,難道要停下來自己寫代碼new出這個對象嗎?當然不行,反射是什麼呢,當我們的程序在運行時,可能需要動態的加載一些類並創建其實例,這些類可能最初用不到,只有在需要時才加載到JVM,即運行時按需加載,這樣的好處對於服務器來說不言而喻。例如Spring,各種各樣的bean是以配置文件的形式配置的,需要用到哪些bean就加載哪些並生成實例配,Spring容器就會根據需求去動態加載,程序能健壯地運行。
由於反射本身比較抽象,所有需要從類加載、Class對象創建、實例創建整個過程來分析。
JVM是如何構建一個實例的
假設main方法中有以下代碼:
Person a = new Person();
很多初學者會以爲整個創建對象的過程是下面這樣的
javac Person.java
java Person
用圖表達就是下面的過程,main線程中創建一個Person實例,線程的方法棧中保存一個指向堆中對象的指針(引用)。
以上過程過於粗放,更細緻的過程如下:
無論是通過new還是反射創建實例,都離不開.class文件及Class對象。找到一個.class文件,用sublime text以UTF-8編碼形式打開,是下面這樣的一片。
.java源碼是給人類讀的,而.class字節碼是給計算機讀的。JVM對.class文件也有一套自己的讀取規則,這些“亂碼”JVM能夠愉快的識別。
類加載器
.class文件是由類加載器加載到JVM中的,類加載器非常複雜。但是其核心方法只有loadClass(),傳入要加載的類的全限定名即可。抽象類ClassLoader中定義的loadClass()方法的源碼如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//互斥鎖,防止多個線程同時加載一個類
synchronized (getClassLoadingLock(name)) {
//先從緩存查找該class對象,找到就不用重新加載
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
加載一個類可以分爲3步:
- 檢查是否已經加載過了,findLoadedClass方法做的事,避免重複;
- 優先交給自己的父加載器加載
- 以上兩步均失敗,就自己加載
ClassLoader是抽象類,無法通過new創建其實例,其findClass()方法的源碼如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
由此可見,findClass方法必須交給子類去重寫,在裏面自定義尋找類的邏輯。
defineClass()是ClassLoader定義的方法,其根據從.class文件中讀取到的字節數組byte[] 構造出一個Class對象。其底層最終調用的是多個native方法:
private native Class<?> defineClass0(String name, byte[] b, int off, int len,
ProtectionDomain pd);
private native Class<?> defineClass1(String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);
private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
int off, int len, ProtectionDomain pd,
String source);
綜合以上描述,ClassLoader加載.class文件並生成Class對象的大致流程如下:
Class類
現在,.class文件被類加載器加載到內存中,類加載器也根據其字節數組創建了對象的Class對象。Class對象是Class類的實例。Class是類的類型對象,用來描述類。通常一個類至少會包括以下信息:
- 方法或字段的權限修飾符
- 類名
- 字段
- 方法
- 構造器
- 註解
- 父類或實現的接口
所以想要定義一個Class類來完整描述任意一個類,以上信息必然要在Class類中封裝好。查看java.lang.Class的源碼,可以發現以上信息都通過靜態內部類封裝好了:
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
由於字段、方法、構造器的信息比較多,例如字段有字段類型、訪問權限符,方法有方法名、參數、返回值、訪問權限符等信息。所以專門創建了Field類、Method類及Constructor類來描述類的字段、方法及構造器。
一個Person類中的所有信息,最終都被解析成一個Class對象,查看Class類的定義可知,一個Class對象可以完整地徹底地描述任意一個類。
再來看Class類的方法,構造函數源碼如下:
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
Class類的構造器是私有的,我們無法手動new一個Class對象,只能由JVM創建。JVM在構造Class對象時必須傳入一個類加載器。
Class.forName()方法源碼如下,還得靠類加載器。
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
newInstance()方法源碼如下:
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
if (System.getSecurityManager() != null) {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
// NOTE: the following code may not be strictly correct under
// the current Java memory model.
// Constructor lookup
if (cachedConstructor == null) {
if (this == Class.class) {
throw new IllegalAccessException(
"Can not call newInstance() on the Class for java.lang.Class"
);
}
try {
Class<?>[] empty = {};
//獲取無參構造函數,getConstructor0函數可能拋出NoSuchMethodException異常
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
// Disable accessibility checks on the constructor
// since we have to do the security check here anyway
// (the stack depth is wrong for the Constructor's
// security check to work)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
c.setAccessible(true);
return null;
}
});
cachedConstructor = c;
//如果找不到無參構造函數,就會進入catch
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
// Security check (same as in java.lang.reflect.Constructor)
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
// Run constructor
try {
//通過Constructor對象的newInstance方法來創建實例
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
查看源碼可知,newInstance()底層就是在調用無參構造器方法。
拿到一個類的Class對象後,要想通過該Class對象去創建類的實例,其實最終是通過Constructor對象來做的,如果該類沒有無參構造函數(即找不到符合條件的contructor對象)就無法使用clazz.newInstance()。
反射API
日常開發中反射的最終目的主要有兩個:
- 創建實例
- 調用方法
創建實例的難點在於,很多人不知道clazz.newInstance()底層還是調用Contructor對象的newInstance()方法。所以,要想通過調用clazz.newInstance()方法生成某個類的實例,必須保證該類的時候有個無參構造函數。
爲什麼根據Class對象獲取Method時,需要傳入方法名+參數的Class類型
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Method method = getMethod0(name, parameterTypes, true);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
因爲.class文件中有多個方法Method[],方法是可以同名的。要想唯一定位一個方法必須明確地給出方法名 + 參數(包括參數類型及順序)。在同一個類中出現多個同名方法叫重載,來複習下重載與覆蓋的區別。
- 重載:如果在一個類中定義了多個同名的方法,但它們有不同的參數(包含三方面:參數個數、參數類型和參數順序),則稱爲方法的重載。其中,不能通過訪問權限、返回類型和拋出異常進行重載。
- 覆蓋:子類中定義的某個方法與其父類中某個方法具有相同的方法簽名(包含相同的名稱和參數列表),則稱爲方法的覆蓋。子類對象使用這個方法時,將調用該方法在子類中的定義,對它而言,父類中該方法的定義被屏蔽了。
總的來說,重載和覆蓋是Java多態性的不同表現。前者是一個類中多態性的一種表現,後者是父類與子類之間多態性的一種表現。
那參數parameterTypes爲什麼要用Class類型,爲什麼不能傳變量名呢,因爲我們無法根據變量名區分方法。
Person getPerson(String userName, int age);
Person getPerson(String nick, int old)
這不叫重載,上面就是同一個方法,IDE中不可能編譯通過。
那麼爲不什麼不能傳String和int呢?原因是這些都是基本類型和引用類型,類型不能用來傳遞,能傳遞的要麼值,要麼是對象的引用。而java.lang.String.class以及int.class是對象。
調用method.invoke(obj, args);時爲什麼要傳入一個目標對象
上面分析過,.class文件通過IO被加載到內存後,對象的本質就是用來存儲數據的。而方法作爲一種行爲描述,是所有對象共有的,不屬於某個對象獨有,比如兩個Person實例:
Person zhangsan = new Person();
Person lisi = new Person();
對象zhangsan保存的名字及年齡分別爲“zhangsan”和18,而對象lisi保存的名字及年齡分別爲“lisi”和20,兩個對象都有changeAge()方法,但是每個對象裏面存一份太浪費。既然是共性行爲,可以抽取出來,放在方法區共用。
但這又產生了一個棘手的問題,類的方法(指非static方法)是共用的,JVM如何保證zhangsan調用changeAge()時,該方法不會跑去把lisi的數據改掉呢?
所以JVM設置了一種隱性機制,每次對象調用方法時,都會隱性傳遞當前調用該方法的對象參數(C++也有類似機制,傳遞的是this指針),方法可以根據這個對象參數知道當前調用本方法的是哪個對象!
同樣的,在通過反射調用方法時,本質還是希望方法處理某個對象的數據,所以必須傳入對象的引用。如果要invoke的是一個靜態方法,就不需要傳入具體的對象了,因爲靜態方法並不能處理對象中保存的數據。
Java反射在實際開發中應用
很多Java框架中都有反射的影子,例如Spring、Mybatis等等,Jdbc利用反射將數據庫的表字段映射到Java對象的getter/setter方法。Jackson、GSO、Boon等類庫也是利用反射將JSON文件的屬性映射到Java對的象getter/setter方法。可見只要使用java,反射就無處不在。
最典型的,動態代理就使用了反射在運行時創建接口的動態實現,java.lang.reflect.Proxy類提供了創建動態實現的功能。我們把運行時創建接口的動態實現稱爲動態代理。 動態代理可以用於許多不同的目的,例如數據庫連接和事務管理、用於單元測試的動態模擬對象以及其他類似AOP的方法攔截等。
代理模式就是在接口和實現之前加一層,用於剝離接口的一些額外的操作。
簡單的代理模式的典型示例代碼如下:
public interface Subject {
void doSomething();
}
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println(this.getClass().getName() + "_doSomething");
}
}
public class SubjectInvocationHandler implements InvocationHandler {
//被代理對象
private Object proxyObject;
public SubjectInvocationHandler(Object proxyObject) {
this.proxyObject = proxyObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在執行真正的方法前,可以做一些事情
System.out.println(this.getClass().getName() + "_invocationHandler");
return method.invoke(proxyObject, args);
}
}
怎樣在運行時動態生成接口的代理類的實例
動態代理就是動態的創建代理對象並動態地處理對其所代理的方法的調用。
對於一個普通的 Java 動態代理,其實現過程可以簡化成爲:
- 提供一個基礎的接口,作爲被調用類型(如com.test.MyInterfaceImpl)和代理類(如com.test.MyInterface)之間的統一入口;
- 通過實現InvocationHandler接口來自定義自己的MyInvocationHandler,對代理對象方法的調用,會被分派到其 invoke 方法來真正實現動作;
- 調用java.lang.reflect.Proxy類的newProxyInstance 方法,生成一個實現了相應基礎接口的代理類實例;
JDK1.8中newProxyInstance方法源碼如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//聲明h不能爲空
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
* 查找或生成代理類
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* 使用指定的調用處理程序調用它的構造函數
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//代理類cl已經有了,通過反射獲取cl的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//反射生成代理對象並返回,這裏的對象就是調用Contructor的newInstance方法生成的
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
newProxyInstance幫我們執行了生成代理類----獲取構造器----生成代理對象這三步;newProxyInstance()方法有三個參數: 目標接口、目標接口的類加載器以及InvocationHandler。
我們重點分析生成代理類getProxyClass0()方法
/**
* a cache of proxy classes:動態代理類的弱緩存容器
* KeyFactory:根據接口的數量,映射一個最佳的key生成函數,其中表示接口的類對象被弱引用;也就是key對象被弱引用繼承自WeakReference(key0、key1、key2、keyX),保存接口密鑰(hash值)
* ProxyClassFactory:生成動態類的工廠
* 注意,兩個都實現了BiFunction<ClassLoader, Class<?>[], Object>接口
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
* 生成代理類,調用前必須進行 checkProxyAccess權限檢查,所以newProxyInstance進行了權限檢查
*/
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
//實現接口的最大數量<65535;誰寫的類能實現這麼多接口
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
// 如果緩存中有,就直接返回,否則會生成
return proxyClassCache.get(loader, interfaces);
}
proxyClassCache.get()方法中,如果沒有從緩存中找到,最終會調用ProxyGenerator.generateProxyClass()方法去生成字節碼。
public static byte[] generateProxyClass(final String name, Class<?>[] interfaces, int accessFlags) {
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
//真正生成字節碼的方法
final byte[] classFile = gen.generateClassFile();
//如果saveGeneratedFiles爲true 則生成字節碼文件,所以在開始我們要設置這個參數
//當然,也可以通過返回的bytes自己輸出
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError( "I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
以上方法執行成功後就返回了字節碼的byte數組。然後,就可以進入我們熟知的類加載過程了,我就不再贅述了。
以下兩行就能爲任意一個接口生成一個代理類實例。
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
運行上面代碼後,proxy這個實例就包含了對MyInterface接口的動態實現。等等,我們並沒有寫出MyInterface的實現類代碼,這樣就直接生成了一個MyInterface實現類的實例?很神奇吧,難道new一個Person實例之前,不是要先給出Person類的定義嗎。所謂“動態”代理的動態就體現在這裏,這裏直接生成了類的字節碼(就是沒有Person.java文件,直接搞出了Person.class文件,Javac的過程都直接省略了),類加載器纔不管字節碼是怎麼來的呢,只要是合乎規範的字節碼即合規的byte[],它都能加載。
總結
一個典型的動態代理創建對象過程可分爲以下四個步驟:
- 通過實現InvocationHandler接口創建自己的調用處理器;
- 通過爲Proxy類指定ClassLoader對象和一組interface創建動態代理類,即Proxy類的getProxyClass0()方法;
- 通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型即對上一步獲得的clazz對象調用getConstructor()
- 通過構造函數創建代理類實例,此時需將調用處理器對象作爲參數被傳入;
爲了簡化對象創建過程,Proxy類中的newInstance方法封裝了2~4,只需兩步即可完成代理對象的創建。
如何查看動態生成的代理類的.class文件
在調用Proxy.newProxyInstance()方法時,最終會調用到ProxyGenerator.generateProxyClass()方法,該方法的作用就是生成代理對象的class文件,返回值是一個byte[]數組。所以我們可以將生成的byte[]數組通過輸出流,將內容寫出到磁盤。
public static void main(String[] args) throws IOException {
String proxyName = "com.test.$Proxy0";
Class[] interfaces = new Class[]{Subject.class};
int accessFlags = Modifier.PUBLIC;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
// 將字節數組寫出到磁盤
File file = new File("/Users/Downloads/test/$Proxy0.class");
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(proxyClassFile);
}
運行完main()方法後,從目錄中找到生成的文件,由於是一個.class文件,所以我們需要把它有反編譯器編譯一下,例如:在idea中,將文件放到target目錄下,打開文件就能看到反編譯後的代碼了。
以下爲將上面生成的.class文件放入target目錄中查看的結果
$Proxy0的源碼如下
public class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m3;
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})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void doSomething() throws {
try {
super.h.invoke(this, m3, (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)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.test.proxy.Subject").getMethod("doSomething", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
通過源碼我們發現,$Proxy0類繼承了Proxy類,同時實現了Subject接口。到這裏,我們就能解釋爲什麼JDK的動態代理只能基於接口實現,不能基於繼承來實現?因爲Java中不支持多繼承,而JDK的動態代理在創建代理對象時,默認讓代理對象繼承了Proxy類,所以JDK只能通過接口去實現動態代理。
$Proxy0類實現了Subject接口,重寫了doSomething()方法,它也同時
重寫了Object類中的幾個方法。所以當我們調用doSomething()方法時,先是調用到$Proxy0.doSomething()方法,在這個方法中,直接調用了super.h.invoke()方法,父類是Proxy,父類中的h就是我們定義的InvocationHandler,所以這兒會調用到SubjectInvocationHandler.invoke()方法。因此當我們通過代理對象去執行目標對象的方法時,會先經過InvocationHandler的invoke()方法,然後在通過反射method.invoke()去調用目標對象的方法。
public static void main(String[] args) throws IOException {
Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class},
new SubjectInvocationHandler(new SubjectImpl()));
proxySubject.doSomething();
}
運行結果如下:
com.test.proxy.SubjectInvocationHandler_invocationHandler
com.test.proxy.impl.SubjectImpl_doSomething
誠然,Proxy已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持interface代理的桎梏,因爲它的設計註定了這個遺憾。它們已經註定有一個共同的父類叫Proxy。Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理,原因是多繼承在Java中本質上就行不通。有很多條理由,人們可以否定對class代理的必要性,但是同樣有一些理由,相信支持class動態代理會更美好。接口和類的劃分,本就不是很明顯,只是到了Java中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。但是,不完美並不等於不偉大,偉大是一種本質,Java動態代理就是佐例。
動態代理在Mybatis中的應用
舉一個Mybatis中使用動態代理的例子。在開始使用Mybatis這個框架的時候,我很不能理解的一點就是定義了一個訪問數據庫的接口進行了一些配置之後,然後通過sqlSession.getMapper()方法就可以獲得這個接口的一個實現類的對象。大致是這樣的:
public interface PersonDao {
PersonDO getPersonById(long id);
}
public void testGetPersonById(){
PersonDao personDao = sqlSession.getMapper(PersonDao.class);
personDao.getPersonById(12);
}
這裏的sqlSession.getMapper()方法其實是爲PersonDao接口生成了一個動態代理對象。來看DefaultSqlSession類中對getMapper()方法的具體實現:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
它直接調用的configuration的getMapper()方法,來看下這個方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
mapperRegistry.getMapper()方法:
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//產生代理對象的方法
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
mapperProxyFactory.newInstance()方法:
//調用Proxy.newProxyInstance()來創建的反射對象
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
可以看到底層是通過Proxy.newProxyInstance()創建的代理對象,然後返回的。而且因爲這裏使用了泛型,所以對Proxy.newProxyInstance()創建返回的Object對象進行了強制類型轉換,返回的對象就是BranchDao接口類型了。
參考文章
深入解析Java反射(1) - 基礎
Java 動態代理原理及其在mybatis中的應用
https://www.cnblogs.com/flyoung2008/p/3251148.html