Java 反射

1. 動態語言

動態語言,是指程序在運行時可以改變其結構:新的函數可以被引進,已有的函數可以被刪除等在結構上的變化。比如衆所周知的ECMAScript(JavaScript)便是一個動態語言。除此之外如Ruby、Python等也都屬於動態語言,而C、C++等語言則不屬於動態語言。(引自: 百度百科)

var execString = "alert(Math.floor(Math.random()*10));";
eval(execString);

2.Class反射機制

  • 指的是可以於運行時加載,探知和使用編譯期間完全未知的類.
  • 程序在運行狀態中, 可以動態加載一個只有名稱的類, 對於任意一個已經加載的類,都能夠知道這個類的所有屬性和方法; 對於任意一個對象,都能調用他的任意一個方法和屬性;
  • 加載完類之後, 在堆內存中會產生一個Class類型的對象(一個類只有一個Class對象), 這個對象包含了完整的類的結構信息,而且這個Class對象就像一面鏡子,透過這個鏡子看到類的結構,所以被稱之爲:反射。
  • 每個類被加載進入內存之後,系統就會爲該類生成一個對應的java.lang.Class對象,通過該Class對象就可以訪問到JVM中的這個類.

3.Class對象的獲取

  • 對象的getClass()方法;
  • 類的.class(最安全/性能最好)屬性;
  • 運用Class.forName(String className)動態加載類,className需要是類的全限定名(最常用).

4.從Class中獲取信息

Class類提供了大量的實例方法來獲取該Class對象所對應的詳細信息,Class類大致包含如下方法,其中每個方法都包含多個重載版本,因此這裏只是做簡單的介紹,詳細請參考JDK文檔

  • 獲取類內信息
獲取內容 方法簽名
構造器 Constructor 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對象直接訪問和修改對象的成員變量值.

5.創建對象

通過反射來生成對象的方式有兩種:

  • 使用Class對象的newInstance()方法來創建該Class對象對應類的實例(這種方式要求該Class對象的對應類有默認構造器).
  • 先使用Class對象獲取指定的Constructor對象, 再調用Constructor對象的newInstance()方法來創建該Class對象對應類的實例(通過這種方式可以選擇指定的構造器來創建實例).

通過第一種方式來創建對象比較常見, 像Spring這種框架都需要根據配置文件(如applicationContext.xml)信息來創建Java對象,從配置文件中讀取的只是某個類的全限定名字符串,程序需要根據該字符串來創建對應的實例,就必須使用默認的構造器來反射對象.

下面我們就模擬Spring實現一個簡單的對象池, 該對象池會根據文件讀取key-value對, 然後創建這些對象, 並放入Map中.

  • 配置文件
{
  "objects": [
    {
      "id": "id1",
      "class": "com.java.test.User"
    },
    {
      "id": "id2",
      "class": "com.java.test.Bean"
    }
  ]
}
  • ObjectPool
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>

6.調用方法

當獲取到某個類對應的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.java.test.User",
      "fields": [
        {
          "name": "id",
          "value": 101
        },
        {
          "name": "name",
          "value": "feiqing"
        },
        {
          "name": "password",
          "value": "ICy5YqxZB1uWSwcVLSNLcA=="
        }
      ]
    },
    {
      "id": "id2",
      "class": "com.java.test.Bean",
      "fields": [
        {
          "name": "usefull",
          "value": true
        },
        {
          "name": "rate",
          "value": 3.14
        },
        {
          "name": "name",
          "value": "bean-name"
        }
      ]
    },
    {
      "id": "id3",
      "class": "com.java.test.ComplexBean",
      "fields": [
        {
          "name": "name",
          "value": "complex-bean-name"
        },
        {
          "name": "refBean",
          "ref": "id2"
        }
      ]
    }
  ]
}

其中fields代表該Bean所包含的屬性, name爲屬性名稱, value爲屬性值(屬性類型爲JSON支持的類型), ref代表引用一個對象(也就是屬性類型爲Object,但是一定要引用一個已經存在了的對象)

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作爲配置文件).

7.訪問成員變量

通過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;

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);
    }
}

8. 使用反射獲取泛型信息

爲了通過反射操作泛型以迎合實際開發的需要, Java新增了

java.lang.reflect.ParameterizedType;
java.lang.reflect.GenericArrayTypejava.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);
            }
        }
    }
}

9.反射性能測試

Method/Constructor/Field/Element都繼承了AccessibleObject,AccessibleObject類中有一個setAccessible方法:

public void setAccessible(boolean flag) throws SecurityException {
    ...
}

該方法有兩個作用:

  • 啓用/禁用訪問安全檢查開關:值爲true,則指示反射的對象在使用時取消Java語言訪問檢查;值爲false,則指示應該實施Java語言的訪問檢查;
  • 可以禁止安全檢查, 提高反射的運行效率.
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.java.test.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.java.test.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+次,因此在我們的實際開發中,其實是不用擔心反射機制帶來的性能消耗的,而且禁用訪問權限檢查,也會有性能的提升.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章