Java入門系列-27-反射

咱們可能都用過 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.測試:創建代理對象生產器,生產代理對象

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