輕量級鎖和偏向鎖等
什麼是反射?
Java的反射(reflection)機制是指在程式的執行狀態中,可以構造任意一個類的物件,可以瞭解任意一個物件所屬的類,可以瞭解任意一個類的成員變數和方法,可以呼叫任意一個物件的屬性和方法。這種動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制。反射被視為動態語言的關鍵(來源於百度)
JAVA 反射機制是指在執行狀態中,
-
對於任意一個類,都能夠知道這個類的所有屬性和方法;
-
對於任意一個物件,都能夠呼叫它的任意方法和屬性;
-
在程式執行過程中,可以操作正在執行的物件
這種動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制
反射機制的作用
Java反射機制的作用是(百度):1)在執行時判斷任意一個物件所屬的類。2)在執行時構造任意一個類的物件。3)在執行時判斷任意一個類所具有的成員變數和方法。4)在執行時呼叫任意一個物件的方法
Class 類與 java.lang.reflect 類庫一起對反射的概念進行了支援,該類庫包含了Field,Method,Constructor 類 (每個類都實現了 Member 介面)。這些型別的物件是由 JVM 在執行時建立的,用以表示未知類裡對應的成員。
這樣你就可以使用 Constructor 建立新的物件,用 get() 和 set() 方法讀取和修改與 Field 物件關聯的欄位,用 invoke()方法呼叫與 Method 物件關聯的方法。另外,還可以呼叫 getFields()、getMethods() 和getConstructors() 等便利的方法,以返回表示欄位,方法,以及構造器的物件的陣列。這樣匿名物件的資訊就能在執行時被完全確定下來,而在編譯時不需要知道任何事情。
通過反射檢視類資訊
Java 程式中許多物件在執行時都會出現兩種型別:編譯時型別和執行時型別
例如:Person p = new Student();
這行程式碼將會生成一個變數 p,該變數的編譯型別為 Person,但執行時型別為 Student,除此之外,還有更極端的情形,程式在執行時接收到外部傳入的一個物件,該物件的編譯型別是 Object,但程式又需要呼叫該物件執行時型別的方法,為了解決這個問題,程式需要在執行時發現物件和類的真實資訊,方法有兩種:
第一種是假設在編譯和執行時都完全知道型別的具體資訊,在這種情況下,我們可以直接先使用instance of 運算子進行判斷,再利用強制型別轉換將其轉換成執行時型別的變數即可
第二種是編譯時根本無法預知該物件和類的資訊,程式只依靠執行時資訊來發現該物件和類的真實資訊,這就必須使用反射
獲取Class物件
Java中每個類被載入之後,系統就會為該類生成一個對應的Class物件,通過Class物件就可以訪問到載入到JVM(Java虛擬機器)中的這個類的所有資訊,一旦獲得某個類所對應的 Class物件之後,程式就可以呼叫 Class 物件的方法來獲得該物件和該類的真實資訊了
Java程式獲得Class物件的三種方式:
-
Class.forName( )方法。該方法需要傳入字串引數,這個字串引數的值是某個類的類名(必須新增完整包名)
-
呼叫某個類的 class 屬性來獲取對於的 Class 物件
-
呼叫某個物件(Object)的 getClass 方法。該方法將返回該物件所屬類對於的 Class 物件
public class Demo1 { public static void main(String[] args) throws ClassNotFoundException { //通過forName方法獲取class物件 forName需要丟擲一個異常 Class aClass = Class.forName("com.gxy.java.entity.Student"); System.out.println("forName方法獲取" + aClass); //通過class屬性獲取class物件 Class<Student> studentClass = Student.class; System.out.println("class屬性獲取" + studentClass); //通過某個物件的 getClass 方法獲取對應的 Class 物件 Student stu = new Student(); Class class2 = stu.getClass(); System.out.println("某個物件的getClass方法獲取" + class2); } }
執行結果:
forName方法獲取class com.gxy.java.entity.Student
class屬性獲取class com.gxy.java.entity.Student
某個物件的getClass方法獲取class com.gxy.java.entity.Student
從Class中獲取資訊
Class 類提供了大量方法,這些方法可以讓我們訪問 Class 物件對應類的詳細資訊
1. 訪問 Class 對應類所包含的構造器(Constructor):
-
Constructor getConstructor(Class...parameterTypes):返回 Class 物件對應類的指定引數的public 構造器。
-
Constructor[] getConstructors():返回 Class 物件對應類的所有 public 構造器。
-
Constructor getDeclaredConstructor(Class...parameterTypes):返回 Class 物件對應類的指定引數的構造器,與構造器訪問級別無關。
-
Constructor[] getDeclaredConstructors():返回 Class 物件對應類的所有構造器,與構造器訪問級別無關
import java.lang.reflect.Constructor; public class Person { private Person(){ System.out.println("這是無參構造器"); } public Person(String name){ System.out.println("這是一個有參構造器"); } public Person(String name,int age){ System.out.println("這是有參構造器二號"); } public static void main(String[] args) throws NoSuchMethodException { //獲取class物件 Class<Person> clazz = Person.class; //獲取person類的引數型別為String的public構造器 Constructor<Person> con = clazz.getConstructor(String.class); System.out.println("person類的引數型別為String的public構造器是:\n" + con); System.out.println("-----------------------------------------"); //獲取person類中所有的public構造器 Constructor[] persons = clazz.getConstructors(); //遍歷出集合中的所有資料 for (Constructor c: persons) { System.out.println("person類public構造器是" + c); } System.out.println("-------------------------------------------"); //獲取person類中的所有構造方法 Constructor[] cons = clazz.getDeclaredConstructors(); for (Constructor x: cons) { System.out.println("獲取person類中的所有構造方法有" + x); } } }
執行結果:
person類的引數型別為String的public構造器是: public com.gxy.java.Person(java.lang.String) ----------------------------------------- person類public構造器是public com.gxy.java.Person(java.lang.String,int) person類public構造器是public com.gxy.java.Person(java.lang.String) ------------------------------------------- 獲取person類中的所有構造方法有public com.gxy.java.Person(java.lang.String,int) 獲取person類中的所有構造方法有public com.gxy.java.Person(java.lang.String) 獲取person類中的所有構造方法有private com.gxy.java.Person()
2. 訪問class對應類所包含的方法(Method):
-
Method getMethod(String name, Class... parameterTypes) :返回 Class 對應類的指定 public 方法。
-
Method[] getMethods() :返回 Class 對應類的所有 public 方法。
-
Method getDeclaredMethod(String name, Class... parameterTypes) :返回 Class 對應類的指定方法,與訪問級別無關。
-
Method[] getDeclaredMethods() :返回 Class 對應類的所有方法,與訪問級別無關。
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Student { private Student(){ System.out.println("這是一個無參構造器"); } public Student(String name){ System.out.println("這是一個有參構造器"); } public void info(){ System.out.println("執行無參info方法"); } public void info(String name){ System.out.println("執行有參info方法"); } public static void main(String[] args) throws NoSuchMethodException { //獲取student類 Class<Student> student = Student.class; //獲取類中的所有方法 Constructor<?>[] students = student.getDeclaredConstructors(); //遍歷方法 for (Constructor c: students) { System.out.println("Student類中定義的方法有" + c); } //獲取Class對應類指定的public方法 Method info = student.getMethod("info", String.class); System.out.println("student定義了帶字串型別引數的 info 方法:\n" + info); } }
執行結果:
Student類中定義的方法有private com.gxy.java.Student() Student類中定義的方法有public com.gxy.java.Student(java.lang.String) student定義了帶字串型別引數的 info 方法: public void com.gxy.java.Student.info(java.lang.String)
3. 訪問 Class 對應類所包含的欄位(Field):
➢ Filed getField(String name) :返回 Class 對應類的指定 public 屬性。
➢ Filed[] getFields() :返回 Class 對應類的所有 public 屬性。
➢ Field getDeclaredField(String name) :返回 Class 對應類的指定屬性,與訪問級別無關。
➢ Field[] getDeclaredField s() :返回 Class 對應類的所有屬性,與訪問級別無關
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.lang.reflect.Field; @AllArgsConstructor @NoArgsConstructor @Data public class Student { private Integer id; public String name; public static void main(String[] args) throws NoSuchFieldException { //獲取class類 Class student = Student.class; //獲取class對應類中的所有屬性返回 與訪問級別無關 Field[] fields = student.getDeclaredFields(); for (Field f: fields) { System.out.println("student的屬性有 " + f); } //獲取class對應類指定的public屬性 Field field = student.getField("name"); System.out.println("student類中屬性為public的為" + field); //獲取所有屬性為public的值 Field[] fields1 = student.getFields(); for (Field f1: fields1) { System.out.println("類中屬性為public的有" + f1); } //返回 Class 對應類的指定屬性,與訪問級別無關 Field id = student.getDeclaredField("id"); System.out.println("student類中id的值為" + id); } }
4. 訪問 Class 對應類所包含的註解(Annotation)
➢ <A extends Annotation> A getAnnotation(Class<A> annotationClass):試圖獲取該 Class 對應類指定型別的註解,如果該型別的註解不存在則返回 null。
➢ Annotation [] getAnnotations():返回此元素上存在的所有註解。
➢ Annotation [] getDeclaredAnnotations():返回直接存在於此元素上的所有註解
//宣告一個簡單的controller import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; //告訴編譯器忽略指定的警告,不用在編譯完成後出現警告資訊 @SuppressWarnings("unchecked") @RestController public class HelloWordController { @RequestMapping("/sayHello") public String sayHello(){ return "歡迎來到Java反射機制的學習"; } } //test測試類 package com.gxy.java; import org.junit.Test; import java.lang.annotation.Annotation; public class Demo2_test { //來自junit包 要匯入對應的依賴 @Test public void test(){ //獲取到HelloWordController類 Class hello = HelloWordController.class; //獲取HelloWordController類在執行時的註解 Annotation[] annotations = hello.getAnnotations(); //遍歷拿出所有的註解 for (Annotation a: annotations) { System.out.println("HelloWorldController 類上存在的註解有:" + a); } //獲取 Class 對應類指定型別的註解 Annotation annotation = hello.getAnnotation(HelloWordController.class); System.out.println("HelloWorldController 類 Property 型別的註解是:\n" + annotation); } } /** * 上面的程式訪問 HelloController 類上存在的所有註解資訊時,沒有訪問到@SuppressWarnings, * 因為這個註解並不是執行註解,所以無法通過反射訪問 */
執行結果:
HelloWorldController 類上存在的註解有:@org.springframework.stereotype.Controller(value=) HelloWorldController 類 Property 型別的註解是: null
5. 訪問 Class 對應類所包含的其他成員
➢ Class[] getDeclaredClasses():返回 Class 對應類的全部內部類。
➢ Class[] getClasses():返回 Class 對應類的全部 public 內部類。
➢ Class<?> getDeclaringClass():返回 Class 對應類的外部類。
➢ Class[] getInterfaces():返回 Class 對應類所實現的全部介面。
➢ int getModifiers():返回 Class 對應類或介面的所有修飾符。修飾符由 public、protected、private、final、static、abstract 等對應的常量組成,返回的整數應使用 Modifier 工具類的方法來解碼,才可以獲得真實的修飾符。
➢ Package getPackage():獲取此類的包。
➢String getName():返回 Class 對應類的名稱。
➢ String getSimpleName():返回 Class 對應類的簡稱。
➢ Class<? super T> getSuperClass():返回 Class 對應類的父類的對應 Class 物件。
使用反射生成物件並操作物件
Class 物件可以獲得該類裡包括的方法(Method)、構造器(Constructor)、屬性(Field)等成員,這就意味著程式可以通過 Method 物件來執行對應的方法,通過 Constructor 物件來呼叫對應的構造器建立物件,通過 Field 物件直接訪問並修改物件的屬性值
import java.lang.reflect.Constructor; public class Demo3{ //異常直接丟擲頂級父類 不用多次丟擲 public static void main(String[] args) throws Exception { Class aClass = Class.forName("java.lang.StringBuffer"); //獲取StringBuffer中帶字串引數的構造器 Constructor constructor = aClass.getConstructor(String.class); //通過構造器的newInstance方法建立物件 Object instance = constructor.newInstance("這是一個初始化字元"); System.out.println(instance); } }
1. 建立物件
反射機制生成物件有兩種方式:通過第一種方式來建立物件是比較常見的情形,因為在很多 Java EE 框架中都需要根據配置檔案資訊來建立 Java 物件,從配置檔案讀取的只是某個類的字串類名,程式通過反射來建立對應例項
➢ 使用 Class 物件的 newInstance()方法來建立該 Class 物件對應類的例項,這種方式要求該 Class 物件的對應類有預設構造器,而執行 newInstance()方法時實際上是利用預設構造器來建立該類例項。
➢ 先使用 Class 物件獲取 Constructor 物件,再呼叫 Constructor 物件的 newInstance()方法來建立該Class 物件對應類的例項。通過這種方式可以選擇使用某個類的指定構造器來建立例項
import java.io.FileInputStream; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 這個程式根據配置檔案呼叫 Class 物件的 newInstance 方法建立 Java 物件,並將這些物件放入物件池中然 *後根據存入池中的 name 取出物件。這種使用配置檔案來配置物件,然後由程式根據配置檔案來建立物件的方式 * 非常有用,鼎鼎大名的 Spring 框架就是採用這種方式大大簡化了 JavaEE 應用的開發,當然,Spring 採用的 *是資訊豐富的 XML 配置檔案。 */ public class ObjectPoolFactory { //定義一個map集合 private Map<String,Object> map = new HashMap<String,Object>(); //定義一個建立物件的方法,該方法只要傳入一個字串類名,程式可以根據該類名生成 Java 物件 private Object createObject(String className) throws Exception{ //根據字串來獲取對應的class物件 Class aClass = Class.forName(className); //使用 aClass 對應類的預設構造器建立例項 //newInstance依賴於構造方法,沒有構造方法不能建立成功 return aClass.newInstance(); } public void initPool(String fileName){ // FileInputStream流被稱為檔案位元組輸入流 // 意思指對檔案資料以位元組的形式進行讀取操作如讀取圖片視訊等 FileInputStream fis = null; try { //需要讀取檔名稱 fis = new FileInputStream(fileName); //新建一個Properties 用於讀取Java配置檔案 Properties pro = new Properties(); //從輸入流中讀取屬性列表 pro.load(fis); for (String name:pro.stringPropertyNames()) { //建立物件並新增到map集合中 map.put(name,createObject(pro.getProperty(name))); } } catch (Exception e) { e.printStackTrace(); } finally { try { //如果當前的位元組流裡面存在資料 if(fis != null){ //關閉這個位元組流 fis.close(); } } catch (Exception e){ e.printStackTrace(); } } } public Object getObject(String name){ //從map集合中取出物件 return map.get(name); } public static void main(String[] args) { //宣告一個物件池工廠 ObjectPoolFactory pool = new ObjectPoolFactory(); //給出要讀取檔案的路徑 pool.initPool("src\\main\\resources\\obj.txt"); //宣告要讀取配置檔案中的屬性 Object a = pool.getObject("a"); //格式化時間格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //輸入所讀取到的資料 System.out.println("獲取當前時間為:" + sdf.format(a)); } }
配值檔案:
a = java.util.Date
b = javax.swing.JFrame
執行結果:
獲取當前時間為:2020-10-06 15:41:39
2. 呼叫方法
當獲得某個類的 Class 物件後,就可以通過 Class 物件的 getMethods 方法或者 getMethod 方法來獲取全部方法(Method 物件陣列)或指定方法(Method 物件,獲得 Method 物件後,程式就可以通過 Method物件的 invoke()方法呼叫目標物件的方法
下面程式對前面的物件池工廠進行加強,程式允許在配置檔案增加配置物件的屬性值,物件池工廠會讀取該物件的屬性值,並利用該物件的 setter 方法為對應屬性設定值
import com.sun.org.apache.xml.internal.utils.ObjectPool; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties; //通過反射實現對 Person 類的 setName 方法的呼叫 public class ExtendedObjectPoolFactory { //初始化一個map集合 private Map<String,Object> map = new HashMap<String, Object>(); //從屬性檔案中初始化properties屬性 Properties config = new Properties(); //宣告一個方法 public void init(String fileName){ //初始化FileInputStream變數 FileInputStream fis = null; try { fis = new FileInputStream(fileName); config.load(fis); } catch (Exception e) { e.printStackTrace(); } finally { try { if(fis != null){ fis.close(); } } catch (Exception e){ e.printStackTrace(); } } } public Object createObject(String className) throws Exception{ //根據字串獲取對應class物件 Class<?> aClass = Class.forName(className); //使用預設構造器建立物件 return aClass.newInstance(); } //根據配置檔案初始化物件池 public void initPool() throws Exception { for (String name : config.stringPropertyNames()){ //每取出一個屬性名-屬性值對時,如果屬性名中不包含%,就根據屬性值建立一個物件 if(!name.contains("%")){ map.put(name,createObject(config.getProperty(name))); } else { //將配置檔案中的屬性按照%分割 String[] split = name.split("%"); //取出要設定屬性的目標物件 Object target = getObject(split[0]); //設定該屬性的對應的setter方法名 String setName = "set" + split[1].substring(0,1).toUpperCase() +split[1].substring(1); //獲取target對應的class物件 Class<?> targetClass = target.getClass(); //獲取改屬性對應的setter方法 Method m = targetClass.getMethod(setName, String.class); //呼叫 Method 物件的 invoke 方法執行 setter 方法 //invoke 的第一個引數表示目標物件,第二個引數表示呼叫時傳入的實參 m.invoke(target,config.getProperty(name)); } } } public Object getObject(String name) { return map.get(name); } public static void main(String[] args) throws Exception { ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory(); epf.init("src\\main\\resources\\object.txt"); epf.initPool(); System.out.println("這是一個配置檔案" + epf.getObject("a")); } }
//測試類 public class Demo4 { private String name; public String getName(){ return name; } public void setName(String name) { this.name = name; System.out.println("setName方法呼叫了:" + name); } }
配置檔案:
#要寫自己的測試類所在位置 a = com.gxy.java.Demo4 #set the name of a a%name = Test Name
執行結果:
setName方法呼叫了:Test Name
這是一個配置檔案com.gxy.java.Demo4@4a574795
3. 訪問屬性值
通過 Class 物件的 getFields()和 getField()方法可以獲得該類所包含的全部 Filed 物件陣列或指定Filed 物件
Filed 提供了兩種方法來訪問屬性:
➢ getXxx(Object obj):獲取 obj 物件的屬性值。此處 Xxx 對應 8 個基本型別,如果是 引用型別則取消get 後面的 Xxx。
➢ setXxx(Object obj , Xxx val):將 obj 物件的屬性值設定成 val。此處 Xxx 表示 8 個基本型別,如果是引用型別則取消 set 後面的 Xxx。
import java.lang.reflect.Field; import java.lang.reflect.Method; class User { public String name; private int age; private void print(){ System.out.println("姓名:"+name+"\n年齡:"+age); } } public class Demo5 { public static void main(String[] args) throws Exception { //通過User獲取對應的Class物件 Class<User> userClass = User.class; //使用反射建立User例項 User user = userClass.newInstance(); //獲取user類的name Field name = userClass.getField("name"); //通過例項修改 name.set(user,"莫離歡"); //獲取user類的private屬性age Field age = userClass.getDeclaredField("age"); //通過反射訪問該屬性時取消訪問許可權檢查 //true是關閉 false是開啟,預設是false開啟狀態 age.setAccessible(true); //設定age屬性 age.set(user,18); //獲取user類的私有方法print方法 Method print = userClass.getDeclaredMethod("print"); //設定通過反射訪問該屬性時取消許可權檢查 print.setAccessible(true); //啟用方法 -- 類似於打點呼叫 //引數:obj給哪個物件啟用方法 args:這個方法需要的引數 print.invoke(user); } }
執行結果:
姓名:莫離歡
年齡:18
動態代理
1. 代理模式
代理模式是常用的 java 設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等;代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,而是通過呼叫委託類的物件的相關方法,來提供特定的服務
比如你要去銀行取錢,你就是委託人,你委託銀行的僱員(代理人)幫你辦理取錢的業務。你和銀行僱員的關聯關係是:表面上是銀行僱員取錢,但實際上是你在取錢,僱員只是為你提供取錢的服務
2.動態代理
在 Java 的 java.lang.reflect 包下提供了一個 Proxy 類和一個 Invocationhandler 介面。這個類和介面是實現動態代理所必須用到的
1) Invocationhandler 介面:
每一個動態代理類都必須要實現 InvocationHandler 介面,並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由 InvocationHandler這個介面的 invoke 方法來進行呼叫
Object invoke(Object proxy, Method method, Object[] args)
引數解析:
➢ Object proxy:指被代理的物件
➢ Method method:要呼叫的方法
➢ Object[] args:方法呼叫時所需的實參
System.out.println("引數一:被指帶的物件:" + proxy.getClass() + "\n引數二:呼叫的方法method" + method.getName() + "\n引數三:所傳入的引數args" + args[0]); 引數一:被指帶的物件:class com.sun.proxy.$Proxy0 引數二:呼叫的方法methodpay 引數三:所傳入的引數args大房間
2) Proxy 類
Proxy 類提供了一個建立動態代理物件的方法,該方法定義如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
引數解析:
➢ ClassLoader loader:類載入器,定義了由哪個 ClassLoader 物件來對生成的代理物件進行載入。
➢ Class<?>[] interfaces:代理類要實現的全部介面。
➢ InvocationHandler h:表示代理物件要關聯的 InvocationHandler 物件。
Object o = Proxy.newProxyInstance( consumer.getClass().getClassLoader(),//類載入器 ClassLoader loader consumer.getClass().getInterfaces(),//要實現的介面 Class<?>[] interfaces new InvocationHandler()//關聯的 InvocationHandler 物件 { @Override public Object invoke(Object proxy, Method method, Object[] args){ return null; } } );
租房案例:
-
定義一個介面
public interface ZuFang { //付款方法 String pay(String claim); }
-
寫這個介面的實現類
package com.gxy.java.dyx; public class Consumer implements ZuFang { @Override public String pay(String claim) { return "完成要求" + claim + "-----付款成功"; } }
-
寫測試類
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { final Consumer consumer = new Consumer(); ZuFang o = (ZuFang) Proxy.newProxyInstance( consumer.getClass().getClassLoader(),//類載入器 consumer.getClass().getInterfaces(),//介面 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是中介,我可以幫你選房...."); Object invoke = method.invoke(consumer, args); System.out.println("房間已按照您的要求" + args[0] + "選擇完畢,請付款"); return invoke; } } ); System.out.println(o.pay("大房間")); } }
本章總結
-
反射機制指的是程式在執行時能夠獲取自身的資訊。在 java 中,只要給定類的名字,那麼就可以通過反射機制來獲得類的所有資訊。
-
可以實現動態建立物件和編譯,體現出很大的靈活性,特別是在 J2EE 的開發中它的靈活性就表現的十分明顯,但是對效能有所影響。
-
反射機制就是專門幫我們做那些重複的有規則的事情,所以現在很多的自動生成程式碼的軟體就是運用反射機制來完成的。
-
代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等