目錄java
使用過Spring框架進行web開發的應該都知道,Spring的兩大核心技術是IOC和AOP。而其中IOC又是AOP的支撐。IOC要求由容器來幫咱們自動建立Bean實例並完成依賴注入。IOC容器的代碼在實現時確定不知道要建立哪些Bean,而且這些Bean之間的依賴關係是怎樣的(若是寫死在裏面,這框架還能用嗎?)。因此其必須在運行期經過掃描配置文件或註解的方式來肯定須要爲哪些類建立實例。通俗的說,必須在運行時爲編譯期還不能肯定的類建立實例。再直白一點,必須提供一種new Object()以外的建立對象的方法。依賴注入存在相似的問題,容器必須可以在運行時發現全部標註有@Autowired或@Resource的字段或方法,而且可以在不知道對象的任何類型信息的狀況下調用其setter方法完成依賴的注入(默認bean的字段都會實現setter方法)。總結一下IOC容器在實現時必須作到的三件看起來「不太可能的事」。mysql
而這些,在java的反射技術下成爲了可能。應該說反射技術並不只僅在IOC容器中被使用,它是整個Spring框架的底層核心技術之一,是Spring實現通用型和擴展性的基石。web
下面這段是官方的定義spring
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
總結下官方的定義,咱們能夠知道反射技術的核心就兩點:sql
回顧下類加載的過程:當JVM須要使用某個類,但內存中不存在時,會將該類的字節碼文件加載進內存的方法區中,並在堆區建立一個Class對象。Class對象至關於存儲於方法區的字節碼信息的映射。咱們能夠經過該Class對象得到關於該類的全部描述信息:類名,訪問權限,類註解,構造方法,字段,方法等等,儘管真實的類信息並不存在於該對象中。但經過它咱們能得到想要的東西,並進行相關的操做,某種程度能夠認爲它們邏輯上等價。對類的結構進一步細分,類主要由構造方法,方法,字段構成。因此也必須存在和它們創建邏輯關係的映射。java在反射包下定義了Constructor類,Method類和Field類來創建和構造方法,方法,字段的映射。Constructor對象映射構造器方法,Method對象映射靜態或實例方法,Field對象映射類的字段,而Class對象映射整個字節碼文件(從字節碼文件中抽取的方法區的運行時數據結構),經過Class對象又能夠得到Method,Constructor,Field對象。它們之間的關係以下圖所示。
數組
經過這個圖像咱們對反射能夠創建更加直觀的認識。堆中的對象就像一面鏡子,反射出類所有或某一部分的面貌。經過這些對象,咱們能夠在運行時獲取類的所有信息;而且一樣經過這些對象,能夠完成建立類的實例,方法調用,字段賦值等操做。數據結構
咱們知道Class對象是進行反射操做的入口,因此首先必須得到Class對象。除了經過實例獲取外,Class對象主要由如下幾種方法得到:app
Class<?> clazz = Thread.currentThread().getContextClassLoader(). loadClass("com.takumiCX.reflect.ClassTest");
Class<?> clazz = Class.forName("com.takumiCX.reflect.ClassTest");
Class<ClassTest> clazz = ClassTest.class;
除了得到的Class對象的泛型類型信息不同外,還有一個不一樣點值得注意。只有2在得到class對象的同時會引發類的初始化,而1和3都不會。還記得得到jdbc鏈接前註冊驅動的操做嗎?這就是完成驅動註冊的代碼框架
Class.forName("com.mysql.jdbc.Driver");
該方法引發了com.mysql.jdbc.Driver類被加載進內存,同時引發了類的初始化,而註冊驅動的邏輯就是在Driver類中的靜態代碼塊中完成的,ide
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
而經過類.class或classLoad.loadClass()雖然會引發類加載進內存,但不會引發類的初始化。經過下面的例子能夠清楚的看到它們之間的區別:
/** * @author: takumiCX * @create: 2018-07-27 **/ public class ClassInitializeTest { public static void main(String[] args) throws ClassNotFoundException { Class<InitialTest> clazz = InitialTest.class; System.out.println("InitialTest.class:若是以前打印了初始化語句,說明該操做引發了類的初始化!"); Thread.currentThread().getContextClassLoader(). loadClass("com.takumiCX.reflect.InitialTest"); System.out.println("classLoader.loadClass:若是以前打印了初始化語句,說明該操做引發了類的初始化!"); Class.forName("com.takumiCX.reflect.InitialTest"); System.out.println("Class.forName:若是以前打印了初始化語句,說明該操做引發了類的初始化!"); } } class InitialTest{ static { System.out.println("ClassTest 初始化!"); } }
測試結果以下
Class類裏的方法比較多,如要是圍繞如何得到Method對象,Field對象,Constructor對象,Annotation對象的方法及其重載方法,固然也能夠得到類的父類,類實現的接口等信息。
/** * @author: takumiCX * @create: 2018-07-27 **/ public class Test { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException { Class<User> clazz = User.class; //根據構造參數類型得到指定的Constructor對象(包括非公有構造方法) Constructor<User> constructor = clazz.getDeclaredConstructor(String.class); System.out.println("得到帶String參數的Constructor:"+constructor); //得到指定字段名的Field對象(包括非公有字段) Field name = clazz.getDeclaredField("name"); System.out.println("得到字段名爲name的Field:"+name); //根據方法名和方法參數類型得到指定的Method對象(包括非公有方法) Method method = clazz.getDeclaredMethod("setName", String.class); System.out.println("得到帶String類型參數且方法名爲setName的Method:"+method); //得到類上指定的註解 MyAnnotation myAnnotation = clazz.getAnnotation(MyAnnotation.class); System.out.println("得到類上MyAnnotation類型的註解:"+myAnnotation); //得到類的全部實現接口 Class<?>[] interfaces = clazz.getInterfaces(); System.out.println("得到類實現的全部接口:"+interfaces); //得到包對象 Package apackage = clazz.getPackage(); System.out.println("得到類所在的包:"+apackage); } @MyAnnotation public static class User implements Iuser{ private String name; public User() { } public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) static @interface MyAnnotation{ } static interface Iuser { } }
除了得到類的常規信息外,類的參數類型(泛型)信息也能夠經過反射在運行時得到。泛型類不能用Class來表示,必須藉助於反射包下關於類型概念的其餘抽象結構。反射包下對類型這個複雜的概念進行了不一樣層次的抽象,咱們有必要知道這種抽象的層次結構以及不一樣的抽象對應着什麼樣的類型信息。
反射包下對類型這個概念進行了不一樣層級的抽象,它們之間的關係能夠用下面這張圖表示
/** * @author: takumiCX * @create: 2018-07-27 **/ abstract class GenericType<T> { } public class TestGenericType extends GenericType<String> { private Map<String, Integer> map; public Map<String,Integer> getMap(){ return map; } public void setMap(Map<String, Integer> map) { this.map = map; } public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException { //獲取Class對象 Class<TestGenericType> clazz = TestGenericType.class; System.out.println("獲取類的參數化類型信息:"); //1.獲取類的參數化類型信息 Type type = clazz.getGenericSuperclass();//獲取帶泛型的父類類型 if (type instanceof ParameterizedType) { //判斷是否參數化類型 Type[] types = ((ParameterizedType) type).getActualTypeArguments(); //得到參數的實際類型 for (Type type1 : types) { System.out.println(type1); } } System.out.println("--------------------------"); System.out.println("獲取字段上的參數化類型信息:"); //獲取字段上的參數化類型信息 Field field = clazz.getDeclaredField("map"); Type type1 = field.getGenericType(); Type[] types = ((ParameterizedType) type1).getActualTypeArguments(); for(Type type2:types){ System.out.println(type2); } System.out.println("--------------------------"); System.out.println("獲取方法參數的參數化類型信息:"); //獲取方法參數的參數化類型信息 Method method = clazz.getDeclaredMethod("setMap",Map.class); Type[] types1 = method.getGenericParameterTypes(); for(Type type2:types1){ if(type2 instanceof ParameterizedType){ Type[] typeArguments = ((ParameterizedType) type2).getActualTypeArguments(); for(Type type3:typeArguments){ System.out.println(type3); } } } System.out.println("--------------------------"); System.out.println("獲取方法返回值的參數化類型信息:"); //獲取方法返回值得參數化類型信息 Method method1 = clazz.getDeclaredMethod("getMap"); Type returnType = method1.getGenericReturnType(); if(returnType instanceof ParameterizedType){ Type[] arguments = ((ParameterizedType) returnType).getActualTypeArguments(); for(Type type2:arguments){ System.out.println(type2); } } } }
/** * @author: takumiCX * @create: 2018-07-27 **/ abstract class GenericType2<T> { protected Class<T> tClass; public GenericType2() { Class<? extends GenericType2> aClass = this.getClass(); Type superclass = aClass.getGenericSuperclass(); if(superclass instanceof ParameterizedType){ Type[] typeArguments = ((ParameterizedType) superclass).getActualTypeArguments(); tClass=(Class<T>) typeArguments[0]; } } } public class TestGenericType2 extends GenericType2<String>{ public static void main(String[] args) { TestGenericType2 type2 = new TestGenericType2(); System.out.println(type2.tClass); } }
java裏的的泛型只在源碼階段存在,編譯的時候就會被擦除,聲明中的泛型類型信息會變成Object或泛型上界的類型,而使用時都用Object替換,若是要返回泛型類型,則經過強轉的方式完成。咱們能夠寫一個泛型類,將其編譯成字節碼文件後再反編譯看看發生了什麼。
/** * @author: takumiCX * @create: 2018-07-27 **/ abstract class GenericType<T> { } public class TestGenericType extends GenericType<String> { private Map<String, Integer> map; public Map<String,Integer> getMap(){ return map; } public void setMap(Map<String, Integer> map) { this.map = map; } public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); } }
package com.takumiCX.reflect; import java.util.ArrayList; import java.util.Map; // Referenced classes of package com.takumiCX.reflect: // GenericType public class TestGenericType extends GenericType { public TestGenericType() { } public Map getMap() { return map; } public void setMap(Map map) { this.map = map; } public static void main(String args[]) { ArrayList list = new ArrayList(); } private Map map; }
反編譯後的源碼泛型信息所有消失了。說明編譯器在編譯源代碼的時候已經把泛型的類型信息擦除。理論上來講,源碼中指定的具體的泛型類型,在運行時是沒法知道的。可是5.2和5.3的例子裏咱們確實經過反射在運行時獲得了類,字段,方法參數以及方法返回值的泛型類型信息。那麼問題出在哪裏?關於這個問題我也是納悶了很久,在網上找了不少資料才得出比較靠譜的答案。泛型若是被用來進行聲明,好比說類上,字段上,方法參數和方法返回值上,這些屬於類的結構信息實際上是會被編譯進Class文件中的;而泛型若是被用來使用,常見的方法體中帶泛型的局部變量,其類型信息不會被編譯進Class文件中。前者由於存在於Class文件中,因此運行時經過反射仍是可以得到其類型信息的;然後者由於在Class文件中根本不存在,反射也就無能爲力了。
/** * @author: takumiCX * @create: 2018-07-27 **/ public class ReflectOpration { private String name; public ReflectOpration(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return "ReflectOpration{" + "name='" + name + '\'' + '}'; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { Class<ReflectOpration> clazz = ReflectOpration.class; //獲取帶參構造器 Constructor<ReflectOpration> constructor = clazz.getConstructor(String.class); //反射建立實例,傳入構造器參數takumiCX ReflectOpration instance = constructor.newInstance("takumiCX"); System.out.println(instance); //根據方法名獲取指定方法 Method getName = clazz.getMethod("getName"); //經過反射進行方法調用,傳入進行調用的對象做參數,後面可跟上方法參數 String res = (String) getName.invoke(instance); System.out.println(res); //獲取Field對象 Field field = clazz.getDeclaredField("name"); //修改訪問權限 field.setAccessible(true); //反射修改字段,將名字改成全大寫 field.set(instance,"TAKUMICX"); System.out.println(instance); } }
反射功能強大,使用它咱們幾乎能夠作到java語言層面支持的任何事情。但要注意到這種強大是有代價的。過多的使用反射可能會帶來嚴重的性能問題。曾今做支付平臺的系統改造時就碰到過前人濫用反射留下的坑,由於模型對象在web,業務和持久層是不一樣,但其屬性基本同樣,因此原來的開發人員爲了偷懶大量使用反射來進行這種對象屬性的拷貝操做。開發時間是節省了,但給系統性能帶來嚴重的負擔,支付接口的調用時間太長,甚至會超時。後來咱們將其改回了手動調用setter賦值的方式,雖然工做量很多,可是最後上線的系統性能有了很大的提升,接口調用的響應時間比原來少了近30%。這個例子說明了對反射合理使用的重要性:框架中大量使用反射是由於要提供一套通用的處理流程來減小開發者的工做量,且大部分都在準備或者說容器啓動階段,反射的使用雖然增長了容器啓動時間,但由於提升了開發效率,因此是能夠接受的;而在對性能有要求的業務代碼層面,使用反射會下降業務處理的速度,拖慢接口的響應時間,不少時候是不可接受的。反射必定要在權衡了開發效率和執行性能後,視場景和性能要求謹慎使用。