什麼是反射?

1、反射的定義

本文基於 JDK8,Oracle官網對反射的解釋是html

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.

反射使 Java 代碼能夠發現有關已加載類的字段,方法和構造函數的信息,並在安全性限制內使用反射對這些字段,方法和構造函數進行操做。java

簡而言之,指在 Java 程序運行時git

  • 給定的一個類(Class)對象,經過反射獲取這個類(Class)對象的全部成員結構。
  • 給定的一個具體的對象,可以動態地調用它的方法及對任意屬性值進行獲取和賦值。

這種動態獲取類的內容,建立對象、以及動態調用對象的方法及操做屬性的機制爲反射。即便該對象的類型在編譯期間是未知,該類的 .class 文件不存在,也能夠經過反射直接建立對象。github

優點spring

  • 增長程序的靈活性,避免將固有的邏輯程序寫死到代碼裏
  • 代碼簡潔,可讀性強,可提升代碼的複用率

劣勢segmentfault

  • 相較直接調用,在量大的情景下反射性能降低
  • 存在一些內部暴露和安全隱患

2、反射的運用

反射的源 java.lang.Class,Class 類是 JDK 對咱們自定義的類和內置類的統一描述,Class 類中存儲了類運行時的類型信息。在 Class 類,能夠獲取以下圖所示類的公共信息。Class 類與反射的聯繫密切相關,Class 類和 java.lang.reflect 一塊兒對反射提供了支持。
在這裏插入圖片描述
定義兩個類 Boy 和 Person,Person 做爲 Boy 的父類,做爲接下演示這個部分反射用法的類。api

Person.class安全

public class Person {

    public String name;

    private int age;

    public void talk() {
        System.out.println(name + "is talking");
    }
}

Boy.class微信

public class Boy extends Person {
    
    public int height;

    private int weight;

    public static String description;

    public Boy() {}

    private Boy(int height) {
        this.height = height;
    }

    public Boy(int height, int weight) {
        this.height = height;
        this.weight = weight;
    }

    public static void playBasketball() {
        System.out.println("play basketball!");
    }

    public static void playBall(String ballType) {
        System.out.println("play " + ballType + "!");
    }

    private void pickUpGirl() {
        System.out.println("pick up girl!");
    }

    public int getWeight() {
        return weight;
    }

    public int getHeight() {
        return height;
    }
}

2.1 獲取 Class 對象實例的四種方法

Class<Boy> clazz = Boy.class; // 經過類的 class 屬性
Class<?> clazz2 = new Boy().getClass(); // 經過運行時類對象的 getClass 方法獲取
Class<?> clazz3 = Class.forName("com.hncboy.corejava.reflection.Boy"); // 經過類的全限定名獲取
Class<?> clazz4 = Main.class.getClassLoader().loadClass("com.hncboy.corejava.reflection.Boy"); // 經過類加載器獲取

2.2 反射獲取類的基本信息

一個類的基本信息包含了修飾符,class 關鍵字,類名,父類,接口等信息,經常使用方法以下:oracle

int modifier = clazz.getModifiers(); // 獲取類的修飾符
Package pack = clazz.getPackage(); // 獲取類的包名
String fullClassName = clazz.getName(); // 獲取類的全路徑名稱
String simpleClassName = clazz.getSimpleName(); // 獲取類的簡單名稱
ClassLoader classLoader = clazz.getClassLoader(); // 獲取類的類加載器
Class<?>[] interfacesClasses = clazz.getInterfaces(); // 獲取類實現的接口列表
Class<?> superClass = clazz.getSuperclass(); // 獲取類的父類
Annotation[] annotations = clazz.getAnnotations(); // 獲取類的註解列表

經過一個測試類,測試以上方法:

public class Test {

    public static void main(String[] args) {
        Class<Boy> clazz = Boy.class;

        // 獲取類的修飾符,若是有多個修飾符,取相加後的結果
        int modifiers = clazz.getModifiers();
        System.out.println("modifiers: " + modifiers);
        System.out.println("modifiers toString: " + Modifier.toString(modifiers));

        // 獲取類的包名
        Package pack = clazz.getPackage();
        System.out.println("package: " + pack);

        // 獲取類的全路徑名稱:包名 + 類名
        String fullClassName = clazz.getName();
        System.out.println("fullClassName: " + fullClassName);

        // 獲取類的簡單名稱:只有類名
        String simpleClassName = clazz.getSimpleName();
        System.out.println("simpleClassName: " + simpleClassName);

        // 獲取類的類加載器
        ClassLoader classLoader = clazz.getClassLoader();
        System.out.println("classLoader: " + classLoader);

        // 獲取類實現的接口列表
        Class<?>[] interfacesClasses = clazz.getInterfaces();
        System.out.println("interfacesClasses: " + Arrays.toString(interfacesClasses));

        // 獲取類的父類
        Class<?> superClass = clazz.getSuperclass();
        System.out.println("superClass: " + superClass);

        // 獲取類的註解列表
        Annotation[] annotations = clazz.getAnnotations();
        System.out.println("annotations: " + Arrays.toString(annotations));
    }
}

