[toc]
1.Java 反射機制概述
1.1 反射的概述
- Reflection(反射)是被視為<font color=red>動態(tài)語言</font>的關(guān)鍵坐桩,反射機制允許<font color=red>程序在執(zhí)行期</font>借助于Reflection API <font color=red>取得任何類的內(nèi)部信息</font>省容,并能<font color=red>直接操作任意對象的內(nèi)部屬性及方法</font>.
1.2 動態(tài)語言和靜態(tài)語言
- 動態(tài)語言:是一類可以在運行時术陶,改變其結(jié)構(gòu)的語言痪寻。<font color=red>其實就是在運行時代碼可以根據(jù)某些條件改變自身結(jié)構(gòu)</font>
- 靜態(tài)語言:運行時結(jié)構(gòu)不可變的語言就是靜態(tài)語言
- Java不是動態(tài)語言螺句,但可以稱為“準(zhǔn)動態(tài)語言”,因為Java可以利用 反射機制橡类、字節(jié)碼操作 獲得類似動態(tài)語言的特性蛇尚。
1.3 Java反射機制提供的功能
- 在運行時判斷任意一個對象所屬的類
- 在運行時構(gòu)造任意一個類的對象
- 在運行時判斷任意一個類所具有的成員變量和方法
- 在運行時獲取泛型信息
- 在運行時調(diào)用任意一個對象的成員變量和方法
- 在運行時處理注解
- 生成動態(tài)代理
1.4 代碼示例
1.4.1 Person類的代碼
public Integer id;
private String name;
public void show(){
System.out.println("我是要成為海賊王的男人");
}
private String showNation(String nation){
System.out.println("我的國籍"+nation);
return nation;
}
1.4.2 通過反射,創(chuàng)建類的對象
Class clazz = Person.class;
//1.通過反射顾画,創(chuàng)建Person類的對象
Constructor cons = clazz.getConstructor(Integer.class,String.class);
Object obj = cons.newInstance(1,"qiaoba");
Person p = (Person)obj;
System.out.println(p.toString());
1.4.3 通過反射取劫,調(diào)用對象指定屬性、方法
//2.通過反射研侣,調(diào)用對象指定的屬性谱邪、方法
//調(diào)用屬性
Field id = clazz.getDeclaredField("id");
Field name = clazz.getDeclaredField("name");
id.set(p,2);
System.out.println(p.toString());
//調(diào)用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
1.4.4 通過反射,還可以調(diào)用類的私有結(jié)構(gòu)
-
調(diào)用私有的構(gòu)造器
Constructor con1 = clazz.getDeclaredConstructor(String.class); con1.setAccessible(true); Person p1 = (Person) con1.newInstance("suolun"); System.out.println(p1);
-
調(diào)用私有的屬性
Field name = clazz.getDeclaredField("name"); name.setAccessible(true); name.set(p1, "山治"); System.out.println(p1);
-
調(diào)用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class); showNation.setAccessible(true); String nation = (String)showNation.invoke(p1, "中國"); //相當(dāng)于p1.showNation("中國") System.out.println(nation);
1.5 反射機制與面向?qū)ο笾械姆庋b性是不是很矛盾的庶诡?如何看到兩個技術(shù)惦银?
1.6 通過直接new 的方式或反射的方式都可以調(diào)用公共的結(jié)構(gòu),開發(fā)中到底用哪個灌砖?
- 建議:直接new的方式
- 什么時候會使用璧函? 反射的方式。 反射的特征:動態(tài)性
2.理解Class類并<font color=red>獲取Class實例</font>
2.1關(guān)于java.lang.Class 類的理解
2.1.1類的加載過程
- 程序經(jīng)過 javac.exe 命令以后基显,會生成一個或多個字節(jié)碼文件(.class)結(jié)尾
- 接著使用 java.exe 命令對某個字節(jié)碼文件進行解釋運行蘸吓。相當(dāng)于將某個字節(jié)碼文件加載到內(nèi)存中。此過程就稱為 類的加載撩幽。
- 加載到內(nèi)存中的類库继,我們稱為運行時類,此運行時類窜醉,就作為Class的一個實例宪萄。
- 也就是說,Class 的實例就對應(yīng)著一個運行時類榨惰。
- 加載到內(nèi)存中的運行時類拜英,會緩存一定的時間,在此時間之內(nèi)琅催,我們可以通過不同的方式來獲取此運行時類居凶。
2.2 哪些類型可以有 Class 對象?
class:外部類藤抡,成員(成員內(nèi)部類侠碧,靜態(tài)內(nèi)部類),局部內(nèi)部類缠黍,匿名內(nèi)部類
interface:接口
enum: 枚舉
annotation: 注解 @interface
primitive type:基本數(shù)據(jù)類型
-
void
Class c1 = Object.class; Class c2 = Comparable.class; Class c3 = String[].class; Class c4 = int[][].class; Class c5 = ElementType.class; Class c6 = Override.class; Class c7 = int.class; Class c8 = void.class; Class c9 = Class.class; int[] a = new int[10]; int[] b = new int[100]; Class c10 = a.getClass(); Class c11 = b.getClass();
2.3 <font color=red>獲取Class 實例</font>
2.3.1 方式一:調(diào)用運行時類的屬性:.class
Class<Person> clazz1 = Person.class;
或
Class clazz = Person.class;
2.3.2 方式二:通過運行時類的對象弄兜,調(diào)用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
2.3.3 方式三:調(diào)用Class的靜態(tài)方法:forName(String classPath)
Class clazz3 = Class.forName("com.lut.java.Person");
2.3.4 方式四:使用類的加載器: ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.lut.java.Person");
3.類的加載與ClassLoader的理解
3.1 類的加載過程
3.1.1 加載
- 將class 字節(jié)碼文件加載到內(nèi)存中,并將靜態(tài)數(shù)據(jù)轉(zhuǎn)換為方法區(qū)的運行時的數(shù)據(jù)結(jié)構(gòu),然后生成一個代表這個類的java.lang.Class 對象替饿。所有需要訪問和使用類數(shù)據(jù)只能通過這個Class對象语泽。
3.1.2 鏈接:將Java類 的二進制代碼合并到JVM的運行狀態(tài)之中的過程。
- 驗證:確保加載類信息符合JVM規(guī)范
- 準(zhǔn)備:正式為類變量(static) 分配內(nèi)存并設(shè)置類變量默認初始值的階段盛垦,
- 解析:虛擬機常量池內(nèi)的符號引用(常量名) 替換為 直接引用(地址)的過程湿弦。
3.1.3 初始化
- 執(zhí)行類構(gòu)造器< clinit >方法的過程。類構(gòu)造器<clinit> () 方法是由編譯器自動收集類中所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并產(chǎn)生腾夯。(類構(gòu)造器是構(gòu)造類信息的,不是構(gòu)造該類對象的構(gòu)造器)蔬充。
- 當(dāng)初始化一個類時蝶俱,如果其父類還為初始化,則需要先觸發(fā)其父類的初始化饥漫。
- 虛擬機會保證一個類的<clinit> 方法在多線程環(huán)境中被正確加鎖和同步
3.2 類的加載器的作用
- 將class字節(jié)碼文件加載到內(nèi)存中榨呆,并將靜態(tài)數(shù)據(jù)轉(zhuǎn)換為方法區(qū)的運行時的數(shù)據(jù)結(jié)構(gòu),在堆中生成一個代表這個類的java.lang.Class 對象庸队,作為方法區(qū)的類數(shù)據(jù)的訪問入口积蜻。
3.3類緩存
- 某個類被加載到加載器中,它將維持加載(緩存)一段時間彻消。不過JVM垃圾回收機制可以回收這些Class對象竿拆。
3.4 ClassLoader
3.4.1 類加載器(ClassLoader)的分類
引導(dǎo)類加載器:負責(zé)Java平臺的核心庫,該加載器無法直接獲取
擴展類加載器:負責(zé)jar/lib/ext 目錄下的jar包或指定目錄下的jar包 宾尚,裝入工作庫
-
系統(tǒng)類加載器:負責(zé)java-classpath 目錄下的類丙笋,與jar包裝入工作,是最常用的類
//對于自定義類煌贴,使用系統(tǒng)類加載器進行加載 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); //調(diào)用系統(tǒng)類加載器的getParent():獲取擴展類加載器 ClassLoader classLoader1 = classLoader.getParent(); //調(diào)用擴展類加載器的getParent():無法獲取引導(dǎo)類加載器 ClassLoader classLoader2 = classLoader1.getParent(); //null
3.4.2 <font color=red>使用ClassLoader 加載配置文件</font>
- 讀取配置文件的方式一:使用輸入流的方式
Properties pros = new Properties();
//此時的文件默認在當(dāng)前的module下
FileInputStream fis = new FileInputStream("jdbc.properties");
//讀取在module下的src下的文件
//FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
pros.load(fis);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user="+user+",password="+password);
- 讀取配置文件的方式二:使用ClassLoader
- 配置文件默認識別為:當(dāng)前module的src下
Properties pros = new Properties();
//讀取配置文件的方式二:使用ClassLoader
//配置文件默認識別為:當(dāng)前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user="+user+",password="+password);
4.<font color=red>創(chuàng)建運行時類的對象</font>
4.1通過反射創(chuàng)建對應(yīng)的運行時類的對象
①
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
②
Class clazz1 = Person.class;
Person obj1 = (Person) clazz1.newInstance();
4.2 new Instance() :調(diào)用此方法御板,創(chuàng)建對應(yīng)的運行時類的對象,內(nèi)部調(diào)用了運行時類的空參構(gòu)造器牛郑。
4.2.1 要想此方法正常的創(chuàng)建運行時類的對象怠肋,要求
- 運行時類必須提供空參構(gòu)造器
- 空參構(gòu)造器的訪問權(quán)限必須得夠。通常設(shè)置為public
4.2.2 在javabean中要求提供一個public 的空參構(gòu)造器淹朋。原因:
- 便于通過反射笙各,創(chuàng)建運行時類
- 便于子類繼承此運行時類時,默認調(diào)用super() 時瑞你,保證父類有此構(gòu)造器酪惭。
4.3 舉例體會反射的動態(tài)性
@Test
public void test2() throws Exception{
for (int i = 0; i < 100 ; i++) {
int num = new Random().nextInt(3);
String classPath="";
switch (num){
case 0 :
classPath="java.util.Date";
break;
case 1:
classPath="java.lang.Object";
break;
case 2:
classPath="com.lut.java.Person";
break;
}
Object obj = getInstance(classPath);
System.out.println(obj);
}
}
public Object getInstance(String classpath) throws Exception{
Class clazz = Class.forName(classpath);
return clazz.newInstance();
}
5.獲取運行時類的完整結(jié)構(gòu)
5.1 獲取屬性結(jié)構(gòu)
5.1.1getFields()
-
獲取當(dāng)前運行是類及其父類中聲明為public 訪問權(quán)限的屬性
Field[] fields = clazz.getFields(); for (Field field : fields) { System.out.println(field); }
5.1.2 getDeclaredFields()
-
獲取當(dāng)前運行時類中聲明的所有屬性。(不包含父類中聲明的屬性)
Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); }
5.1.3 獲取權(quán)限修飾符
int modifiers = field.getModifiers();
System.out.println(Modifier.toString(modifiers));
5.1.4 獲取數(shù)據(jù)類型
Class type = field.getType();
System.out.print(type.getName()+"\t");
5.1.5 獲取變量名
String fName = field.getName();
System.out.println(fName);
5.2 獲取運行時類的方法結(jié)構(gòu)
5.2.1 getMethods():獲取當(dāng)前運行時類及其所有父類中聲明為public 權(quán)限的方法
Class clazz = Person.class;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
5.2.2 getDeclaredMethods(): 獲取當(dāng)前運行時類中聲明的所有方法者甲。(不包含父類中聲明的方法)
Method[] dms = clazz.getDeclaredMethods();
for (Method dm : dms) {
System.out.println(dm);
}
5.2.3 getAnnotations():獲取方法的聲明的注解
Annotation[] annos = dm.getAnnotations();
for (Annotation anno : annos) {
System.out.println(anno);
}
5.2.4 getModifers(): 獲取權(quán)限修飾符
System.out.print(Modifier.toString(dm.getModifiers()) + "\t");
5.2.5 getReturnType(): 獲取返回類型
System.out.print(dm.getReturnType() + "\t");
5.2.6 getName(): 獲取方法名
System.out.println(dm.getName());
5.2.7 getParameterTypes(): 獲取形參列表
Class[] parameterTypes = dm.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for (int i = 0; i <parameterTypes.length ; i++) {
if(i == parameterTypes.length - 1) {
System.out.print(parameterTypes[i].getName() + " args_" + i );
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
}
5.2.8 getExceptionTypes(): 獲取拋出的異常
lass[] ets = dm.getExceptionTypes();
if(ets.length > 0){
System.out.println("throws ");
for (int i = 0; i < ets.length ; i++) {
if(i == ets.length - 1){
System.out.println(ets[i].getName());
break;
}
System.out.println(ets[i].getName() +",");
}
}
5.3 獲取運行時類的構(gòu)造器結(jié)構(gòu)
5.3.1 getConstructors(): 獲取當(dāng)前運行時類中聲明為 public 的構(gòu)造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
5.3.2 getDeclaredConstructors(): 獲取當(dāng)前運行時類中聲明的所有的構(gòu)造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
5.4 獲取運行時類的父類 getSuperclass()
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
5.5 獲取運行時類的帶泛型的父類 getGenericeSuperclass()
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
5.6 獲取運行時類的帶泛型的父類的泛型
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType =(ParameterizedType) genericSuperclass;
//獲取泛型的類型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
// System.out.println(actualTypeArguments[0].getTypeName());
//或
System.out.println(((Class)actualTypeArguments[0]).getName());
5.7 獲取運行時類實現(xiàn)的接口
5.7.1 獲取運行時類實現(xiàn)的接口
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for (Class c : interfaces) {
System.out.println(c);
}
5.7.2獲取運行時類實現(xiàn)的父類的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class aClass : interfaces1) {
System.out.println(aClass);
}
5.8 獲取運行時類所在包
Class clazz = Person.class;
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
5.9 獲取運行時類聲明的注解
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
6.<font color=red>調(diào)用運行時類的指定結(jié)構(gòu)</font>
6.1如何操作運行時類中指定的屬性
6.1.1保證當(dāng)前屬性是可訪問的 setAccessible(true);
6.1.2 操作運行類中public的屬性(不理想)
Class clazz = Person.class;
//創(chuàng)建運行時類的對象
Person p = (Person) clazz.newInstance();
//獲取指定的屬性
Field id = clazz.getField("id") ;
//設(shè)置當(dāng)前屬性的值
// set(): 參數(shù)1:指明設(shè)置那個對象的屬性 參數(shù)2:將此屬性值設(shè)置為多少
id.set(p, 1001);
/*
獲取當(dāng)前屬性的值
get():參數(shù)1:獲取那個對象的屬性值
*/
int pId = (int) id.get(p);
System.out.println(pId);
6.1.3 操作運行時類中的指定屬性(<font color=red>掌握</font>)
- getDeclaredField(String fieldName):獲取運行時類中指定變量名的屬性
- setAccessible(true) 保證當(dāng)前屬性是可訪問的
- 設(shè)置當(dāng)前屬性的值 set(): 參數(shù)1:指明設(shè)置那個對象的屬性 參數(shù)2:將此屬性值設(shè)置為多少
- 獲取當(dāng)前屬性的值 get():參數(shù)1:獲取那個對象的屬性值
Class clazz = Person.class;
//創(chuàng)建運行時類的對象
Person p = (Person) clazz.newInstance();
//1. getDeclaredField(String fieldName):獲取運行時類中指定變量名的屬性
Field name = clazz.getDeclaredField("name");
//2. 保證當(dāng)前屬性是可訪問的
name.setAccessible(true);
//3. 獲取春感、設(shè)置指定對象的此屬性值
name.set(p,"Tom");
System.out.println(name.get(p));
6.2 操作運行時類的中的指定方法(<font color=red>掌握</font>)
6.2.1 獲取指定的某個方法 getDeclaredMethod(): 參數(shù)1:指明獲取方法的名稱, 參數(shù)2:指明獲取方法的形參列表
6.2.2 invoke(): 參數(shù)1:方法的調(diào)用者 參數(shù)2 : 給方法形參賦值的實參
- invoke() 的返回值即為對應(yīng)類中調(diào)用的方法的返回值
6.2.3 獲取運行時類的非靜態(tài)方法
Class clazz = Person.class;
Person p = (Person)clazz.newInstance();
//1.獲取指定的某個方法
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保證當(dāng)前方法是可訪問的
show.setAccessible(true);
//3.調(diào)用invoke
//invoke() 的返回值即為對應(yīng)類中調(diào)用的方法的返回值
Object returnValue = show.invoke(p, "CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);
6.2.4 獲取運行時類的靜態(tài)方法
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
// Object returnVal = showDesc.invoke(Person.class);
//或
Object returnVal = showDesc.invoke(null);
System.out.println(returnVal);
6.3 調(diào)用運行時類的構(gòu)造器
6.3.1獲取指定的構(gòu)造器 getDeclaredConstructro():參數(shù):指明構(gòu)造器的參數(shù)列表
Class clazz = Person.class;
//1. 獲取指定的構(gòu)造器
//getDeclaredConstructro():參數(shù):指明構(gòu)造器的參數(shù)列表
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2. 保證此構(gòu)造器是可訪問的
constructor.setAccessible(true);
//3. 調(diào)用此構(gòu)造器創(chuàng)建運行時類的對象
Person person = (Person)constructor.newInstance("Tom");
System.out.println(person);
7.反射的應(yīng)用:動態(tài)代理
7.1 代理設(shè)計模式的原理:
- 使用一個代理將對象包裝起來,然后用代理對象取代原始對象鲫懒,任何對原始對象的調(diào)用都要通過代理嫩实。代理對象決定是否以及何時將方法調(diào)用轉(zhuǎn)到原始對象上。
7.2靜態(tài)代理的實例
7.2.1特點:代理類和被代理類在編譯期間窥岩,就確定下來了
7.2.2 代碼
interface ClothFactory{
void produceCloth();
}
class ProxyClothFactory implements ClothFactory{
private ClothFactory clothFactory;//用被代理類對象進行實例化
public ProxyClothFactory(ClothFactory clothFactory){
this.clothFactory = clothFactory;
}
@Override
public void produceCloth() {
System.out.println("代理工廠做一些準(zhǔn)備工作");
clothFactory.produceCloth();
System.out.println("代理工廠做一些后續(xù)的收尾工作");
}
}
// 被代理類
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike 工廠生產(chǎn)一批運動服");
}
}
7.3 動態(tài)代理
interface Human{
String getBelief();
void eat(String food);
}
//被代理類
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜歡吃"+food);
}
}
/*
動態(tài)代理實現(xiàn)需要解決的問題:
① 如何根據(jù)加載到內(nèi)存中的被代理類甲献,動態(tài)的創(chuàng)建一個代理類及其對象
② 當(dāng)通過被代理類的對象調(diào)用方法時,如何動態(tài)的去調(diào)用被代理類中的同名方法
*/
class ProxyFactory{
//調(diào)用此方法颂翼,返回一個代理類的對象晃洒。
public static Object getProxyInstance(Object obj){//obj:被代理類的對象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(), handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//賦值時肩袍,也需要使用被代理類的對象進行賦值
public void bind(Object obj){ //obj: 被代理類的對象
this.obj = obj;
}
//當(dāng)我們通過代理類的對象鳖擒,調(diào)用方法a時,就會自動調(diào)用如下方法:invoke
//將被代理類要執(zhí)行的方法a的功能就聲明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即為代理類對象調(diào)用的方法颅崩,此方法也就作為了被代理類對象要調(diào)用的方法
//obj:被代理類的對象
Object returnValue = method.invoke(obj,args);
//上述方法的返回值就作為當(dāng)前類中到的invoke()的返回值
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance: 代理類的對象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//當(dāng)通過代理類對象調(diào)用方法是呻疹,會自動調(diào)用被代理類中的同名方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣燙");
}
}