Java入門系列-27-反射

極客大全發表於2018-11-16

我們們可能都用過 Spring AOP ,底層的實現原理是怎樣的呢?

反射常用於編寫工具,企業級開發要用到的 Mybatis、Spring 等框架,底層的實現都用到了反射。能用好反射,就能提高我們編碼的核心能力。

反射機制

JAVA反射機制是在執行狀態中,對於任意一個實體類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意方法和屬性。

作用:

  • 在執行時判斷任意一個物件所屬的類
  • 在執行時構造任意一個類的物件
  • 在執行時判斷任意一個類所具有的成員變數和方法
  • 在執行時呼叫任意一個物件的成員變數和方法
  • 生成動態代理

常用的類:

  • java.lang.Class:代表一個類
  • java.lang.reflect.Method:代表類的方法
  • java.lang.reflect.Field:代表類的成員變數
  • java.lang.reflect.Constructor:代表類的構造方法

Class 類

Class 類的例項表示正在執行的 Java 應用程式中的類和介面,Class 沒有公共構造方法,Class 物件是在載入類時由 Java 虛擬機器及通過呼叫類載入器中的 defineClass 方法自動構造的。

  • 一個類在 JVM 中只會有一個 Class 例項
  • 一個 Class 物件對應的是一個載入到 JVM 中的一個 .class 檔案
  • 每個類的例項都會記得自己是由哪個 Class 例項所生成
  • 通過 Class 可以完整地得到一個類中的完整結構

獲取 Class 物件

獲取 Class 物件有4種方式,前三種比較常用。

首先建立一個類用於測試

package com.jikedaquan.reflection;

public class User {
    
    private int id;
    private String username;
    private String password;

    public User() {
    }
    
    public User(int id, String username, String password) {
        super();
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    public void show() {
        System.out.println("Hello");
    }
    
    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
    }
}

編寫測試

package com.jikedaquan.reflection;

public class GetClass {

    public static void main(String[] args) {
        //方法1
        try {
            Class clz1=Class.forName("com.jikedaquan.reflection.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("找不到指定類");
        }
        //方法2
        Class clz2=User.class;
        //方法3
        User user=new User();
        Class clz3=user.getClass();
        
        //方法4 類的載入器
        try {
            Class clz4=GetClass.class.getClassLoader().loadClass("com.jikedaquan.reflection.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("找不到指定類");
        }
    }
}

方法1語法:Class Class物件 = Class.forName(包名+類名);

方法2語法:Class Class物件 = 類名.class;

方法3語法:Class Class物件 = 物件.getClass();

getClass() 方法是從 Object 類中繼承過來的

獲取類的結構

Class 類常用方法

方法名稱 說明
Annotation[] getAnnotations() 返回此元素上存在的所有註解
Constructor getConstructor(Class<?>… parameterTypes) 獲取指定引數的建構函式
Constructor<?>[] getConstructors() 返回包含的公有構造方法
Constructor<?>[] getDeclaredConstructors() 返回所有構造方法
Field getDeclaredField(String name) 返回一個 Field 物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告欄位
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 根據方法名和引數獲取方法物件

API 中可以看到有兩種獲取結構的方式:getDeclaredXxx()和getXxx();getDeclaredXxx()可以獲取所有包括私有的

獲取類的結構

package com.jikedaquan.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class GetClassStruct {

    public static void main(String[] args) {
        try {
            Class clz=Class.forName("com.jikedaquan.reflection.User");
            System.out.println("===========構造===========");
            //獲取構造方法
            Constructor[] cons=clz.getDeclaredConstructors();
            for (Constructor constructor : cons) {
                System.out.println(constructor);
            }
            //獲取欄位
            System.out.println("===========欄位===========");
            Field[] fields=clz.getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field);
            }
            //獲取方法
            System.out.println("===========方法===========");
            Method[] methods=clz.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method);
            }
            //獲取父類
            System.out.println("===========父類===========");
            Class supperClass=clz.getSuperclass();
            System.out.println(supperClass.getName());
            //獲取實現的介面
            System.out.println("===========介面===========");
            Class[] interfaces=clz.getInterfaces();
            for (Class interf : interfaces) {
                System.out.println(interf);
            }
            //獲取註解
            System.out.println("===========註解===========");
            Annotation[] annotations=clz.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

呼叫類的指定方法、屬性

獲取構造方法並例項化物件

注意:jdk1.9棄用此方式例項化物件
Object obj=clz.newInstance();

通過反射獲取有參或無參構造後方可例項化化物件

package com.jikedaquan.reflection;

import java.lang.reflect.Constructor;

public class CallConstructor {