運行輸出結果以下所示:

modifiers: 1
modifiers toString: public
package: package com.hncboy.corejava.reflection
fullClassName: com.hncboy.corejava.reflection.Boy
simpleClassName: Boy
classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
interfacesClasses: []
superClass: class com.hncboy.corejava.reflection.Person
annotations: []

其中 getModifiers 以整數形式編碼返回此類或接口的 Java 語言修飾符。經過查 java.lang.reflect.Modifier 的 API 可知,返回結果有以下類型,全部修飾符的值都爲二進制運算的結果,經過位運算判斷修飾符是最快的。
在這裏插入圖片描述

2.2 反射對字段的操做

字段的信息存儲在 Field 類中, Field 提供有關類或接口的單個字段的信息,以及對它們的動態訪問,而且能夠對變量進行修改。反射字段能夠是類(靜態)字段或實例字段。經常使用方法以下:

Field[] fields = clazz.getFields(); // 獲取類中全部的公有字段
Field[] declaredFields = clazz.getDeclaredFields(); // 獲取類中定義的字段
Field nameField = clazz.getField("name"); // 獲取指定名稱的公有字段
Field declaredField = clazz.getDeclaredField("likeDesc"); // 獲取指定名稱類中定義的字段
int modifiersField = likeDescField.getModifiers(); // 獲取字段的修飾
nameField.setAccessible(true); // 指定字段強制訪問
nameField.set(person, "hncboy"); // 成員字段賦值(需指定對象)
descriptionField.set(null, "hncboy"); // 靜態字段賦值

經過一個測試類,測試以上方法:

public class Test {

    public static void main(String[] args) throws Exception {
        Class<Boy> clazz = Boy.class;

        // 獲取類中全部的公有字段,包含繼承的
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        // 獲取指定名稱的公有字段,包含繼承的
        Field nameField = clazz.getField("name");
        System.out.println(nameField);

        // 獲取本類中定義的全部字段,不包含繼承的,包含私有的
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field);
        }

        // 獲取本類中指定名稱類中定義的字段
        Field weightField = clazz.getDeclaredField("weight");
        System.out.println(weightField.getModifiers());

        // 給指定字段賦值(需指定對象)
        Boy boy = clazz.newInstance();
        // 將該字段設置爲強制訪問
        weightField.setAccessible(true);
        weightField.set(boy, 120);
        System.out.println(boy.getWeight());

        // 靜態字段賦值,靜態字段不須要指定對象
        Field descField = clazz.getField("description");
        descField.set(null, "靜態屬性");
        System.out.println(Boy.description);
    }
}

運行輸出結果以下所示:

public int com.hncboy.corejava.reflection.Boy.height
public static java.lang.String com.hncboy.corejava.reflection.Boy.description
public java.lang.String com.hncboy.corejava.reflection.Person.name
public java.lang.String com.hncboy.corejava.reflection.Person.name
public int com.hncboy.corejava.reflection.Boy.height
private int com.hncboy.corejava.reflection.Boy.weight
public static java.lang.String com.hncboy.corejava.reflection.Boy.description
2
120
靜態屬性

在直接訪問私有 private 變量 weight 時,會報以下的錯誤,不能訪問 Boy 類的私有變量。經過 Field 繼承的 java.lang.reflect.AccessibleObject 類中的 setAccessible(boolean flag) 能夠開啓權限,setAccessible 方法經過調用 native setAccessible0 方法取消 Java 語言訪問檢查權限。

Exception in thread "main" java.lang.IllegalAccessException: Class com.hncboy.corejava.reflection.Test can not access a member of class com.hncboy.corejava.reflection.Boy with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Field.set(Field.java:761)
    at com.hncboy.corejava.reflection.Test.main(Test.java:41)

2.3 反射對方法的操做

方法的信息存儲在 Method 類中, Method 提供有關類或接口上單個方法的信息,以及對單個方法的訪問。反射方法能夠是類方法或實例方法(包括抽象方法)。經常使用方法以下:

