實例分析講解java中的反射機制
動態語言
動態語言,是指程序在運行時可以改變其結構:新的函數可以被引進,已有的函數可以被刪除等在結構上的變化。比如眾所周知的ECMA(Java)便是一個動態語言。除此之外如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文檔
獲取類內信息
一些判斷類本身信息的方法
使用反射生成并操作對象:
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. */publicclassObjectPool{privateMap《String, Object》 pool; privateObjectPool(Map《String, Object》 pool) { this.pool = pool; } privatestaticObjectgetInstance(String className) throwsClassNotFoundException, IllegalAccessException, InstantiationException { returnClass.forName(className).newInstance(); }privatestaticJSONArray getObjects(String config) throwsIOException { Reader reader =newInputStreamReader(ClassLoader.getSystemResourceAsStream(config));returnJSONObject.parseObject(CharStreams.toString(reader)).getJSONArray( “objects”); } // 根據指定的JSON配置文件來初始化對象池publicstaticObjectPool init(String config) {try{ JSONArray objects = getObjects(config); ObjectPool pool = newObjectPool(newHashMap《String, Object》()); if(objects != null&& objects.size() != 0) { for( inti = 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)); } } returnpool; }catch(IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { thrownewRuntimeException(e); } } publicObjectgetObject(String id) { returnpool.get(id); } publicvoidputObject(String id, Object object) { pool.put(id, object); } publicvoidclear() { pool.clear(); } }
Client
/** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassClient{@Testpublicvoidclient() { 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
publicclass User { privateint id; privateStringname; privateStringpassword; publicint getId() { returnid; } publicvoidsetId( Integerid) { this .id =id; } publicStringgetName() { returnname; } publicvoidsetName( Stringname) { this .name =name; } publicStringgetPassword() {returnpassword; } publicvoidsetPassword( Stringpassword) { this .password =password; } @Override publicStringtoString() { return“User{”+“id=”+id +“, name=‘”+name +’/‘’ + “, password=‘” + password + ’/‘’ + ‘}’; } }
Bean
publicclass Bean { privateBoolean usefull; privateBigDecimal rate; privateStringname;publicBoolean getUsefull() { returnusefull; } publicvoidsetUsefull(Boolean usefull) { this.usefull =usefull; } publicBigDecimal getRate() { returnrate; } publicvoidsetRate(BigDecimal rate) { this .rate =rate; } publicStringgetName() { returnname; } publicvoidsetName(Stringname) { this .name =name; } @Override publicStringtoString() {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,但是一定要引用一個已經存在了的對象)
/** *@authorjifang *@since15/12/31下午4:00 */publicclassObjectPool{privateMap《String, Object》 pool; privateObjectPool(Map《String, Object》 pool) { this.pool = pool; }privatestaticJSONArray getObjects(String config) throwsIOException { Reader reader =newInputStreamReader(ClassLoader.getSystemResourceAsStream(config));returnJSONObject.parseObject(CharStreams.toString(reader)).getJSONArray( “objects”); } privatestaticObject getInstance(String className, JSONArray fields)throwsClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { // 配置的ClassClass《?》 clazz = Class.forName(className); // 目標Class的實例對象Object targetObject = clazz.newInstance(); if(fields != null&& fields.size() != 0) { for( inti = 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”); } elseif(field.containsKey( “ref”)) { String refBeanId = field.getString( “ref”); fieldValue = OBJECTPOOL.getObject(refBeanId); }else{ thrownewRuntimeException( “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); } } returntargetObject; } privatestaticObjectPool OBJECTPOOL; // 創建一個對象池的實例(保證是多線程安全的)privatestaticvoidinitSingletonPool() { if(OBJECTPOOL ==null) { synchronized(ObjectPool.class) { if(OBJECTPOOL == null) { OBJECTPOOL =newObjectPool( newConcurrentHashMap《String, Object》()); } } } } // 根據指定的JSON配置文件來初始化對象池publicstaticObjectPool init(String config) { // 初始化poolinitSingletonPool(); try{ JSONArray objects = getObjects(config); for( inti = 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”))); }returnOBJECTPOOL; } catch(IOException | ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { thrownewRuntimeException(e); } } publicObjectgetObject(String id) { returnpool.get(id); } publicvoidputObject(String id, Object object) { pool.put(id, object); } publicvoidclear() { pool.clear(); } }
Client
/** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassClient{@Testpublicvoidclient() { 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
publicclass ComplexBean { privateStringname; privateBean refBean;publicStringgetName() { returnname; } publicvoidsetName( Stringname) { this .name=name; } publicBean getRefBean() { returnrefBean; } publicvoidsetRefBean(Bean refBean) { this .refBean =refBean; } @Override publicStringtoString() {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;
/** *@authorjifang *@since16/1/2下午1:00. */publicclassClient{@Testpublicvoidclient()throwsNoSuchFieldException, IllegalAccessException { User user = newUser(); 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); }privatevoidsetAccessible(AccessibleObject object) { object.setAccessible( true); } }使用反射獲取泛型信息
為了通過反射操作泛型以迎合實際開發的需要, Java新增了java.lang.reflect.ParameterizedType java.lang.reflect.GenericArrayTypejava.lang.reflect.TypeVariable java.lang.reflect.WildcardType幾種類型來代表不能歸一到Class類型但是又和原始類型同樣重要的類型。
其中, 我們可以使用ParameterizedType來獲取泛型信息。
/** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassClient{privateMap《String, Object》 objectMap; publicvoidtest(Map《String, User》 map, String string) { } publicMap《User, Bean》 test() { returnnull; } /** * 測試屬性類型 * *@throwsNoSuchFieldException */@TestpublicvoidtestFieldType()throwsNoSuchFieldException { 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 instanceofParameterizedType) { ParameterizedType pType = (ParameterizedType) gType; Type[] types = pType.getActualTypeArguments(); for(Type type : types) { System.out.println(type.toString()); } } } /** * 測試參數類型 * *@throwsNoSuchMethodException */@TestpublicvoidtestParamType()throwsNoSuchMethodException { 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 instanceofParameterizedType) { Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments(); for(Type actualType : actualTypes) { System.out.println( “/tactual type -》 ”+ actualType); } } } } /** * 測試返回值類型 * *@throwsNoSuchMethodException */@TestpublicvoidtestReturnType()throwsNoSuchMethodException { Method testMethod = Client.class.getMethod( “test”); Type returnType = testMethod.getGenericReturnType(); System.out.println( “return type -》 ”+ returnType); if(returnType instanceofParameterizedType) { Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments(); for(Type actualType : actualTypes) { System.out.println( “/tactual type -》 ”+ actualType); } } } }反射性能測試
Method/Constructor/Field/Element都繼承了AccessibleObject,AccessibleObject類中有一個setAccessible方法:
public void setAccessible(boolean flag) throws SecurityException { 。。.}
該方法有兩個作用:
啟用/禁用訪問安全檢查開關:值為true,則指示反射的對象在使用時取消Java語言訪問檢查;值為false,則指示應該實施Java語言的訪問檢查;
可以禁止安全檢查, 提高反射的運行效率。
/** *@authorjifang *@since15/12/31下午4:53. */ public classTestReflect{ @Before publicvoid testNoneReflect() { User user = newUser(); longstart = System.currentTimeMillis();for( longi = 0; i 《 Integer.MAX_VALUE; ++i) { user.getName(); } longcount = System.currentTimeMillis() - start; System.out.println( “沒有反射, 共消耗 《”+ count + “》 毫秒”); } @Test public void testNotAccess() throwsClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { User user = newUser(); Method method = Class.forName(“com.fq.domain.User”).getMethod( “getName”); longstart = System.currentTimeMillis();for( longi = 0; i 《 Integer.MAX_VALUE; ++i) { method.invoke(user, null); } longcount = System.currentTimeMillis() - start; System.out.println( “沒有訪問權限, 共消耗 《”+ count +“》 毫秒”); } @After public void testUseAccess() throwsClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { User user = newUser(); Method method = Class.forName(“com.fq.domain.User”).getMethod( “getName”); method.setAccessible( true); longstart = System.currentTimeMillis(); for( longi = 0; i 《 Integer.MAX_VALUE; ++i) { method.invoke(user, null); } longcount = System.currentTimeMillis() - start; System.out.println( “有訪問權限, 共消耗 《”+ count + “》 毫秒”); } }
執行上面程序,在我的機器上會有如下結果:
機器配置信息如下:
可以看到使用反射會比直接調用慢3000毫秒,但是前提是該方法會執行20E+次(而且服務器的性能也肯定比我的機器要高),因此在我們的實際開發中,其實是不用擔心反射機制帶來的性能消耗的,而且禁用訪問權限檢查,也會有性能的提升。
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%