    public static void main(String[] args) {
        //獲取User 的 Class
        Class<User> clz=User.class;
        
        //獲取無參構造方法並例項化
        try {
            //getConstructor()方法不傳參即無參
            Constructor<User> constructor=clz.getConstructor();
            User user=constructor.newInstance();
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //獲取有參構造方法並例項化
        try {
            Constructor<User> constructor=clz.getConstructor(int.class,String.class,String.class);
            User user=constructor.newInstance(18,"張三","abc123");
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

獲取指定構造方法時,第二個引數為動態引數,不填寫即獲取無參構造方法,填寫指定個數和指定型別.class可獲取對應方式的構造方法。

呼叫類中的方法

package com.jikedaquan.reflection;

import java.lang.reflect.Method;

public class CallMethod {

    public static void main(String[] args) {
        //獲取User 的 Class
        Class<User> clz=User.class;
        //獲取無參方法  show
        try {
            Method method=clz.getMethod("show");
            //執行clz中的方法
            method.invoke(clz.getConstructor().newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //獲取一個引數為String的方法
        try {
            Method method=clz.getMethod("setUsername", String.class);
            //反射例項化物件
            User user=clz.getConstructor().newInstance();
            //執行這個物件的方法
            method.invoke(user, "反射");
            //測試結果
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

如果有多個引數,獲取方法:getMethod("方法名稱",引數1.class,引數2.class,引數3.class)

多個引數執行時:method.invoke(物件,引數1,引數2,引數3);

動態代理

動態代理是指客戶通過代理類來呼叫其他物件的方法,並且是在程式執行時根據需要建立目標類的代理物件。

原理:

使用一個代理將物件包裝起來,然後用該代理物件取代原物件,任何對原始物件的呼叫都要通過dialing,代理物件決定是否以及何時將方法呼叫轉到原始物件上。

生活中海外代購其實就用到了代理,你可能不方便出國,但是代購可以,最終幫你完成購買行為。

以代購為例子完成靜態代理

package com.jikedaquan.reflection;

//購買介面(約定)
interface Buy{
    void buyProduct();
}
//被代理的
class Customer implements Buy{

    @Override
    public void buyProduct() {
        System.out.println("購買商品");
    }
}
//代理
class ProxyBuy implements Buy{
    private Customer customer;
    
    public ProxyBuy(Customer customer) {
        this.customer=customer;
    }
    
    @Override
    public void buyProduct() {
        System.out.println("代理:出國");
        //被代理的物件的行為
        customer.buyProduct();
        System.out.println("代理:回國");
    }
}

public class TestStaticProxy {

    public static void main(String[] args) {
        Customer customer=new Customer();
        ProxyBuy proxyBuy=new ProxyBuy(customer);
        proxyBuy.buyProduct();
    }
}

那麼動態代理意味著不能只代理 Customer 類的行為,還可以代理其他類的行為

package com.jikedaquan.reflection;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//工廠介面
interface Factory{
    void product();
}
//電腦工廠
class ComputerFactory implements Factory{

    @Override
    public void product() {
        System.out.println("生產電腦");
    }
}
//動態代理處理器
class MyInvocationHandler implements InvocationHandler{
    //要被代理的物件
    private Object proxyObj;
    //產生代理物件
    public Object bind(Object proxyObj) {
        this.proxyObj=proxyObj;
        return Proxy.newProxyInstance(
                proxyObj.getClass().getClassLoader(),
                proxyObj.getClass().getInterfaces(), 
                this
                );
    }
    //代理物件實際執行的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理:收收費");
        Object result=method.invoke(proxyObj, args);
        System.out.println("代理:代理完成");
        return result;
    }

}

public class TestDynamicProxy {

    public static void main(String[] args) {
        //建立代理物件生產器
        MyInvocationHandler invocationHandler=new MyInvocationHandler();

        //建立要被代理的物件
        ComputerFactory computerFactory=new ComputerFactory();
        //生產代理物件
        Object factoryProxy=invocationHandler.bind(computerFactory);
        Factory factory=(Factory) factoryProxy;
        factory.product();

        //建立另一個要被代理的物件(上個示例靜態代理的物件和介面)
        Customer customer=new Customer();
        //生產代理物件
        Object buyProxy=invocationHandler.bind(customer);
        Buy buy=(Buy) buyProxy;
        buy.buyProduct();
    }
}

在 main 方法中,建立了一個 MyInvocationHandler 物件,通過 bind 方法可以傳入任意要被代理的物件,實現了動態。

重點來了,拿好小本子筆記!!!!!

實現動態代理的步驟

1.建立要被代理的類的介面

2.建立要被代理的類實現類

3.建立代理物件處理器(MyInvocationHandler),實現 InvocationHandler 介面

4.編寫生產代理物件的方法,方法內呼叫 Proxy.newInstance() 方法,返回代理物件

5.重寫 InvocationHandler 的 invoke 方法

6.測試:建立代理物件生產器,生產代理物件


相關文章