Java 反射機制詳解
動態語言
動態語言,是指程式在執行時可以改變其結構:新的函式可以被引進,已有的函式可以被刪除等在結構上的變化。比如眾所周知的ECMAScript(JavaScript)便是一個動態語言。除此之外如Ruby、Python等也都屬於動態語言,而C、C++等語言則不屬於動態語言。(引自: 百度百科)
var execString = "alert(Math.floor(Math.random()*10));"; eval(execString);
Class
反射機制
- 指的是可以於執行時載入,探知和使用編譯期間完全未知的類.
- 程式在執行狀態中, 可以動態載入一個只有名稱的類, 對於任意一個已經載入的類,都能夠知道這個類的所有屬性和方法; 對於任意一個物件,都能呼叫他的任意一個方法和屬性;
- 載入完類之後, 在堆記憶體中會產生一個
Class
型別的物件(一個類只有一個Class物件), 這個物件包含了完整的類的結構資訊,而且這個Class
物件就像一面鏡子,透過這個鏡子看到類的結構,所以被稱之為:反射。
Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions(維度). The primitive Java types (boolean, byte, char, short, int, long, float, anddouble), and the keyword void are also represented as Class objects.
- 每個類被載入進入記憶體之後,系統就會為該類生成一個對應的
java.lang.Class
物件,通過該Class
物件就可以訪問到JVM中的這個類.
Class物件的獲取
- 物件的
getClass()
方法; - 類的
.class
(最安全/效能最好)屬性; - 運用
Class.forName(String className)
動態載入類,className
需要是類的全限定名(最常用).
從Class中獲取資訊
Class
類提供了大量的例項方法來獲取該Class物件所對應的詳細資訊,Class類大致包含如下方法,其中每個方法都包含多個過載版本,因此我們只是做簡單的介紹,詳細請參考JDK文件
獲取類內資訊
獲取內容 | 方法簽名 |
---|---|
構造器 | Constructor<T> getConstructor(Class<?>... parameterTypes) |
包含的方法 | Method getMethod(String name, Class<?>... parameterTypes) |
包含的屬性 | Field getField(String name) |
包含的Annotation |
<A extends Annotation> A getAnnotation(Class<A> annotationClass) |
內部類 | Class<?>[] getDeclaredClasses() |
外部類 | Class<?> getDeclaringClass() |
所實現的介面 | Class<?>[] getInterfaces() |
修飾符 | int getModifiers() |
所在包 | Package getPackage() |
類名 | String getName() |
簡稱 | String getSimpleName() |
一些判斷類本身資訊的方法
判斷內容 | 方法簽名 |
---|---|
註解型別? | boolean isAnnotation() |
使用了該Annotation 修飾? |
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) |
匿名類? | boolean isAnonymousClass() |
陣列? | boolean isArray() |
列舉? | boolean isEnum() |
原始型別? | boolean isPrimitive() |
介面? | boolean isInterface() |
obj 是否是該Class 的例項 |
boolean isInstance(Object obj) |
使用反射生成並操作物件:
Method Constructor Field這些類都實現了java.lang.reflect.Member介面,程式可以通過Method物件來執行相應的方法,通過Constructor物件來呼叫對應的構造器建立例項,通過Filed物件直接訪問和修改物件的成員變數值.
建立物件
通過反射來生成物件的方式有兩種:
- 使用
Class
物件的newInstance()
方法來建立該Class
物件對應類的例項(這種方式要求該Class物件的對應類有預設構造器). - 先使用
Class
物件獲取指定的Constructor
物件, 再呼叫Constructor物件的newInstance()
方法來建立該Class物件對應類的例項(通過這種方式可以選擇指定的構造器來建立例項).
通過第一種方式來建立物件比較常見, 像Spring這種框架都需要根據配置檔案(如applicationContext.xml
)資訊來建立Java物件,從配置檔案中讀取的只是某個類的全限定名字串,程式需要根據該字串來建立對應的例項,就必須使用預設的構造器來反射物件.
下面我們就模擬Spring實現一個簡單的物件池, 該物件池會根據檔案讀取key-value對, 然後建立這些物件, 並放入Map
中.
配置檔案
{ "objects": [ { "id": "id1", "class": "com.fq.domain.User" }, { "id": "id2", "class": "com.fq.domain.Bean" } ] }
ObjectPool
/** * Created by jifang on 15/12/31. */ public class ObjectPool { private Map<String, Object> pool; private ObjectPool(Map<String, Object> pool) { this.pool = pool; } private static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException { return Class.forName(className).newInstance(); } private static JSONArray getObjects(String config) throws IOException { Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config)); return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects"); } // 根據指定的JSON配置檔案來初始化物件池 public static ObjectPool init(String config) { try { JSONArray objects = getObjects(config); ObjectPool pool = new ObjectPool(new HashMap<String, Object>()); if (objects != null && objects.size() != 0) { for (int i = 0; i < objects.size(); ++i) { JSONObject object = objects.getJSONObject(i); if (object == null || object.size() == 0) { continue; } String id = object.getString("id"); String className = object.getString("class"); pool.putObject(id, getInstance(className)); } } return pool; } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } public Object getObject(String id) { return pool.get(id); } public void putObject(String id, Object object) { pool.put(id, object); } public void clear() { pool.clear(); } }
Client
public class Client { @Test public void client() { ObjectPool pool = ObjectPool.init("config.json"); User user = (User) pool.getObject("id1"); System.out.println(user); Bean bean = (Bean) pool.getObject("id2"); System.out.println(bean); } }
User
public class User { private int id; private String name; private String password; public int getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '/'' + ", password='" + password + '/'' + '}'; } }
Bean
public class Bean { private Boolean usefull; private BigDecimal rate; private String name; public Boolean getUsefull() { return usefull; } public void setUsefull(Boolean usefull) { this.usefull = usefull; } public BigDecimal getRate() { return rate; } public void setRate(BigDecimal rate) { this.rate = rate; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Bean{" + "usefull=" + usefull + ", rate=" + rate + ", name='" + name + '/'' + '}'; } }
注意: 需要在pom.xml中新增如下依賴:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.7</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency>
呼叫方法
當獲取到某個類對應的Class物件之後, 就可以通過該Class物件的getMethod
來獲取一個Method陣列或Method物件.每個Method物件對應一個方法,在獲得Method物件之後,就可以通過呼叫invoke方法來呼叫該Method物件對應的方法.
@CallerSensitive public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { ... }
下面我們對上面的物件池加強:可以看到Client
獲取到的物件的成員變數全都是預設值,既然我們已經使用了JSON這麼優秀的工具,我們又學習了動態呼叫物件的方法,那麼我們就通過配置檔案來給物件設定值(在物件建立時), 新的配置檔案形式如下:
{ "objects": [ { "id": "id1", "class": "com.fq.domain.User", "fields": [ { "name": "id", "value": 101 }, { "name": "name", "value": "feiqing" }, { "name": "password", "value": "ICy5YqxZB1uWSwcVLSNLcA==" } ] }, { "id": "id2", "class": "com.fq.domain.Bean", "fields": [ { "name": "usefull", "value": true }, { "name": "rate", "value": 3.14 }, { "name": "name", "value": "bean-name" } ] }, { "id": "id3", "class": "com.fq.domain.ComplexBean", "fields": [ { "name": "name", "value": "complex-bean-name" }, { "name": "refBean", "ref": "id2" } ] } ] }
其中fields
代表該Bean
所包含的屬性, name
為屬性名稱, value
為屬性值(屬性型別為JSON支援的型別), ref
代表引用一個物件(也就是屬性型別為Object
,但是一定要引用一個已經存在了的物件)
/** * @author jifang * @since 15/12/31下午4:00 */ public class ObjectPool { private Map<String, Object> pool; private ObjectPool(Map<String, Object> pool) { this.pool = pool; } private static JSONArray getObjects(String config) throws IOException { Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config)); return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects"); } private static Object getInstance(String className, JSONArray fields) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { // 配置的Class Class<?> clazz = Class.forName(className); // 目標Class的例項物件 Object targetObject = clazz.newInstance(); if (fields != null && fields.size() != 0) { for (int i = 0; i < fields.size(); ++i) { JSONObject field = fields.getJSONObject(i); // 需要設定的成員變數名 String fieldName = field.getString("name"); // 需要設定的成員變數的值 Object fieldValue; if (field.containsKey("value")) { fieldValue = field.get("value"); } else if (field.containsKey("ref")) { String refBeanId = field.getString("ref"); fieldValue = OBJECTPOOL.getObject(refBeanId); } else { throw new RuntimeException("neither value nor ref"); } String setterName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); // 需要設定的成員變數的setter方法 Method setterMethod = clazz.getMethod(setterName, fieldValue.getClass()); // 呼叫setter方法將值設定進去 setterMethod.invoke(targetObject, fieldValue); } } return targetObject; } private static ObjectPool OBJECTPOOL; // 建立一個物件池的例項(保證是多執行緒安全的) private static void initSingletonPool() { if (OBJECTPOOL == null) { synchronized (ObjectPool.class) { if (OBJECTPOOL == null) { OBJECTPOOL = new ObjectPool(new ConcurrentHashMap<String, Object>()); } } } } // 根據指定的JSON配置檔案來初始化物件池 public static ObjectPool init(String config) { // 初始化pool initSingletonPool(); try { JSONArray objects = getObjects(config); for (int i = 0; objects != null && i < objects.size(); ++i) { JSONObject object = objects.getJSONObject(i); if (object == null || object.size() == 0) { continue; } String id = object.getString("id"); String className = object.getString("class"); // 初始化bean並放入池中 OBJECTPOOL.putObject(id, getInstance(className, object.getJSONArray("fields"))); } return OBJECTPOOL; } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new RuntimeException(e); } } public Object getObject(String id) { return pool.get(id); } public void putObject(String id, Object object) { pool.put(id, object); } public void clear() { pool.clear(); } }
Client
public class Client { @Test public void client() { ObjectPool pool = ObjectPool.init("config.json"); User user = (User) pool.getObject("id1"); System.out.println(user); Bean bean = (Bean) pool.getObject("id2"); System.out.println(bean); ComplexBean complexBean = (ComplexBean) pool.getObject("id3"); System.out.println(complexBean); } }
ComplexBean
public class ComplexBean { private String name; private Bean refBean; public String getName() { return name; } public void setName(String name) { this.name = name; } public Bean getRefBean() { return refBean; } public void setRefBean(Bean refBean) { this.refBean = refBean; } @Override public String toString() { return "ComplexBean{" + "name='" + name + '/'' + ", refBean=" + refBean + '}'; } }
Spring框架就是通過這種方式將成員變數值以及依賴物件等都放在配置檔案中進行管理的,從而實現了較好地解耦(不過Spring是通過XML作為配置檔案).
訪問成員變數
通過Class
物件的的getField()
方法可以獲取該類所包含的全部或指定的成員變數Field
,Filed
提供瞭如下兩組方法來讀取和設定成員變數值.
getXxx(Object obj)
: 獲取obj物件的該成員變數的值, 此處的Xxx對應8中基本型別,如果該成員變數的型別是引用型別, 則取消get後面的Xxx;setXxx(Object obj, Xxx val)
: 將obj物件的該成員變數值設定成val值.此處的Xxx對應8種基本型別, 如果該成員型別是引用型別, 則取消set後面的Xxx;
注: getDeclaredXxx方法可以獲取所有的成員變數,無論private/public;
/** * @author jifang * @since 16/1/2下午1:00. */ public class Client { @Test public void client() throws NoSuchFieldException, IllegalAccessException { User user = new User(); Field idFiled = User.class.getDeclaredField("id"); setAccessible(idFiled); idFiled.setInt(user, 46); Field nameFiled = User.class.getDeclaredField("name"); setAccessible(nameFiled); nameFiled.set(user, "feiqing"); Field passwordField = User.class.getDeclaredField("password"); setAccessible(passwordField); passwordField.set(user, "ICy5YqxZB1uWSwcVLSNLcA=="); System.out.println(user); } private void setAccessible(AccessibleObject object) { object.setAccessible(true); } }
使用反射獲取泛型資訊
為了通過反射操作泛型以迎合實際開發的需要, Java新增了java.lang.reflect.ParameterizedType
java.lang.reflect.GenericArrayType
java.lang.reflect.TypeVariable
java.lang.reflect.WildcardType
幾種型別來代表不能歸一到Class型別但是又和原始型別同樣重要的型別.
型別 | 含義 |
---|---|
ParameterizedType |
一種引數化型別, 比如Collection<String> |
GenericArrayType |
一種元素型別是引數化型別或者型別變數的陣列型別 |
TypeVariable |
各種型別變數的公共介面 |
WildcardType |
一種萬用字元型別表示式, 如? ? extends Number ? super Integer |
其中, 我們可以使用ParameterizedType
來獲取泛型資訊.
public class Client { private Map<String, Object> objectMap; public void test(Map<String, User> map, String string) { } public Map<User, Bean> test() { return null; } /** * 測試屬性型別 * * @throws NoSuchFieldException */ @Test public void testFieldType() throws NoSuchFieldException { Field field = Client.class.getDeclaredField("objectMap"); Type gType = field.getGenericType(); // 列印type與generic type的區別 System.out.println(field.getType()); System.out.println(gType); System.out.println("**************"); if (gType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) gType; Type[] types = pType.getActualTypeArguments(); for (Type type : types) { System.out.println(type.toString()); } } } /** * 測試引數型別 * * @throws NoSuchMethodException */ @Test public void testParamType() throws NoSuchMethodException { Method testMethod = Client.class.getMethod("test", Map.class, String.class); Type[] parameterTypes = testMethod.getGenericParameterTypes(); for (Type type : parameterTypes) { System.out.println("type -> " + type); if (type instanceof ParameterizedType) { Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments(); for (Type actualType : actualTypes) { System.out.println("/tactual type -> " + actualType); } } } } /** * 測試返回值型別 * * @throws NoSuchMethodException */ @Test public void testReturnType() throws NoSuchMethodException { Method testMethod = Client.class.getMethod("test"); Type returnType = testMethod.getGenericReturnType(); System.out.println("return type -> " + returnType); if (returnType instanceof ParameterizedType) { Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments(); for (Type actualType : actualTypes) { System.out.println("/tactual type -> " + actualType); } } } }
使用反射獲取註解
使用反射獲取註解資訊的相關介紹, 請參看我的部落格Java註解實踐
反射效能測試
Method/Constructor/Field/Element
都繼承了AccessibleObject
,AccessibleObject
類中有一個setAccessible
方法:
public void setAccessible(boolean flag) throws SecurityException { ... }
該方法有兩個作用:
1. 啟用/禁用訪問安全檢查開關:值為true,則指示反射的物件在使用時取消Java語言訪問檢查;值為false,則指示應該實施Java語言的訪問檢查;
2. 可以禁止安全檢查, 提高反射的執行效率.
/** * @author jifang * @since 15/12/31下午4:53. */ public class TestReflect { @Before public void testNoneReflect() { User user = new User(); long start = System.currentTimeMillis(); for (long i = 0; i < Integer.MAX_VALUE; ++i) { user.getName(); } long count = System.currentTimeMillis() - start; System.out.println("沒有反射, 共消耗 <" + count + "> 毫秒"); } @Test public void testNotAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { User user = new User(); Method method = Class.forName("com.fq.domain.User").getMethod("getName"); long start = System.currentTimeMillis(); for (long i = 0; i < Integer.MAX_VALUE; ++i) { method.invoke(user, null); } long count = System.currentTimeMillis() - start; System.out.println("沒有訪問許可權, 共消耗 <" + count + "> 毫秒"); } @After public void testUseAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { User user = new User(); Method method = Class.forName("com.fq.domain.User").getMethod("getName"); method.setAccessible(true); long start = System.currentTimeMillis(); for (long i = 0; i < Integer.MAX_VALUE; ++i) { method.invoke(user, null); } long count = System.currentTimeMillis() - start; System.out.println("有訪問許可權, 共消耗 <" + count + "> 毫秒"); } }
執行上面程式,在我的機器上會有如下結果:
機器配置資訊如下:
可以看到使用反射會比直接呼叫慢3000毫秒
,但是前提是該方法會執行20E+次(而且伺服器的效能也肯定比我的機器要高),因此在我們的實際開發中,其實是不用擔心反射機制帶來的效能消耗的,而且禁用訪問許可權檢查,也會有效能的提升。
相關文章
- JAVA中的反射機制詳解Java反射
- Java 型別資訊詳解和反射機制Java型別反射
- 詳解 php 反射機制原理PHP反射
- Java註解與反射機制Java反射
- JS 反射機制及 Reflect 詳解JS反射
- Java反射機制Java反射
- 深入詳解Java反射機制與底層實現原理?Java反射
- Java核心反射機制Java反射
- java利器——反射機制Java反射
- Java的反射機制Java反射
- Java反射機制(轉)Java反射
- Java反射機制研究Java反射
- java反射詳解Java反射
- Java 反射詳解Java反射
- Java SPI機制詳解Java
- Java反射機制那些事Java反射
- Java 中的 反射機制Java反射
- Java反射機制簡答Java反射
- 說說 Java 反射機制Java反射
- JAVA(五)反射機制/AnnotationJava反射
- Java 反射機制分析指南Java反射
- Java利用spring註解做反射機制JavaSpring反射
- java反射詳解(轉)Java反射
- 一文帶你瞭解Java反射機制Java反射
- 從Java反射機制到Android註解框架Java反射Android框架
- Java-SPI機制詳解Java
- Java中的類反射機制Java反射
- Java筆記-反射機制(一)Java筆記反射
- java進階(41)--反射機制Java反射
- 淺析java的反射機制Java反射
- 小白都能學會的Java註解與反射機制Java反射
- Java併發控制機制詳解Java
- Java 類載入機制詳解Java
- Java 動態代理機制詳解Java
- Java介面回撥機制詳解Java
- java反射機制的學習心得Java反射
- 關於Java中的反射機制Java反射
- Java基礎之反射機制(續)Java反射