1.1 框架的概念
? 半成品軟件篙贸⊥抖樱可以在框架的基礎(chǔ)上進(jìn)行軟件開發(fā),簡化編碼爵川。學(xué)習(xí)框架并不需要了解反射敷鸦,但是要是想自己寫一個(gè)框架,那么就要對(duì)反射機(jī)制有很深入的了解寝贡。
1.2 反射
? 反射機(jī)制:將類的各個(gè)組成部分封裝為其他對(duì)象扒披,這就是反射機(jī)制。
反射的好處:
可以在程序運(yùn)行過程中圃泡,操作這些對(duì)象碟案。
可以解耦,提高程序的可擴(kuò)展性颇蜡。
補(bǔ)充:Java代碼在計(jì)算機(jī)中經(jīng)歷的三個(gè)階段:
Source源代碼階段:.java被編譯成.class字節(jié)碼文件价说。
-
Class類對(duì)象階段:.class字節(jié)碼文件被類加載器加載進(jìn)內(nèi)存,并將其封裝成Class對(duì)象(用于在內(nèi)存中描述字節(jié)碼文件)澡匪,
Class對(duì)象將原字節(jié)碼文件中的成員變量抽取出來封裝成數(shù)組
Field[]
,將原字節(jié)碼文件中的構(gòu)造函數(shù)抽取出來封裝成數(shù)組
Construction[]
熔任,在將成員方法封裝成
Method[]
。當(dāng)然Class類內(nèi)不止這三個(gè)唁情,還封裝了很多疑苔,我們常用的就這三個(gè)。 RunTime運(yùn)行時(shí)階段:創(chuàng)建對(duì)象的過程new甸鸟。
2.1 獲取Class對(duì)象的方式
獲取Class對(duì)象的三種方式對(duì)應(yīng)著java代碼在計(jì)算機(jī)中的三個(gè)階段
-
Source源代碼階段
Class.forName("全類名")
:將字節(jié)碼文件加載進(jìn)內(nèi)存惦费,返回Class對(duì)象- 多用于配置文件兵迅,將類名定義在配置文件中。讀取文件薪贫,加載類恍箭。
-
Class類對(duì)象階段
類名.class
:通過類名的屬性class獲取- 多用于參數(shù)的傳遞
-
Runtime運(yùn)行時(shí)階段
對(duì)象.getClass()
:getClass()方法是定義在Objec類中的方法- 多用于對(duì)象的獲取字節(jié)碼的方式
結(jié)論:同一個(gè)字節(jié)碼文件(.class)在一次程序運(yùn)行過程中,只會(huì)被加載一次瞧省,無論通過哪一種方式獲取的Class對(duì)象都是同一個(gè)扯夭。
舉例
public void reflect1() throws ClassNotFoundException {
//方式一:Class.forName("全類名");
Class cls1 = Class.forName("com.test.domain.Person"); //Person自定義實(shí)體類
System.out.println("cls1 = " + cls1); //方式二:類名.class
Class cls2 = Person.class;
System.out.println("cls2 = " + cls2); //方式三:對(duì)象.getClass();
Person person = new Person();
Class cls3 = person.getClass();
System.out.println("cls3 = " + cls3); // == 比較三個(gè)對(duì)象
System.out.println("cls1 == cls2 : " + (cls1 == cls2)); //true
System.out.println("cls1 == cls3 : " + (cls1 == cls3)); //true
3.1 Class對(duì)象功能
詳見JDK文檔 JDK doc-zh
3.1.1 成員變量Field
Field[] getFields()
:獲取所有public修飾的成員變量
Field getField(String name)
獲取指定名稱的 public修飾的成員變量
Field[] getDeclaredFields()
獲取所有的成員變量,不考慮修飾符
Field getDeclaredField(String name)
獲取指定名稱的所有的成員變量
測試類:
class Person {
public String a;
protected String b;
String c;
private String d;
}
Field的有關(guān)操作
設(shè)置值
void set(Object obj, Object value)
獲取值
get(Object obj)
忽略訪問權(quán)限修飾符的安全檢查
setAccessible(true)
:暴力反射
測試:getFields和getField(String name)
public void reflect2() throws Exception {
//0鞍匾、獲取Person的Class對(duì)象
Class personClass = Person.class;
//1交洗、Field[] getFields()獲取所有public修飾的成員變量
Field[] fields = personClass.getFields();
for(Field field : fields){
System.out.println(field);
}
System.out.println("=============================");
//2.Field getField(String name)
Field a = personClass.getField("a");
//獲取成員變量a 的值 [也只能獲取公有的,獲取私有的或者不存在的字符會(huì)拋出異常]
Person p = new Person();
Object value = a.get(p);
System.out.println("value = " + value); // value == null
//設(shè)置屬性a的值
a.set(p,"張三");
System.out.println(p); // Person(name=null,a="張三", ...)
}
測試 getDeclaredFields和getDeclaredField(String name)方法
對(duì)于私有變量雖然能會(huì)獲取到橡淑,但不能直接set和get ,如果需要构拳,加上暴力破解
public void reflect3() throws Exception {
Class personClass = Person.class; //Field[] getDeclaredFields():獲取所有的成員變量,不考慮修飾符
Field[] declaredFields = personClass.getDeclaredFields();
for(Field filed : declaredFields){
System.out.println(filed);
}
System.out.println("===================================");
//Field getDeclaredField(String name)
Field d = personClass.getDeclaredField("d");
//private String d;
Person p = new Person();
Object value1 = d.get(p); //會(huì)拋出異常
System.out.println("value1 = " + value1); //對(duì)于私有變量雖然能會(huì)獲取到梁棠,但不能直接set和get
//忽略訪問權(quán)限修飾符的安全檢查
d.setAccessible(true);//暴力反射
Object value2 = d.get(p);
System.out.println("value2 = " + value2); }
3.1.2 獲取構(gòu)造方法Constructor
具體描述與命名方式與獲取成員變量類似
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
獲取的主要目的:創(chuàng)建對(duì)象
注意:如果使用空參數(shù)構(gòu)造方法創(chuàng)建對(duì)象置森,操作可以簡化:Class對(duì)象的newInstance方法
此方法在JDK1.9后棄用
測試類:
public class Person {
private String name;
private Integer age;
//無參構(gòu)造函數(shù)
public Person() {}
//單個(gè)參數(shù)的構(gòu)造函數(shù),且為私有構(gòu)造方法
private Person(String name){}
//有參構(gòu)造函數(shù)
public Person(String name, Integer age){
this.name = name;
this.age = age;
}
}
測試:
/**
* 2. 獲取構(gòu)造方法們
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(類<?>... parameterTypes)
*/
@Test
public void reflect4() throws Exception {
Class personClass = Person.class;
//Constructor<?>[] getConstructors()
Constructor[] constructors = personClass.getConstructors();
for(Constructor constructor : constructors){ //Constructor 對(duì)象reflect包下的 import java.lang.reflect.Constructor;
System.out.println(constructor);
}
System.out.println("==========================================");
//獲取無參構(gòu)造函數(shù) 注意:Person類中必須要有無參的構(gòu)造函數(shù)符糊,不然拋出異常
Constructor constructor1 = personClass.getConstructor();
System.out.println("constructor1 = " + constructor1);
//獲取到構(gòu)造函數(shù)后可以用于創(chuàng)建對(duì)象
Object person1 = constructor1.newInstance();//Constructor類內(nèi)提供了初始化方法newInstance();方法
System.out.println("person1 = " + person1);
System.out.println("==========================================");
//獲取有參的構(gòu)造函數(shù) //public Person(String name, Integer age) 參數(shù)類型順序要與構(gòu)造函數(shù)內(nèi)一致凫海,且參數(shù)類型為字節(jié)碼類型
Constructor constructor2 = personClass.getConstructor(String.class,Integer.class);
System.out.println("constructor2 = " + constructor2);
//創(chuàng)建對(duì)象
Object person2 = constructor2.newInstance("張三", 23); //獲取的是有參的構(gòu)造方法,就必須要給參數(shù)
System.out.println(person2);
System.out.println("=========================================");
//對(duì)于一般的無參構(gòu)造函數(shù)濒蒋,我們都不會(huì)先獲取無參構(gòu)造器之后在進(jìn)行初始化盐碱。而是直接調(diào)用Class類內(nèi)的newInstance()方法
Object person3 = personClass.newInstance();
System.out.println("person3 = " + person3);
//我們之前使用的 Class.forName("").newInstance; 其本質(zhì)上就是調(diào)用了類內(nèi)的無參構(gòu)造函數(shù)來完成實(shí)例化的
//故可以得出結(jié)論 我們以后在使用 Class.forName("").newInstance; 反射創(chuàng)建對(duì)象時(shí),一定要保證類內(nèi)有無參構(gòu)造函數(shù)
}
對(duì)于getDeclaredConstructor方法和getDeclaredConstructors方法
? 對(duì)于多出個(gè)Declared關(guān)鍵詞的兩個(gè)方法沪伙,與不帶這個(gè)詞的兩個(gè)方法的對(duì)比瓮顽。與之前敘述的一樣,getDeclaredConstructor
方法可以獲取到任何訪問權(quán)限的構(gòu)造器围橡,而getConstructor
方法只能獲取public修飾的構(gòu)造器暖混。具體不再測試。此外在構(gòu)造器的對(duì)象內(nèi)也有setAccessible(true);
方法翁授,并設(shè)置成true就可以操作了拣播。
?
3.1.3 獲取成員方法們Method
Method[] getMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
測試實(shí)體類
public class Person {
private String name;
private Integer age;
//無參構(gòu)造函數(shù)
public Person() {
}
//有參構(gòu)造函數(shù)
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
//無參方法
public void eat(){
System.out.println("eat...");
}
//重載有參方法
public void eat(String food){
System.out.println("eat..."+food);
}
}
測試invoke方法
/**
* 3. 獲取成員方法們:
* Method[] getMethods()
* Method getMethod(String name, 類<?>... parameterTypes)
*/
@Test
public void reflect5() throws Exception {
Class personClass = Person.class;
//獲取指定名稱的方法
Method eat_method1 = personClass.getMethod("eat");
//執(zhí)行方法
Person person = new Person();
Object rtValue = eat_method1.invoke(person);//如果方法有返回值類型可以獲取到,沒有就為null
//輸出返回值 eat方法沒有返回值收擦,故輸出null
System.out.println("rtValue = " + rtValue);
System.out.println("--------------------------------------------");
//獲取有參的構(gòu)造函數(shù) 有兩個(gè)參數(shù) 第一個(gè)方法名 第二個(gè)參數(shù)列表 贮配,不同的參數(shù)是不同的方法(重載)
Method eat_method2 = personClass.getMethod("eat", String.class);
//執(zhí)行方法
eat_method2.invoke(person,"飯");
System.out.println("============================================");
//獲取方法列表
Method[] methods = personClass.getMethods();
for(Method method : methods){ //注意:獲取到的方法名稱不僅僅是我們?cè)赑erson類內(nèi)看到的方法
System.out.println(method); //繼承下來的方法也會(huì)被獲取到(當(dāng)然前提是public修飾的)
}
}
測試getName方法
getName()方法獲取的方法名僅僅就是方法名(不帶全類名),且不帶有參數(shù)列表塞赂。
public void reflect6() throws NoSuchMethodException {
Class personClass = Person.class;
Method[] methods = personClass.getMethods();
for(Method method : methods){
System.out.println(method);
//獲取方法名
String name = method.getName();
System.out.println(name);
}
}
獲取全類名
String getName()
如xx.xx.xx.Person
public void reflect7(){
Class personClass = Person.class;
String className = personClass.getName();
System.out.println(className);
}
4 案例
? 寫一個(gè)"框架"泪勒,不能改變?cè)擃惖娜魏未a的前提下,可以幫我們創(chuàng)建任意類的對(duì)象,并且執(zhí)行其中任意方法圆存。
4.1 配置文件實(shí)現(xiàn)
-
實(shí)體類Person
public class Person { //無參方法 public void eat(){ System.out.println("eat..."); } }
-
配置文件
pro.properties
.properties為擴(kuò)展名的文件被認(rèn)為是配置文件叼旋,支持在java.util.Properties中,以key=Value(\n)的形式配置
className = com.test.domain.Person methodName = eat
-
編寫測試方法
public class ReflectTest { public static void main(String[] args) throws Exception { /** * 前提:不能改變?cè)擃惖娜魏未a沦辙》蛑玻可以創(chuàng)建任意類的對(duì)象,可以執(zhí)行任意方法 * 即:拒絕硬編碼 */ //1.加載配置文件 //1.1創(chuàng)建Properties對(duì)象 Properties pro = new Properties(); //1.2加載配置文件油讯,轉(zhuǎn)換為一個(gè)集合 //1.2.1獲取class目錄下的配置文件 使用類加載器 ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("pro.properties"); pro.load(is); //2.獲取配置文件中定義的數(shù)據(jù) String className = pro.getProperty("className"); String methodName = pro.getProperty("methodName"); //3.加載該類進(jìn)內(nèi)存 Class cls = Class.forName(className); //4.創(chuàng)建對(duì)象 Object obj = cls.newInstance(); //5.獲取方法對(duì)象 Method method = cls.getMethod(methodName); //6.執(zhí)行方法 method.invoke(obj); } }
4.2 注解實(shí)現(xiàn)
? Pro.java 注釋定義文件
? 使用方式 用于類 @Pro(ClassName="全類名", FuncName = "方法名")
? 獲取注釋:
Class cls = MethodFactory.class;
Pro annotation = (Pro) cls.getAnnotation(Pro.class);
String className = annotation.ClassName();
String funcName = annotation.FuncName();
<script src="https://gist.github.com/AaronFang123/51846128f7b37a919f951189e02cd467.js">
</script>
5. 反射的優(yōu)點(diǎn)
? 對(duì)于框架來說详民,是封裝好的,直接用就可以了撞羽,而不能去修改框架內(nèi)的代碼阐斜。但如果我們使用傳統(tǒng)的new形式來實(shí)例化,那么當(dāng)類名更改時(shí)我們就要修改Java代碼诀紊,這是很繁瑣的。修改Java代碼以后我們還要進(jìn)行測試隅俘,重新編譯邻奠、發(fā)布等等一系列的操作。而如果我們僅僅只是修改配置文件为居,就來的簡單的多碌宴,配置文件就是一個(gè)實(shí)實(shí)在在的物理文件。
? 此外使用反射還能達(dá)到解耦的效果蒙畴,假設(shè)我們使用的是new這種形式進(jìn)行對(duì)象的實(shí)例化贰镣。此時(shí)如果在項(xiàng)目的某一個(gè)小模塊中我們的一個(gè)實(shí)例類丟失了,那么在編譯期間就會(huì)報(bào)錯(cuò)膳凝,以導(dǎo)致整個(gè)項(xiàng)目無法啟動(dòng)碑隆。而對(duì)于反射創(chuàng)建對(duì)象Class.forName("全類名");這種形式,我們?cè)诰幾g期需要的僅僅只是一個(gè)字符串(全類名)蹬音,在編譯期不會(huì)報(bào)錯(cuò)上煤,這樣其他的模塊就可以正常的運(yùn)行,而不會(huì)因?yàn)橐粋€(gè)模塊的問題導(dǎo)致整個(gè)項(xiàng)目崩潰著淆。這就是Spring框架中IOC控制反轉(zhuǎn)的本質(zhì)劫狠。