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 | 獲取指定引數的建構函式 |
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棄用此方式例項化物件
通過反射獲取有參或無參構造後方可例項化化物件
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.測試:建立代理物件生產器,生產代理物件