1. 程式人生 > >SpringBoot學習系列之一(反射)

SpringBoot學習系列之一(反射)

  最近在學習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() 靜態方法來獲取,用的最多

&n