<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在java的物件導向程式設計過程中,通常我們需要先知道一個Class類,然後new 類名()
方式來獲取該類的物件。也就是說我們需要在寫程式碼的時候(編譯期或者類載入之前)就知道我們要範例化哪一個類,執行哪一個方法,這種通常被稱為靜態的類載入。
但是在有些場景下,我們事先是不知道我們的程式碼的具體行為的。比如,我們定義一個服務任務工作流,每一個服務任務都是對應的一個類的一個方法。
面對這個情況,我們就不能用程式碼new 類名()
來實現了,因為你不知道使用者具體要怎麼做設定,這一秒他希望服務任務A執行Xxxx類的x方法,下一秒他可能希望執行Yyyy類的y方法。當然你也可以說提需求嘛,使用者改一次需求,我改一次程式碼。這種方式也能需求,但對於使用者和程式設計師個人而言都是痛苦,那麼有沒有一種方法在執行期動態的改變程式的呼叫行為的方法呢?這就是要為大家介紹的“java反射機制”。
那麼java的反射機制能夠做那些事呢?大概是這樣幾種:
package名.類名
範例化類物件先入個門,大佬可以略過這一段。我們定義一個類叫做Student
package com.zimug.java.reflection; public class Student { public String nickName; private Integer age; //這裡是private public void dinner(){ System.out.println("吃晚餐!"); } private void sleep(int minutes){ //private修飾符 System.out.println("睡" + minutes + "分鐘"); } }
如果不用反射的方式,我相信只要學過java的朋友肯定會呼叫dinner方法
Student student = new Student(); student.dinner();
如果是反射的方式我們該怎麼呼叫呢?
//獲取Student類資訊 Class cls = Class.forName("com.zimug.java.reflection.Student"); //物件範例化 Object obj = cls.getDeclaredConstructor().newInstance(); //根據方法名獲取並執行方法 Method dinnerMethod = cls.getDeclaredMethod("dinner"); dinnerMethod.invoke(obj); //列印:吃晚餐!
通過上面的程式碼我們看到,com.zimug.java.reflection.Student類名和dinner方法名是字串。既然是字串我們就可以通過組態檔,或資料庫、或什麼其他的靈活設定方法來執行這段程式了。這就是反射最基礎的使用方式。
java的類載入機制還是挺複雜的,我們這裡為了不混淆重點,只為大家介紹和“反射”有關係的一部分內容。
java執行編譯的時候將java檔案編譯成位元組碼class檔案,類載入器在類載入階段將class檔案載入到記憶體,並範例化一個java.lang.Class的物件。比如對於Student類在載入階段會有如下動作:
有了上面的關於Student類的基本資訊物件(java.lang.Class物件),在執行期就可以根據這些資訊來範例化Student類的物件。
但是無論你new多少個Student物件,不論你反射構建多少個Student物件,儲存Student類資訊的java.lang.Class物件都只有一個。下面的程式碼可以證明。
Class cls = Class.forName("com.zimug.java.reflection.Student"); Class cls2 = new Student().getClass(); System.out.println(cls == cls2); //比較Class物件的地址,輸出結果是true
瞭解了上面的這些基礎資訊,我們就可以更深入學習反射類相關的類和方法了:
java.lang.Class: 代表一個類
java.lang.reflect.Constructor: 代表類的構造方法
java.lang.reflect.Method: 代表類的普通方法
java.lang.reflect.Field: 代表類的成員變數
Java.lang.reflect.Modifier: 修飾符,方法的修飾符,成員變數的修飾符。
java.lang.annotation.Annotation:在類、成員變數、構造方法、普通方法上都可以加註解
Class.forName()
方法獲取Class物件
/** * Class.forName方法獲取Class物件,這也是反射中最常用的獲取物件的方法,因為字串傳參增強了設定實現的靈活性 */ Class cls = Class.forName("com.zimug.java.reflection.Student");
類名.class
獲取Class物件
/** * `類名.class`的方式獲取Class物件 */ Class clz = User.class;
類物件.getClass()
方式獲取Class物件
/** * `類物件.getClass()`方式獲取Class物件 */ User user = new User(); Class clazz = user.getClass();
雖然有三種方法可以獲取某個類的Class物件,但是隻有第一種可以被稱為“反射”。
Class cls = Class.forName("com.zimug.java.reflection.Student"); //獲取類的包名+類名 System.out.println(cls.getName()); //com.zimug.java.reflection.Student //獲取類的父類別 Class cls = Class.forName("com.zimug.java.reflection.Student"); //這個型別是不是一個註解? System.out.println(cls.isAnnotation()); //false //這個型別是不是一個列舉? System.out.println(cls.isEnum()); //false //這個型別是不是基礎資料型別? System.out.println(cls.isPrimitive()); //false
Class類物件資訊中幾乎包括了所有的你想知道的關於這個型別定義的資訊,更多的方法就不一一列舉了。還可以通過下面的方法
獲取Class類物件代表的類實現了哪些介面: getInterfaces()
獲取Class類物件代表的類使用了哪些註解: getAnnotations()
結合上文中的Student類的定義理解下面的程式碼
Class cls = Class.forName("com.zimug.java.reflection.Student"); Field[] fields = cls.getFields(); for (Field field : fields) { System.out.println(field.getName()); //nickName } fields = cls.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); //nickName 換行 age }
getFields()
方法獲取類的非私有的成員變數,陣列,包含從父類別繼承的成員變數
getDeclaredFields
方法獲取所有的成員變數,陣列,但是不包含從父類別繼承而來的成員變數
getMethods() : 獲取Class物件代表的類的所有的非私有方法,陣列,包含從父類別繼承而來的方法
getDeclaredMethods() : 獲取Class物件代表的類定義的所有的方法,陣列,但是不包含從父類別繼承而來的方法
getMethod(methodName): 獲取Class物件代表的類的指定方法名的非私有方法
getDeclaredMethod(methodName): 獲取Class物件代表的類的指定方法名的方法
Class cls = Class.forName("com.zimug.java.reflection.Student"); Method[] methods = cls.getMethods(); System.out.println("Student物件的非私有方法"); for (Method m : methods) { System.out.print(m.getName() + ","); } System.out.println(" end"); Method[] allMethods = cls.getDeclaredMethods(); System.out.println("Student物件的所有方法"); for (Method m : allMethods) { System.out.print(m.getName() + ","); } System.out.println(" end"); Method dinnerMethod = cls.getMethod("dinner"); System.out.println("dinner方法的引數個數" + dinnerMethod.getParameterCount()); Method sleepMethod = cls.getDeclaredMethod("sleep",int.class); System.out.println("sleep方法的引數個數" + sleepMethod.getParameterCount()); System.out.println("sleep方法的引數物件陣列" + Arrays.toString(sleepMethod.getParameters())); System.out.println("sleep方法的引數返回值型別" + sleepMethod.getReturnType());
上面程式碼的執行結果如下:
Student物件的非私有方法
dinner,wait,wait,wait,equals,toString,hashCode,getClass,notify,notifyAll, end
Student物件的所有方法
dinner,sleep, end
dinner方法的引數個數0
sleep方法的引數個數1
sleep方法的引數物件陣列[int arg0]
sleep方法的引數返回值型別void
可以看到getMethods獲取的方法中包含Object父類別中定義的方法,但是不包含本類中定義的私有方法sleep。另外我們還可以獲取方法的引數及返回值資訊:
獲取引數相關的屬性:
獲取返回值相關的屬性
實際在上文中已經演示了方法的呼叫,如下invoke呼叫dinner方法
Method dinnerMethod = cls.getDeclaredMethod("dinner"); dinnerMethod.invoke(obj); //列印:吃晚餐!
dinner方法是無參的,那麼有引數的方法怎麼呼叫?看看invoke方法定義,第一個引數是Method物件,無論後面Object... args
有多少引數就按照方法定義依次傳參就可以了。
public Object invoke(Object obj, Object... args)
//獲取Student類資訊 Class cls = Class.forName("com.zimug.java.reflection.Student"); //物件範例化 Student student = (Student)cls.getDeclaredConstructor().newInstance(); //下面的這種方法是已經Deprecated了,不建議使用。但是在比較舊的JDK版本中仍然是唯一的方式。 //Student student = (Student)cls.newInstance();
通過設定資訊呼叫類的方法
結合註解實現特殊功能
按需載入jar包或class
將上文的hello world中的程式碼封裝一下,你知道類名className和方法名methodName是不是就可以呼叫方法了?至於你將className和 methodName設定到檔案,還是nacos,還是資料庫,自己決定吧!
public void invokeClassMethod(String className,String methodName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //獲取類資訊 Class cls = Class.forName(className); //物件範例化 Object obj = cls.getDeclaredConstructor().newInstance(); //根據方法名獲取並執行方法 Method dinnerMethod = cls.getDeclaredMethod(methodName); dinnerMethod.invoke(obj); }
大家如果學習過mybatis plus都應該學習過這樣的一個註解TableName,這個註解表示當前的實體類Student對應的資料庫中的哪一張表。如下問程式碼所示,Student所示該類對應的是t_student這張表。
@TableName("t_student") public class Student { public String nickName; private Integer age; }
下面我們自定義TableName這個註解
@Target(ElementType.TYPE) //表示TableName可作用於類、介面或enum Class, 或interface @Retention(RetentionPolicy.RUNTIME) //表示執行時由JVM載入 public @interface TableName { String value() ; //則使用@TableName註解的時候: @TableName(」t_student」); }
有了這個註解,我們就可以掃描某個路徑下的java檔案,至於類註解的掃描我們就不用自己開發了,引入下面的maven座標就可以
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency>
看下面程式碼:先掃描包,從包中獲取標註了TableName註解的類,再對該類列印註解value資訊
// 要掃描的包 String packageName = "com.zimug.java.reflection"; Reflections f = new Reflections(packageName); // 獲取掃描到的標記註解的集合 Set<Class<?>> set = f.getTypesAnnotatedWith(TableName.class); for (Class<?> c : set) { // 迴圈獲取標記的註解 TableName annotation = c.getAnnotation(TableName.class); // 列印註解中的內容 System.out.println(c.getName() + "類,TableName註解value=" + annotation.value());
輸出結果是:
com.zimug.java.reflection.Student類,TableName註解value=t_student
有的朋友會問這有什麼用?這有大用處了。有了類定義與資料庫表的對應關係,你還能通過反射獲取類的成員變數,之後你是不是就可以根據表明t_student和欄位名nickName,age構建增刪改查的SQL了?全都構建完畢,是不是就是一個基礎得Mybatis plus了?
反射和註解結合使用,可以演化出許許多多的應用場景,特別是在框架程式碼實現方面。等待你去發覺啊!
在某些場景下,我們可能不希望JVM的載入器一次性的把所有classpath下的jar包裝載到JVM虛擬機器器中,因為這樣會影響專案的啟動和初始化效率,並且佔用較多的記憶體。我們希望按需載入,需要用到哪些jar,按照程式動態執行的需求取載入這些jar。
把jar包放在classpath外面,指定載入路徑,實現動態載入。
//按路徑載入jar包 File file = new File("D:/com/zimug/commons-lang3.jar"); URL url = file.toURI().toURL(); //建立類載入器 ClassLoader classLoader = new URLClassLoader(new URL[]{url}); Class cls = classLoader.loadClass("org.apache.commons.lang3.StringUtils");
同樣的把.class檔案放在一個路徑下,我們也是可以動態載入到的
//java的.class檔案所在路徑 File file = new File("D:/com/zimug"); URL url = file.toURI().toURL(); //建立類載入器 ClassLoader classLoader = new URLClassLoader(new URL[]{url}); //載入指定類,package全路徑 Class<?> cls = classLoader.loadClass("com.zimug.java.reflection.Student");
類的動態載入能不能讓你想到些什麼?是不是可以實現程式碼修改,不需要重新啟動web容器?對的,就是這個原理,因為一個類的Class物件只有一個,所以不管你重新載入多少次,都是使用最後一次載入的class物件(上文講過哦)。
優點:自由,使用靈活,不受類的存取許可權限制。可以根據指定類名、方法名來實現方法呼叫,非常適合實現業務的靈活設定。在框架開發方面也有非常廣泛的應用,特別是結合註解的使用。
缺點:
言盡於此,限於筆者的知識結構,可能有不嚴謹之處,歡迎大家討論與指正!期待您的關注,我將持續帶來更哇塞的作品,希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45