SpringBoot學習系列之一(反射)

  • 2019 年 10 月 3 日
  • 筆記

  最近在學習SpringBoot的知識,動起手來學習的時候才發現SpringBoot項目採用了大量的反射機制,暈,作為一個應屆畢業生,以前學習反射的時候給我的感覺就是,這個到底用來幹嘛的,好像沒啥用啊,而且接觸的地方也不是非常的多,接觸比較多的地方還是JDBC註冊驅動的那條語句:

Class.forName("com.mysql.jdbc.Driver");  //註冊數據庫驅動  

  這樣肯定是不行的,想要學好SpringBoot的第一步,就是把反射學好。於是,我決定重新把遺忘的而重要的知識撿起來,重新好好地學一下。

  我們帶着個兩個問題來學:反射是什麼?反射怎麼用?


 

  反射是什麼?

  JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。

  要想解剖一個類,必須先要獲取到該類的位元組碼文件對象。而解剖使用的就是Class類中的方法.所以先要獲取到每一個位元組碼文件對應的Class類型的對象.

  這裡用一個類,Person類的加載過程來具體說明一下,如下圖

 

 

這裡補充一下,當我們的JVM把Person.class加載到內存的時候,會同步的產生關於這個文件的Class對象,這個對象裏面封裝的就是Person的信息,而且,不管我們new Person()使用多少次,我們的JVM只會為它創建一個Class對象。

我們反射的本質就是得到Class對象之後,通過調用Class中的方法反向的獲取Person對象的各種信息。


 

  反射怎麼用?

  一、Class對象的獲取

   通過上面的講解,我們知道,要使用反射的關鍵就是獲取到Class對象,然後通過調用Class對象裏面的方法,獲取到我們想要知道的東西,比如這個類的包路徑,都有什麼方法等等。而獲取Class對象的方法一共有三個,這裡一一介紹。

 

  1.Object ——> getClass(),通常應用在:比如你傳過來一個 Object