Method[] methods = clazz.getMethods(); // 獲取類中全部的公有方法
Method[] declaredMethods = clazz.getDeclaredMethods(); // 獲取本類的全部方法
Method talkMethod = clazz.getMethod("talk", String.class); // 獲取類中指定名稱和參數的公有方法
Method pugMethod = clazz.getDeclaredMethod("pickUpGirls"); // 獲取本類中定義的指定名稱和參數的方法
int modifiers = pugMethod.getModifiers(); // 獲取方法的修飾符
talkMethod.invoke(boy, "I tell you"); // 指定對象進行成員方法的調用
pugMethod.setAccessible(true); // 指定方法的強制訪問
pickUpGirlsMethod.invoke(null); // 指定靜態方法的調用

經過一個測試類,測試以上方法:

public class Test {

    public static void main(String[] args) throws Exception {
        Class<Boy> clazz = Boy.class;

        // 獲取類中定義的方法,包含繼承的(Object)
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        // 獲取類中指定的方法(無參)
        Method talkMethod = clazz.getMethod("talk");
        System.out.println(talkMethod.getName());
        
        // 獲取類中指定的方法(有參)
        Method playMethod = clazz.getMethod("playBall", String.class);
        System.out.println(playMethod.getName());

        // 獲取本類中的全部方法,不包含繼承,包含私有的
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method);
        }

        // 獲取本類中特定的的方法
        Method declaredMethod = clazz.getDeclaredMethod("pickUpGirl");
        System.out.println(declaredMethod.getName());

        // 底層是基於構造器的建立無參構造器
        Boy boy = clazz.newInstance();
        // 調用公有有參方法
        playMethod.invoke(boy, "足球");
        // 調用私有無參方法,須要設置強制訪問
        declaredMethod.setAccessible(true);
        declaredMethod.invoke(boy);

        // 調用靜態方法
        Method playBasketBallMethod = clazz.getDeclaredMethod("playBasketball");
        playBasketBallMethod.invoke(null);
    }
}

運行輸出結果以下所示:

public static void com.hncboy.corejava.reflection.Boy.playBasketball()
public int com.hncboy.corejava.reflection.Boy.getWeight()
public int com.hncboy.corejava.reflection.Boy.getHeight()
public static void com.hncboy.corejava.reflection.Boy.playBall(java.lang.String)
public void com.hncboy.corejava.reflection.Person.talk()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
talk
playBall
private void com.hncboy.corejava.reflection.Boy.pickUpGirl()
public static void com.hncboy.corejava.reflection.Boy.playBasketball()
public int com.hncboy.corejava.reflection.Boy.getWeight()
public int com.hncboy.corejava.reflection.Boy.getHeight()
public static void com.hncboy.corejava.reflection.Boy.playBall(java.lang.String)
pickUpGirl
play 足球!
pick up girl!
play basketball!

在經過 getMethods 獲取全部父類的公有方法,Boy 類的父類包含 Person 類和 Object 類,因此總共輸出 14 個公有方法。getMethod 或 getDeclaredMethod 方法獲取指定方法名無參的方法時,參數能夠省略,直接傳入方法名,獲取帶參數的方法時,若是類型錯誤會報 NoSuchMethodException 異常,以下所示。經過 method 的 invoke 方法傳入實例對象調用實例方法,調用靜態方法傳入 null 便可。

Exception in thread "main" java.lang.NoSuchMethodException: com.hncboy.corejava.reflection.Boy.playBall(int)
    at java.lang.Class.getMethod(Class.java:1786)
    at com.hncboy.corejava.reflection.Test.main(Test.java:29)

2.4 反射對構造器的操做

構造器的信息存儲在 Constructor 類中, Constructor 提供有關類的單個構造函數的信息,以及對類的訪問。經常使用方法以下:

Constructor<?>[] constructors = clazz.getConstructors(); // 獲取類中全部的公有構造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 獲取類中全部的構造器
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(); // 獲取類中無參的構造器
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, String.class); // 獲取類中有參構造器
int modifiers = constructor.getModifiers(); // 獲取構造器的修飾符
declaredConstructor.newInstance(); // 構造器實例對象
declaredConstructor.setAccessible(true); // 指定構造器的強制訪問
constructor.newInstance("hncboy"); // 有參構造調用
clazz.newInstance(); // 直接調用默認無參構造

經過一個測試類,測試以上方法:

public class Test {

    public static void main(String[] args) throws Exception {
        Class<Boy> clazz = Boy.class;

        // 獲取類中全部的公有構造器
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

        // 獲取類中全部的構造器,包含私有的
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : declaredConstructors) {
            System.out.println(constructor);
        }

        // 獲取類中無參的構造器
        Constructor<?> noParamsConstructor = clazz.getDeclaredConstructor();
        System.out.println(noParamsConstructor);

