只有光頭才能變強。文本已收錄至個人GitHub精選文章,歡迎Star:https://github.com/ZhongFuCheng3y/3yhtml
今天來簡單寫一下Java的反射。原本沒打算寫反射這個知識點的,只是很多的讀者都問過我:「你的知識點好像缺了反射阿。能不能補一下?」java
這週末也有點空了,因此來寫寫我對反射的簡單理解。這篇是入門文章,沒有高深的知識點,但願能對新人有幫助。若是文章有錯的地方,麻煩在評論區友善評論指出~mysql
Java經常使用和重要的知識點我都寫過(如今已有200+篇技術原創),若是想看的同窗,不妨關注個人GitHub,便可獲取個人全部原創文章。git
在學習Java基礎的時候,通常都會學過反射。我在初學反射的時候,並不能理解反射是用來幹嗎的。學了一些API發現:「明明我本身能直接new一個對象,爲何它要繞一個圈子,先拿到Class對象,再調用Class對象的方法來建立對象呢,這不是多餘嗎?」程序員
相信不少人在初學反射的時候也都會有這個想法(我就不相信就只有我一我的這麼蠢!!)github
並且在搜索相關資料的時候,通常也僅僅是講解反射的一系列API,始終是不瞭解反射到底是有什麼用,這篇文章來告訴你吧。以爲不錯,給我點個讚唄sql
首先咱們來看一段代碼:數據庫
public class Demo { // 自建了一個Student類 class Student{ } public static void main(String[] args) { // 將Object 強轉成Student類 Object o = new Object(); Student s = (Student) o; } }
咱們在IDE編寫這一段代碼的時候,不會出現任何的錯誤。可是等咱們執行的時候,咱們會知道這確定強轉失敗了。app
那麼「Java」(實質上JVM)是怎麼知道咱們寫的強轉有沒有問題的呢?能夠依賴Class
對象來協助判斷。框架
若是看過我寫JVM
的那篇文章的同窗應該都知道一個對象的加載過程,若是沒看過的同窗能夠再去看看,順便在這裏給你們複習一下:
.java
的文件通過javac
命令編譯成功後,獲得一個.class的文件
.class
文件經過類加載器裝載到jvm
中.class
文件加載器加載到jvm中,又分了好幾個步驟,其中包括 加載、鏈接和初始化 既然說,Class對象表明着類相關的信息,那說明只要類有什麼東西,在Class對象我都能找獲得。咱們打開IDE看看裏邊的方法:
因而咱們能夠經過Class對象來判斷對象的真正類型。
其實反射就是圍繞着Class
對象和java.lang.reflect
類庫來學習,就是各類的API
好比上面截圖的Method
/Field
/Constructor
這些都是在java.lang.reflect
類庫下,正是由於這些類庫的學習並不難,因此我才一直沒寫反射的文章。
我並非說這些API我都能記住,只是這些API教程在網上有很是很是多,也足夠通俗易懂了。在入門的時候,其實掌握如下幾種也差很少了:
/* 下面是我初學反射時作的筆記,應該能夠幫到你們,代碼我就不貼了。(Java3y你值得關注) */ 想要使用反射,我先要獲得class文件對象,其實也就是獲得Class類的對象 Class類主要API: 成員變量 - Field 成員方法 - Constructor 構造方法 - Method 獲取class文件對象的方式: 1:Object類的getClass()方法 2:數據類型的靜態屬性class 3:Class類中的靜態方法:public static Class ForName(String className) -------------------------------- 獲取成員變量並使用 1: 獲取Class對象 2:經過Class對象獲取Constructor對象 3:Object obj = Constructor.newInstance()建立對象 4:Field field = Class.getField("指定變量名")獲取單個成員變量對象 5:field.set(obj,"") 爲obj對象的field字段賦值 若是須要訪問私有或者默認修飾的成員變量 1:Class.getDeclaredField()獲取該成員變量對象 2:setAccessible() 暴力訪問 --------------------------------- 經過反射調用成員方法 1:獲取Class對象 2:經過Class對象獲取Constructor對象 3:Constructor.newInstance()建立對象 4:經過Class對象獲取Method對象 ------getMethod("方法名"); 5: Method對象調用invoke方法實現功能 若是調用的是私有方法那麼須要暴力訪問 1: getDeclaredMethod() 2: setAccessiable();
相信我,去搜索引擎看一會,你就學會了。反射的API並不難學,通常人學不懂反射由於不知道反射究竟能幹什麼,下面我來說講個人講解。
在初學Java的時候其實我我的認爲仍是比較難理解爲何須要反射的,由於沒有必定的代碼量下,很難理解爲何我要繞一個圈子去搞反射這一套。
我如今認爲用反射主要有兩個緣由:
我一直在文章中都在強調,學某一項技術以前,必定要理解爲何要學這項技術,因此個人文章通常會花比較長的幅度上講爲何。
下面我來舉幾個例子來幫助你們理解
相信你們都寫過jdbc
的代碼,我貼一小段,你們回顧一下:
Class.forName("com.mysql.jdbc.Driver"); //獲取與數據庫鏈接的對象-Connetcion connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/java3y", "root", "root"); //獲取執行sql語句的statement對象 statement = connection.createStatement(); //執行sql語句,拿到結果集 resultSet = statement.executeQuery("SELECT * FROM users");
後來爲何要變成下面這種形式呢?
//獲取配置文件的讀入流 InputStream inputStream = UtilsDemo.class.getClassLoader().getResourceAsStream("db.properties"); Properties properties = new Properties(); properties.load(inputStream); //獲取配置文件的信息 driver = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); //加載驅動類 Class.forName(driver);
理由很簡單,人們不想修改代碼。只要存在有變更的地方,我寫在配置裏邊,不香嗎?但凡是有一天,個人username,password,url甚至是數據庫都改了,我都可以經過修改配置的方式去實現。
不須要動我絲毫的代碼,改下配置就完事了,這就能提供程序的靈活性。
有人可能會問:「那仍是要改啊,我改代碼也很快啊,你改配置不也是要改嗎」。
其實不同的,我舉個例子:
改代碼的風險要比改配置大,即使不知道代碼的實現都能經過改配置來完成要作的事。
這種就能經過可配的,其內部極可能就是經過反射來作的。
這裏只是說可能,但不全是。有的可配的參數可能就僅僅只是配置,跟反射無關。但上面jdbc的例子,就是經過反射來加載驅動的。
相信你們學SpringMVC以前都學過Servlet的吧,若是沒學過,建議看個人文章再複復習。
我當時學MVC框架的時候給我帶來印象最深的是什麼,原本須要各類getParameter()
,如今只要經過約定好JavaBean
的字段名,就能把值填充進去了。
仍是上代碼吧,這是咱們當時學Servlet的現狀:
//經過html的name屬性,獲取到值 String username = request.getParameter("username"); String password = request.getParameter("password"); String gender = request.getParameter("gender"); //複選框和下拉框有多個值,獲取到多個值 String[] hobbies = request.getParameterValues("hobbies"); String[] address = request.getParameterValues("address"); //獲取到文本域的值 String description = request.getParameter("textarea"); //獲得隱藏域的值 String hiddenValue = request.getParameter("aaa");
咱們學到SpringMVC的時候是怎麼樣的:
@RequestMapping(value = "/save") @ResponseBody public String taskSave(PushConfig pushConfig) { // 直接使用 String name= pushConfig.getName(); }
爲何SpringMVC能作到?其實就是經過反射來作的。
相信你也有過的經歷:
set
不進去了。因此就組裝失敗了呀~若是在使用框架的時候,爲何咱們每每寫上JavaBean,保持字段名與參數名相同,就能「自動」獲得對應的值呢。這就是反射的好處。
屏蔽掉實現的細節,讓使用者更加方便好用
大部分程序員都是寫業務代碼的,大部分程序員都是維護老系統的,其實要咱們本身寫反射的代碼的時候,真的很少。
從上面也看出,何時會寫反射?寫咱們本身組件/框架的時候。若是想找個地練手一下反射,我以爲自定義註解是一個不錯的選擇。
由於如今用註解的地方不少,主要是夠清晰簡單(不再用對着一堆的XML文件了,哈哈哈哈~)。
我初學的時候寫過一段,能夠簡單參考一下,思路都差很少的哈。下面是使用的效果(使用自定義註解給不一樣的接口增長權限)
@permission("添加分類") /*添加分類*/ void addCategory(Category category); /*查找分類*/ void findCategory(String id); @permission("查找分類") /*查看分類*/ List<Category> getAllCategory();
返回一個代理的Service對象來處理自定義註解:
public class ServiceDaoFactory { private static final ServiceDaoFactory factory = new ServiceDaoFactory(); private ServiceDaoFactory() { } public static ServiceDaoFactory getInstance() { return factory; } //須要判斷該用戶是否有權限 public <T> T createDao(String className, Class<T> clazz, final User user) { System.out.println("添加分類進來了!"); try { //獲得該類的類型 final T t = (T) Class.forName(className).newInstance(); //返回一個動態代理對象出去 return (T) Proxy.newProxyInstance(ServiceDaoFactory.class.getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, PrivilegeException { //判斷用戶調用的是什麼方法 String methodName = method.getName(); System.out.println(methodName); //獲得用戶調用的真實方法,注意參數!!! Method method1 = t.getClass().getMethod(methodName,method.getParameterTypes()); //查看方法上有沒有註解 permission permis = method1.getAnnotation(permission.class); //若是註解爲空,那麼表示該方法並不須要權限,直接調用方法便可 if (permis == null) { return method.invoke(t, args); } //若是註解不爲空,獲得註解上的權限 String privilege = permis.value(); //設置權限【後面經過它來判斷用戶的權限有沒有本身】 Privilege p = new Privilege(); p.setName(privilege); //到這裏的時候,已是須要權限了,那麼判斷用戶是否登錄了 if (user == null) { //這裏拋出的異常是代理對象拋出的,sun公司會自動轉換成運行期異常拋出,因而在Servlet上咱們根據getCause()來判斷是否是該異常,從而作出相對應的提示。 throw new PrivilegeException("對不起請先登錄"); } //執行到這裏用戶已經登錄了,判斷用戶有沒有權限 Method m = t.getClass().getMethod("findUserPrivilege", String.class); List<Privilege> list = (List<Privilege>) m.invoke(t, user.getId()); //看下權限集合中有沒有包含方法須要的權限。使用contains方法,在Privilege對象中須要重寫hashCode和equals() if (!list.contains(p)) { //這裏拋出的異常是代理對象拋出的,sun公司會自動轉換成運行期異常拋出,因而在Servlet上咱們根據getCause()來判斷是否是該異常,從而作出相對應的提示。 throw new PrivilegeException("您沒有權限,請聯繫管理員!"); } //執行到這裏的時候,已經有權限了,因此能夠放行了 return method.invoke(t, args); } }); } catch (Exception e) { new RuntimeException(e); } return null; } }
這篇反射跟網上的文章不太同樣,網上的反射通常都是介紹反射的API如何使用。若是你以爲還不錯,給我點贊吧👍。想要看其餘知識點的同窗,能夠給我留言,我能夠酌情考慮寫一下(哈哈哈哈,忽然變大牌了)
這篇文章涉及到的其餘知識點:JVM類的加載過程、註解、動態代理、SpringMVC、JDBC我都已經寫過文章了,想要閱讀的同窗能夠關注個人GitHub搜索相關關鍵字便可。
若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人公衆號「Java3y」。
在公衆號下回復「888」便可獲取!!
本已收錄至個人GitHub精選文章,歡迎Star: https://github.com/ZhongFuCheng3y/3y求點贊 求關注️ 求分享👥 求留言💬 對我來講真的 很是有用!!!