package ydy;    /**   * 獲取Class對象的三種方法之第一種   * @author dengyan.yao   *   */  public class Reflection {  	public static void main(String[] args) {    		//獲取Class對象的第一種方法  		Person person = new Person();//new的時候產生了一個Person對象和一個Class對象  		Class perClass = person.getClass();//獲取Class對象  		System.out.println("獲取到的對象:" + perClass);      	}  }  

  

  2、類名.class 的方式得到,該方法最為安全可靠,程序性能更高 

package ydy;    /**   * 獲取Class對象的三種方法之第二種   * @author dengyan.yao   *   */  public class Reflection {  	public static void main(String[] args) {    		//獲取Class對象的第二種方法  		Class perClass = Person.class;  		System.out.println("獲取到的對象:" + perClass);      	}  }  

  

  3、通過 Class 對象的 forName() 靜態方法來獲取,用的最多

package ydy;    /**   * 獲取Class對象的三種方法之第三種   * @author dengyan.yao   *   */  public class Reflection {  	public static void main(String[] args) {    		//獲取Class對象的第三種方法  		Class perClass;  		try {  			perClass = Class.forName("ydy.Person");//這裡的String路徑是類的全路徑,從包名到類名  			System.out.println("獲取到的對象:" + perClass);  		} catch (ClassNotFoundException e) {  			// TODO Auto-generated catch block  			e.printStackTrace();  		}      	}  }  

  

  二、反射的簡單使用

  反射的使用主要就是獲取到了Class對象之後,通過調用Class對象的方法來操縱,使用Person對象的一些屬性,這裡主要使用第三種方式來獲取Class對象,並且列舉一些簡單的使用。

  

  1、獲取成員變量並調用

  定義一個Person類,裏面添加一些屬性:

package ydy;    public class Person {  	public String name;//公開的name  	private Double weight;//私有的weight  	protected char sex; //受保護的sex  	int age;	//默認的age  	@Override  	public String toString() {  		return "Person [name=" + name + ", weight=" + weight + ", sex=" + sex + ", age=" + age + "]";  	}    }  

  編寫測試類Test:

package ydy;    import java.lang.reflect.Field;  import java.lang.reflect.InvocationTargetException;    /**   * 獲取成員變量並調用   * @author dengyan.yao   *   */  public class Test {  	public static void main(String[] args) throws Exception{  		//獲取Class對象  		Class perClass = Class.forName("ydy.Person");  		//獲取字段  		System.out.println("獲取所有字段");  		 Field[] fieldArray = perClass.getDeclaredFields();  		 for (Field field : fieldArray) {  			System.out.println(field);  		}  		 System.out.println("獲取所有共有字段");  		 fieldArray = perClass.getFields();  		 for (Field field : fieldArray) {  			 System.out.println(field);  		}  		 System.out.println("獲取所有共有字段並調用");  		 Field f = perClass.getField("name");  		 System.out.println(f);  		 //獲取Person對象  		 Object per = perClass.getConstructor().newInstance();  		 //為字段設置值  		 f.set(per, "反射");  		 //驗證  		 Person person = (Person)per;  		 System.out.println("驗證名字:" +person.name);  	}  }  

  測試結果:

獲取所有字段  public java.lang.String ydy.Person.name  private java.lang.Double ydy.Person.weight  protected char ydy.Person.sex  int ydy.Person.age  獲取所有共有字段  public java.lang.String ydy.Person.name  獲取所有共有字段並調用  public java.lang.String ydy.Person.name  驗證名字:反射  

  

  2、獲取成員變量並調用

  Person類

package ydy;    public class Person {    	//---------------構造方法-------------------  		//默認的構造方法  	Person(String str){  			System.out.println("(默認)的構造方法 s = " + str);  		}    		//無參構造方法  		public Person(){  			System.out.println("調用了公有、無參構造方法執行了。。。");  		}    		//有一個參數的構造方法  		public Person(char name){  			System.out.println("姓名:" + name);  		}    		//有多個參數的構造方法  		public Person(String name ,int age){  			System.out.println("姓名:"+name+"年齡:"+ age);//這的執行效率有問題,以後解決。  		}    		//受保護的構造方法  		protected Person(boolean n){  			System.out.println("受保護的構造方法 n = " + n);  		}    		//私有構造方法  		private Person(int age){  			System.out.println("私有的構造方法   年齡:"+ age);  		}  }  

  測試類

package ydy;    import java.lang.reflect.Constructor;  import java.lang.reflect.Field;  import java.lang.reflect.InvocationTargetException;    /**   * 獲取構造方法並調用   * @author dengyan.yao   *   */  public class Test {      public static void main(String[] args) throws Exception{          //1.加載Class對象                  Class clazz = Class.forName("ydy.Person");                      //2.獲取所有公有構造方法                  System.out.println("所有公有構造方法");                  Constructor[] conArray = clazz.getConstructors();                  for(Constructor c : conArray){                      System.out.println(c);                  }                      System.out.println("所有的構造方法(包括:私有、受保護、默認、公有)");                  conArray = clazz.getDeclaredConstructors();                  for(Constructor c : conArray){                      System.out.println(c);                  }                    System.out.println("獲取公有、無參的構造方法");                  Constructor con = clazz.getConstructor();                  System.out.println("con = " + con);                  //調用構造方法                  Object obj = con.newInstance();                    System.out.println("獲取私有構造方法,並調用");                  con = clazz.getDeclaredConstructor(char.class);                  System.out.println(con);                  //調用構造方法                  con.setAccessible(true);//暴力訪問(忽略掉訪問修飾符)                  obj = con.newInstance('');              }  }

  測試結果

所有公有構造方法  public ydy.Person(java.lang.String,int)  public ydy.Person(char)  public ydy.Person()  所有的構造方法(包括:私有、受保護、默認、公有)  private ydy.Person(int)  protected ydy.Person(boolean)  public ydy.Person(java.lang.String,int)  public ydy.Person(char)  public ydy.Person()  ydy.Person(java.lang.String)  獲取公有、無參的構造方法  con = public ydy.Person()  調用了公有、無參構造方法執行了。。。  獲取私有構造方法,並調用  public ydy.Person(char)  姓名:男  

  

  3、獲取成員方法並調用

  Person類

package ydy;    public class Person {          public void eat(String s){              System.out.println("調用了:公有的,String參數的eat(): s = " + s);          }          protected void paly(){              System.out.println("調用了:受保護的,無參的paly()");          }          void run(){              System.out.println("調用了:默認的,無參的run()");          }          private String study(int age){              System.out.println("調用了,私有的,並且有返回值的,int參數的study(): age = " + age);              return "abcd";          }  }

  測試類

package ydy;    import java.lang.reflect.Constructor;  import java.lang.reflect.Field;  import java.lang.reflect.InvocationTargetException;  import java.lang.reflect.Method;    /**   * 獲取成員方法並調用   * @author dengyan.yao   *   */  public class Test {  	public static void main(String[] args) throws Exception{  				//獲取Class對象  				Class stuClass = Class.forName("ydy.Person");    				//獲取所有公有方法  				System.out.println("獲取所有的”公有“方法");  				stuClass.getMethods();  				Method[] methodArray = stuClass.getMethods();  				for(Method m : methodArray){  					System.out.println(m);  				}    				//獲取所有方法  				System.out.println("獲取所有的方法,包括私有的");  				methodArray = stuClass.getDeclaredMethods();  				for(Method m : methodArray){  					System.out.println(m);  				}    				//獲取特定的共有方法  				System.out.println("獲取公有的eat()方法");  				Method m = stuClass.getMethod("eat", String.class);  				System.out.println(m);  				//實例化一個Student對象  				Object obj = stuClass.getConstructor().newInstance();  				m.invoke(obj, "反射");      				System.out.println("獲取私有的study()方法");  				m = stuClass.getDeclaredMethod("study", int.class);  				System.out.println(m);      				m.setAccessible(true);//解除私有限定  				Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(獲取有反射),一個是實參  				System.out.println("返回值:" + result);    		}  }  

   測試結果

獲取所有的”公有“方法  public void ydy.Person.eat(java.lang.String)  public final void java.lang.Object.wait() throws java.lang.InterruptedException  public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException  public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException  public boolean java.lang.Object.equals(java.lang.Object)  public java.lang.String java.lang.Object.toString()  public native int java.lang.Object.hashCode()  public final native java.lang.Class java.lang.Object.getClass()  public final native void java.lang.Object.notify()  public final native void java.lang.Object.notifyAll()  獲取所有的方法,包括私有的  void ydy.Person.run()  public void ydy.Person.eat(java.lang.String)  private java.lang.String ydy.Person.study(int)  protected void ydy.Person.paly()  獲取公有的eat()方法  public void ydy.Person.eat(java.lang.String)  調用了:公有的,String參數的eat(): s = 反射  獲取私有的study()方法  private java.lang.String ydy.Person.study(int)  調用了,私有的,並且有返回值的,int參數的study(): age = 20  返回值:abcd  

  


  

  總結

  1.反射的定義:

  在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能

  2.反射的用法:

  主要是通過獲取Class對象之後調用Class的方法來使用,獲取Class對象的方法有三個,分別是:

    對象調用 getClass() 方法來獲取,通常應用在:比如你傳過來一個 Object

    類名.class 的方式得到,該方法最為安全可靠,程序性能更高

    通過 Class 對象的 forName() 靜態方法來獲取,用的最多