        // 獲取類中指定參數構造器
        Constructor<?> constructor1 = clazz.getDeclaredConstructor(int.class);
        Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, int.class);
        System.out.println(noParamsConstructor.getModifiers());
        System.out.println(constructor1.getModifiers());
        System.out.println(constructor2.getModifiers());

        // 調用構造器
        Boy boy = (Boy) noParamsConstructor.newInstance();
        System.out.println(boy);
        constructor1.setAccessible(true);
        boy = (Boy) constructor1.newInstance(177);
        System.out.println(boy.getHeight());
    }
}

運行輸出結果以下所示:

public com.hncboy.corejava.reflection.Boy(int,int)
public com.hncboy.corejava.reflection.Boy()
public com.hncboy.corejava.reflection.Boy(int,int)
private com.hncboy.corejava.reflection.Boy(int)
public com.hncboy.corejava.reflection.Boy()
public com.hncboy.corejava.reflection.Boy()
1
2
1
com.hncboy.corejava.reflection.Boy@4b67cf4d
177

getConstructors 方法獲取類中的全部公有構造器,構造器不能繼承,全部只能獲取本類中的。getDeclaredConstructors 獲取本類的全部構造器,包含私有的。在獲取特定參數構造器時,傳入的要與構造器的參數同樣,如 int.class 不能寫成 Integer.class,由於自動拆箱是在編譯過程當中的,而反射是在運行期間的。

經過反射,可以使用 Class.newInstance() 或 Constructor.newInstance() 兩種方式建立對象。Class 類下的 newInstance 是弱類型,只能調用無參的構造方法,若是沒有默認構造方法,會拋出 InstantiationException 實例化異常,經過源碼可知,該方法的本質上是 return tmpConstructor.newInstance((Object[])null); ,也是調用 Constructor 的 newInstance 方法 。 而 Constructor 類下的 newInstance 能夠調用任意參數的構造器。

3、反射破壞單例

單例模式

  • 私有化構造函數
  • 全局惟一的公有訪問點
  • 對外提供獲取實例的靜態方法

3.1 定義餓漢式 Hungry

public class Hungry {

    private static final Hungry INSTANCE = new Hungry();

    private Hungry() {}

    public static Hungry getInstance() {
        return INSTANCE;
    }
}

3.2 定義懶漢式 Lazy

public class Lazy {

    private static Lazy instance;

    private Lazy() {}

    public static Lazy getInstance() {
        if (instance == null) {
            synchronized (Lazy.class) {
                if (instance == null) {
                    instance = new Lazy();
                }
            }
        }
        return instance;
    }
}

3.3 定義 SingletonDestroyer

public class SingletonDestroyer {

    public static void main(String[] args) throws Exception {
        // 破壞懶漢模式
        Lazy lazyInstance = Lazy.getInstance();
        Constructor<Lazy> lazyConstructor = Lazy.class.getDeclaredConstructor();
        lazyConstructor.setAccessible(true);
        Lazy lazyInstanceReflect = lazyConstructor.newInstance();
        System.out.println(lazyInstance);
        System.out.println(lazyInstanceReflect);

        // 破壞餓漢模式
        Hungry hungryInstance = Hungry.getInstance();
        Constructor<Hungry> hungryConstructor = Hungry.class.getDeclaredConstructor();
        hungryConstructor.setAccessible(true);
        Hungry hungryInstanceReflect = hungryConstructor.newInstance();
        System.out.println(hungryInstance);
        System.out.println(hungryInstanceReflect);
    }
}

運行結果以下,經過反射機制能夠破環單例模式,將私有化的構造器經過強制訪問建立對象。

com.hncboy.corejava.reflection.Lazy@4b67cf4d
com.hncboy.corejava.reflection.Lazy@7ea987ac
com.hncboy.corejava.reflection.Hungry@12a3a380
com.hncboy.corejava.reflection.Hungry@29453f44

4、反射實現簡單 Spring IOC Bean 實例建立

IOC(Inversion of Control) 控制反轉,他是一種設計思想,並不是實際的技術,最核心的思想就是將預先設計的對象實例建立的控制權交給程序(IOC 容器)。 IOC 容器本質上是一個 K-V 結構的 Map。IOC 的實現原理就是工廠模式加反射機制。

經過 Spring 文檔可查看 Bean 實例的三種建立方式:

  • Instantiation with a Constructor 經過構造器實例化
  • Instantiation by Using an Instance Factory Method 經過靜態工廠實例化
  • Instantiation by Using an Instance Factory Method 經過實例工廠實例化

步驟以下:

4.1 添加三個類 A,B,C

public class A {

    public A() {
        System.out.println("調用 A 的無參構造器");
    }

    public static B createBInstance() {
        System.out.println("調用 A 的靜態方法 createBInstance");
        return new B();
    }

    public C createCInstance() {
        System.out.println("調用 A 的實例方法 createCInstance");
        return new C();
    }
}

class B {}
class C {}

4.2 添加 spring-ioc.xml

經過模擬該配置文件來進行對象的建立

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 建立方式1:無參構造器建立 A 對象-->
    <bean id="a" class="com.hncboy.corejava.reflection.A"/>

    <!-- 建立方式2:靜態工廠建立,調用 A 的 createBObj 方法來建立名爲 b 的對象放入容器 -->
    <bean id="b" class="com.hncboy.corejava.reflection.A" factory-method="createBInstance"/>

    <!-- 建立方式3:實例工廠建立,調用實例 a 的 createBObj 方法來建立名爲 c 的對象放入容器 -->
    <bean id="c" factory-bean="a" factory-method="createCInstance"/>

</beans>

4.3 添加 BeanConfig

/**
 * 存放 bean 的基本信息
 */
public class BeanConfig {

    private String id;
    private String clazz;
    private String factoryMethod;
    private String factoryBean;
    
    /* getter、setter 省略 */
}

4.4 添加 IOCContainer

/**
 * 定義 map 存放 map
 */
public class IOCContainer {

    private static Map<String, Object> container = new HashMap<>();

    public static void putBean(String id, Object object) {
        container.put(id, object);
    }

    public static Object getBean(String id) {
        return container.get(id);
    }
}

4.5 添加 Init

建立方式1:無參構造器建立。bean 的內容包括 id 和 clazz

建立方式2:靜態工廠建立。bean 的內容包括 id 和 factory-method

建立方式3:實例工廠建立。bean 的內容包括 id,factory-bean 和 factory-method

public class Init {

    public static void main(String[] args) throws Exception {
        List<BeanConfig> beanConfigs = parseXmlToBeanConfig();
        // 將解析的 BeanConfig 進行實例化
        for (BeanConfig beanConfig : beanConfigs) {
            if (beanConfig.getClazz() != null) {
                Class<?> clazz = Class.forName(beanConfig.getClazz());
                if (beanConfig.getFactoryMethod() != null) {
                    // 建立方式2:靜態工廠建立
                    Method method = clazz.getDeclaredMethod(beanConfig.getFactoryMethod());
                    IOCContainer.putBean(beanConfig.getId(), method.invoke(null));
                } else {
                    // 建立方式1:無參構造器建立
                    IOCContainer.putBean(beanConfig.getId(), clazz.newInstance());
                }
            } else if (beanConfig.getId() != null) {
                // 建立方式3:實例工廠建立
                Object object = IOCContainer.getBean(beanConfig.getFactoryBean());
                Method method = object.getClass().getDeclaredMethod(beanConfig.getFactoryMethod());
                IOCContainer.putBean(beanConfig.getId(), method.invoke(object));
            } else {
                System.out.println("缺乏配置,沒法建立對象!");
            }
        }
    }

    /**
     * 模擬解析 XML 中的 bean
     *
     * @return
     */
    private static List<BeanConfig> parseXmlToBeanConfig() {
        List<BeanConfig> beanConfigs = new ArrayList<>();
        // 模擬無參構造器建立對象
        BeanConfig beanConfig1 = new BeanConfig();
        beanConfig1.setId("a");
        beanConfig1.setClazz("com.hncboy.corejava.reflection.A");
        beanConfigs.add(beanConfig1);

        // 模擬靜態工廠建立對象
        BeanConfig beanConfig2 = new BeanConfig();
        beanConfig2.setId("b");
        beanConfig2.setClazz("com.hncboy.corejava.reflection.A");
        beanConfig2.setFactoryMethod("createBInstance");
        beanConfigs.add(beanConfig2);

        // 模擬實例工廠建立對象
        BeanConfig beanConfig3 = new BeanConfig();
        beanConfig3.setId("c");
        beanConfig3.setFactoryBean("a");
        beanConfig3.setFactoryMethod("createCInstance");
        beanConfigs.add(beanConfig3);

        return beanConfigs;
    }
}

運行結果以下:

調用 A 的無參構造器
調用 A 的靜態方法 createBInstance
調用 A 的實例方法 createCInstance
Java反射徹底解析

深刻解析Java反射

文章同步到公衆號和Github,有問題的話能夠聯繫做者。
微信掃描二維碼,關注個人公衆號

相關文章
相關標籤/搜索