Java高級-反射&動態(tài)代理詳解
1. 反射
1.1 反射的概述
專業(yè)的解釋(了解一下):
- 是在運(yùn)行狀態(tài)中,對于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;
- 對于任意一個(gè)對象,都能夠調(diào)用它的任意屬性和方法;
- 這種動態(tài)獲取信息以及動態(tài)調(diào)用對象方法的功能稱為Java語言的反射機(jī)制。
通俗的理解:(掌握)
利用反射創(chuàng)建的對象可以無視修飾符調(diào)用類里面的內(nèi)容
可以跟配置文件結(jié)合起來使用,把要?jiǎng)?chuàng)建的對象信息和方法寫在配置文件中。
- 讀取到什么類,就創(chuàng)建什么類的對象
- 讀取到什么方法,就調(diào)用什么方法
- 此時(shí)當(dāng)需求變更的時(shí)候不需要修改代碼,只要修改配置文件即可。
1.2 學(xué)習(xí)反射到底學(xué)什么?
反射都是從class字節(jié)碼文件中獲取的內(nèi)容。(前提)
- 如何獲取class字節(jié)碼文件的對象
- 利用反射如何獲取構(gòu)造方法(創(chuàng)建對象)
- 利用反射如何獲取成員變量(賦值,獲取值)
- 利用反射如何獲取成員方法(運(yùn)行)
1.3 獲取字節(jié)碼文件對象的三種方式
- Class這個(gè)類里面的靜態(tài)方法forName(“全類名”)(最常用)
- 通過class屬性獲取
- 通過對象獲取字節(jié)碼文件對象
代碼示例:
//1.Class這個(gè)類里面的靜態(tài)方法forName //Class.forName("類的全類名"): 全類名 = 包名 + 類名 Class clazz1 = Class.forName("com.itheima.reflectdemo.Student"); //源代碼階段獲取 --- 先把Student加載到內(nèi)存中,再獲取字節(jié)碼文件的對象 //clazz 就表示Student這個(gè)類的字節(jié)碼文件對象。 //就是當(dāng)Student.class這個(gè)文件加載到內(nèi)存之后,產(chǎn)生的字節(jié)碼文件對象 ? ? //2.通過class屬性獲取 //類名.class Class clazz2 = Student.class; ? //因?yàn)閏lass文件在硬盤中是唯一的,所以,當(dāng)這個(gè)文件加載到內(nèi)存之后產(chǎn)生的對象也是唯一的 System.out.println(clazz1 == clazz2);//true ? ? //3.通過Student對象獲取字節(jié)碼文件對象 Student s = new Student(); Class clazz3 = s.getClass(); System.out.println(clazz1 == clazz2);//true System.out.println(clazz2 == clazz3);//true
1.4 字節(jié)碼文件和字節(jié)碼文件對象
- java文件:就是我們自己編寫的java代碼。
- 字節(jié)碼文件:就是通過java文件編譯之后的class文件(是在硬盤上真實(shí)存在的,用眼睛能看到的)
- 字節(jié)碼文件對象:當(dāng)class文件加載到內(nèi)存之后,虛擬機(jī)自動創(chuàng)建出來的對象。
- 這個(gè)對象里面至少包含了:構(gòu)造方法,成員變量,成員方法。
- 而我們的反射獲取的是什么?字節(jié)碼文件對象,這個(gè)對象在內(nèi)存中是唯一的。
1.5 獲取構(gòu)造方法
規(guī)則:
- get表示獲取
- Declared表示私有
- 最后的s表示所有,復(fù)數(shù)形式
- 如果當(dāng)前獲取到的是私有的,必須要臨時(shí)修改訪問權(quán)限,否則無法使用
方法名 | 說明 |
---|---|
Constructor<?>[] getConstructors() | 獲得所有的構(gòu)造(只能public修飾) |
Constructor<?>[] getDeclaredConstructors() | 獲得所有的構(gòu)造(包含private修飾) |
Constructor<T> getConstructor(Class<?>... parameterTypes) | 獲取指定構(gòu)造(只能public修飾) |
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | 獲取指定構(gòu)造(包含private修飾) |
代碼示例:
public class ReflectDemo2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { //1.獲得整體(class字節(jié)碼文件對象) Class clazz = Class.forName("com.itheima.reflectdemo.Student"); ? ? //2.獲取構(gòu)造方法對象 //獲取所有構(gòu)造方法(public) Constructor[] constructors1 = clazz.getConstructors(); for (Constructor constructor : constructors1) { System.out.println(constructor); } ? System.out.println("======================="); ? //獲取所有構(gòu)造(帶私有的) Constructor[] constructors2 = clazz.getDeclaredConstructors(); ? for (Constructor constructor : constructors2) { System.out.println(constructor); } System.out.println("======================="); ? //獲取指定的空參構(gòu)造 Constructor con1 = clazz.getConstructor(); System.out.println(con1); ? Constructor con2 = clazz.getConstructor(String.class,int.class); System.out.println(con2); ? System.out.println("======================="); //獲取指定的構(gòu)造(所有構(gòu)造都可以獲取到,包括public包括private) Constructor con3 = clazz.getDeclaredConstructor(); System.out.println(con3); //了解 System.out.println(con3 == con1); //每一次獲取構(gòu)造方法對象的時(shí)候,都會新new一個(gè)。 ? Constructor con4 = clazz.getDeclaredConstructor(String.class); System.out.println(con4); } }
1.6 獲取構(gòu)造方法并創(chuàng)建對象
涉及到的方法:newInstance
代碼示例:
//首先要有一個(gè)javabean類 public class Student { private String name; ? private int age; ? ? public Student() { ? } ? public Student(String name) { this.name = name; } ? private Student(String name, int age) { this.name = name; this.age = age; } ? ? /** * 獲取 * @return name */ public String getName() { return name; } ? /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } ? /** * 獲取 * @return age */ public int getAge() { return age; } ? /** * 設(shè)置 * @param age */ public void setAge(int age) { this.age = age; } ? public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } } ? ? ? //測試類中的代碼: //需求1: //獲取空參,并創(chuàng)建對象 ? //1.獲取整體的字節(jié)碼文件對象 Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student"); //2.獲取空參的構(gòu)造方法 Constructor con = clazz.getConstructor(); //3.利用空參構(gòu)造方法創(chuàng)建對象 Student stu = (Student) con.newInstance(); System.out.println(stu); ? ? System.out.println("============================================="); ? ? //測試類中的代碼: //需求2: //獲取帶參構(gòu)造,并創(chuàng)建對象 //1.獲取整體的字節(jié)碼文件對象 Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student"); //2.獲取有參構(gòu)造方法 Constructor con = clazz.getDeclaredConstructor(String.class, int.class); //3.臨時(shí)修改構(gòu)造方法的訪問權(quán)限(暴力反射) con.setAccessible(true); //4.直接創(chuàng)建對象 Student stu = (Student) con.newInstance("zhangsan", 23); System.out.println(stu);
1.7 獲取成員變量
規(guī)則:
- get表示獲取
- Declared表示私有
- 最后的s表示所有,復(fù)數(shù)形式
- 如果當(dāng)前獲取到的是私有的,必須要臨時(shí)修改訪問權(quán)限,否則無法使用
方法名:
方法名 | 說明 |
---|---|
Field[] getFields() | 返回所有成員變量對象的數(shù)組(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成員變量對象的數(shù)組,存在就能拿到 |
Field getField(String name) | 返回單個(gè)成員變量對象(只能拿public的) |
Field getDeclaredField(String name) | 返回單個(gè)成員變量對象,存在就能拿到 |
代碼示例:
public class ReflectDemo4 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //獲取成員變量對象 ? //1.獲取class對象 Class clazz = Class.forName("com.itheima.reflectdemo.Student"); ? //2.獲取成員變量的對象(Field對象)只能獲取public修飾的 Field[] fields1 = clazz.getFields(); for (Field field : fields1) { System.out.println(field); } ? System.out.println("==============================="); ? //獲取成員變量的對象(public + private) Field[] fields2 = clazz.getDeclaredFields(); for (Field field : fields2) { System.out.println(field); } ? System.out.println("==============================="); //獲得單個(gè)成員變量對象 //如果獲取的屬性是不存在的,那么會報(bào)異常 //Field field3 = clazz.getField("aaa"); //System.out.println(field3);//NoSuchFieldException ? Field field4 = clazz.getField("gender"); System.out.println(field4); ? System.out.println("==============================="); //獲取單個(gè)成員變量(私有) Field field5 = clazz.getDeclaredField("name"); System.out.println(field5); ? } } ? ? ? public class Student { private String name; ? private int age; ? public String gender; ? public String address; ? ? public Student() { } ? public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } ? ? public Student(String name, int age, String gender, String address) { this.name = name; this.age = age; this.gender = gender; this.address = address; } ? /** * 獲取 * @return name */ public String getName() { return name; } ? /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } ? /** * 獲取 * @return age */ public int getAge() { return age; } ? /** * 設(shè)置 * @param age */ public void setAge(int age) { this.age = age; } ? /** * 獲取 * @return gender */ public String getGender() { return gender; } ? /** * 設(shè)置 * @param gender */ public void setGender(String gender) { this.gender = gender; } ? /** * 獲取 * @return address */ public String getAddress() { return address; } ? /** * 設(shè)置 * @param address */ public void setAddress(String address) { this.address = address; } ? public String toString() { return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}"; } }
1.8 獲取成員變量并獲取值和修改值
方法 | 說明 |
---|---|
void set(Object obj, Object value) | 賦值 |
Object get(Object obj) | 獲取值 |
代碼示例:
public class ReflectDemo5 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Student s = new Student("zhangsan",23,"廣州"); Student ss = new Student("lisi",24,"北京"); ? //需求: //利用反射獲取成員變量并獲取值和修改值 ? //1.獲取class對象 Class clazz = Class.forName("com.itheima.reflectdemo.Student"); ? //2.獲取name成員變量 //field就表示name這個(gè)屬性的對象 Field field = clazz.getDeclaredField("name"); //臨時(shí)修飾他的訪問權(quán)限 field.setAccessible(true); ? //3.設(shè)置(修改)name的值 //參數(shù)一:表示要修改哪個(gè)對象的name? //參數(shù)二:表示要修改為多少? field.set(s,"wangwu"); ? //3.獲取name的值 //表示我要獲取這個(gè)對象的name的值 String result = (String)field.get(s); ? //4.打印結(jié)果 System.out.println(result); ? System.out.println(s); System.out.println(ss); ? } } ? ? public class Student { private String name; private int age; public String gender; public String address; ? ? public Student() { } ? public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } ? ? public Student(String name, int age, String gender, String address) { this.name = name; this.age = age; this.gender = gender; this.address = address; } ? /** * 獲取 * @return name */ public String getName() { return name; } ? /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } ? /** * 獲取 * @return age */ public int getAge() { return age; } ? /** * 設(shè)置 * @param age */ public void setAge(int age) { this.age = age; } ? /** * 獲取 * @return gender */ public String getGender() { return gender; } ? /** * 設(shè)置 * @param gender */ public void setGender(String gender) { this.gender = gender; } ? /** * 獲取 * @return address */ public String getAddress() { return address; } ? /** * 設(shè)置 * @param address */ public void setAddress(String address) { this.address = address; } ? public String toString() { return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}"; } }
1.9 獲取成員方法
規(guī)則:
- get表示獲取
- Declared表示私有
- 最后的s表示所有,復(fù)數(shù)形式
- 如果當(dāng)前獲取到的是私有的,必須要臨時(shí)修改訪問權(quán)限,否則無法使用
方法名 | 說明 |
---|---|
Method[] getMethods() | 返回所有成員方法對象的數(shù)組(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成員方法對象的數(shù)組,存在就能拿到 |
Method getMethod(String name, Class<?>... parameterTypes) | 返回單個(gè)成員方法對象(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回單個(gè)成員方法對象,存在就能拿到 |
代碼示例:
public class ReflectDemo6 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { //1.獲取class對象 Class<?> clazz = Class.forName("com.itheima.reflectdemo.Student"); ? ? //2.獲取方法 //getMethods可以獲取父類中public修飾的方法 Method[] methods1 = clazz.getMethods(); for (Method method : methods1) { System.out.println(method); } ? System.out.println("==========================="); //獲取所有的方法(包含私有) //但是只能獲取自己類中的方法 Method[] methods2 = clazz.getDeclaredMethods(); for (Method method : methods2) { System.out.println(method); } ? System.out.println("==========================="); //獲取指定的方法(空參) Method method3 = clazz.getMethod("sleep"); System.out.println(method3); ? Method method4 = clazz.getMethod("eat",String.class); System.out.println(method4); ? //獲取指定的私有方法 Method method5 = clazz.getDeclaredMethod("playGame"); System.out.println(method5); } }
1.10 獲取成員方法并運(yùn)行
方法
- Object invoke(Object obj, Object... args) :運(yùn)行方法
- 參數(shù)一:用obj對象調(diào)用該方法
- 參數(shù)二:調(diào)用方法的傳遞的參數(shù)(如果沒有就不寫)
- 返回值:方法的返回值(如果沒有就不寫)
代碼示例:
package com.itheima.a02reflectdemo1; ? import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; ? public class ReflectDemo6 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { //1.獲取字節(jié)碼文件對象 Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student"); //2.獲取一個(gè)對象 //需要用這個(gè)對象去調(diào)用方法 Student s = new Student(); //3.獲取一個(gè)指定的方法 //參數(shù)一:方法名 //參數(shù)二:參數(shù)列表,如果沒有可以不寫 Method eatMethod = clazz.getMethod("eat",String.class); //運(yùn)行 //參數(shù)一:表示方法的調(diào)用對象 //參數(shù)二:方法在運(yùn)行時(shí)需要的實(shí)際參數(shù) //注意點(diǎn):如果方法有返回值,那么需要接收invoke的結(jié)果 //如果方法沒有返回值,則不需要接收 String result = (String) eatMethod.invoke(s, "重慶小面"); System.out.println(result); ? } } ? ? ? public class Student { private String name; private int age; public String gender; public String address; ? ? public Student() { ? } ? public Student(String name) { this.name = name; } ? private Student(String name, int age) { this.name = name; this.age = age; } ? /** * 獲取 * @return name */ public String getName() { return name; } ? /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } ? /** * 獲取 * @return age */ public int getAge() { return age; } ? /** * 設(shè)置 * @param age */ public void setAge(int age) { this.age = age; } ? public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } ? private void study(){ System.out.println("學(xué)生在學(xué)習(xí)"); } ? private void sleep(){ System.out.println("學(xué)生在睡覺"); } ? public String eat(String something){ System.out.println("學(xué)生在吃" + something); return "學(xué)生已經(jīng)吃完了,非常happy"; } }
面試題:
你覺得反射好不好?好,有兩個(gè)方向
- 第一個(gè)方向:無視修飾符訪問類中的內(nèi)容。但是這種操作在開發(fā)中一般不用,都是框架底層來用的。
- 第二個(gè)方向:反射可以跟配置文件結(jié)合起來使用,動態(tài)的創(chuàng)建對象,動態(tài)的調(diào)用方法。
1.11 練習(xí)泛型擦除(掌握概念,了解代碼)
理解:(掌握)
集合中的泛型只在java文件中存在,當(dāng)編譯成class文件之后,就沒有泛型了。
代碼示例:(了解)
package com.itheima.reflectdemo; ? import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; ? public class ReflectDemo8 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { //1.創(chuàng)建集合對象 ArrayList<Integer> list = new ArrayList<>(); list.add(123); // list.add("aaa"); ? //2.利用反射運(yùn)行add方法去添加字符串 //因?yàn)榉瓷涫褂玫氖莄lass字節(jié)碼文件 ? //獲取class對象 Class clazz = list.getClass(); ? //獲取add方法對象 Method method = clazz.getMethod("add", Object.class); ? //運(yùn)行方法 method.invoke(list,"aaa"); ? //打印集合 System.out.println(list); } }
1.12 練習(xí):修改字符串的內(nèi)容(掌握概念,了解代碼)
在這個(gè)練習(xí)中,我需要你掌握的是字符串不能修改的真正原因。
字符串,在底層是一個(gè)byte類型的字節(jié)數(shù)組,名字叫做value
private final byte[] value;
真正不能被修改的原因:final和private
- final修飾value表示value記錄的地址值不能修改。
- private修飾value而且沒有對外提供getvalue和setvalue的方法。所以,在外界不能獲取或修改value記錄的地址值。
如果要強(qiáng)行修改可以用反射:
代碼示例:(了解)
String s = "abc"; String ss = "abc"; // private final byte[] value= {97,98,99}; // 沒有對外提供getvalue和setvalue的方法,不能修改value記錄的地址值 // 如果我們利用反射獲取了value的地址值。 // 也是可以修改的,final修飾的value // 真正不可變的value數(shù)組的地址值,里面的內(nèi)容利用反射還是可以修改的,比較危險(xiǎn) ? //1.獲取class對象 Class clazz = s.getClass(); ? //2.獲取value成員變量(private) Field field = clazz.getDeclaredField("value"); //但是這種操作非常危險(xiǎn) //JDK高版本已經(jīng)屏蔽了這種操作,低版本還是可以的 //臨時(shí)修改權(quán)限 field.setAccessible(true); ? //3.獲取value記錄的地址值 byte[] bytes = (byte[]) field.get(s); bytes[0] = 100; ? System.out.println(s);//dbc System.out.println(ss);//dbc
1.13 練習(xí),反射和配置文件結(jié)合動態(tài)獲取的練習(xí)(重點(diǎn))
需求: 利用反射根據(jù)文件中的不同類名和方法名,創(chuàng)建不同的對象并調(diào)用方法。
分析:
- ①通過Properties加載配置文件
- ②得到類名和方法名
- ③通過類名反射得到Class對象
- ④通過Class對象創(chuàng)建一個(gè)對象
- ⑤通過Class對象得到方法
- ⑥調(diào)用方法
代碼示例:
public class ReflectDemo9 { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //1.讀取配置文件的信息 Properties prop = new Properties(); FileInputStream fis = new FileInputStream("day14-code\\prop.properties"); prop.load(fis); fis.close(); System.out.println(prop); ? String classname = prop.get("classname") + ""; String methodname = prop.get("methodname") + ""; ? //2.獲取字節(jié)碼文件對象 Class clazz = Class.forName(classname); ? //3.要先創(chuàng)建這個(gè)類的對象 Constructor con = clazz.getDeclaredConstructor(); con.setAccessible(true); Object o = con.newInstance(); System.out.println(o); ? //4.獲取方法的對象 Method method = clazz.getDeclaredMethod(methodname); method.setAccessible(true); ? //5.運(yùn)行方法 method.invoke(o); ? ? } } ? 配置文件中的信息: classname=com.itheima.a02reflectdemo1.Student methodname=sleep
1.14 利用反射保存對象中的信息(重點(diǎn))
public class MyReflectDemo { public static void main(String[] args) throws IllegalAccessException, IOException { /* 對于任意一個(gè)對象,都可以把對象所有的字段名和值,保存到文件中去 */ Student s = new Student("小A",23,'女',167.5,"睡覺"); Teacher t = new Teacher("播妞",10000); saveObject(s); } ? //把對象里面所有的成員變量名和值保存到本地文件中 public static void saveObject(Object obj) throws IllegalAccessException, IOException { //1.獲取字節(jié)碼文件的對象 Class clazz = obj.getClass(); //2. 創(chuàng)建IO流 BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\\a.txt")); //3. 獲取所有的成員變量 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); //獲取成員變量的名字 String name = field.getName(); //獲取成員變量的值 Object value = field.get(obj); //寫出數(shù)據(jù) bw.write(name + "=" + value); bw.newLine(); } ? bw.close(); ? } } public class Student { private String name; private int age; private char gender; private double height; private String hobby; ? public Student() { } ? public Student(String name, int age, char gender, double height, String hobby) { this.name = name; this.age = age; this.gender = gender; this.height = height; this.hobby = hobby; } ? /** * 獲取 * @return name */ public String getName() { return name; } ? /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } ? /** * 獲取 * @return age */ public int getAge() { return age; } ? /** * 設(shè)置 * @param age */ public void setAge(int age) { this.age = age; } ? /** * 獲取 * @return gender */ public char getGender() { return gender; } ? /** * 設(shè)置 * @param gender */ public void setGender(char gender) { this.gender = gender; } ? /** * 獲取 * @return height */ public double getHeight() { return height; } ? /** * 設(shè)置 * @param height */ public void setHeight(double height) { this.height = height; } ? /** * 獲取 * @return hobby */ public String getHobby() { return hobby; } ? /** * 設(shè)置 * @param hobby */ public void setHobby(String hobby) { this.hobby = hobby; } ? public String toString() { return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}"; } } public class Teacher { private String name; private double salary; ? public Teacher() { } ? public Teacher(String name, double salary) { this.name = name; this.salary = salary; } ? /** * 獲取 * @return name */ public String getName() { return name; } ? /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } ? /** * 獲取 * @return salary */ public double getSalary() { return salary; } ? /** * 設(shè)置 * @param salary */ public void setSalary(double salary) { this.salary = salary; } ? public String toString() { return "Teacher{name = " + name + ", salary = " + salary + "}"; } } ?
2. 動態(tài)代理
2.1 好處
無侵入式的給方法增強(qiáng)功能
2.2 動態(tài)代理三要素
- 1,真正干活的對象
- 2,代理對象
- 3,利用代理調(diào)用方法
切記一點(diǎn):代理可以增強(qiáng)或者攔截的方法都在接口中,接口需要寫在newProxyInstance的第二個(gè)參數(shù)里。
2.3 代碼實(shí)現(xiàn)
public class Test { public static void main(String[] args) { /* 需求: 外面的人想要大明星唱一首歌 1. 獲取代理的對象 代理對象 = ProxyUtil.createProxy(大明星的對象); 2. 再調(diào)用代理的唱歌方法 代理對象.唱歌的方法("只因你太美"); */ //1. 獲取代理的對象 BigStar bigStar = new BigStar("雞哥"); Star proxy = ProxyUtil.createProxy(bigStar); ? //2. 調(diào)用唱歌的方法 String result = proxy.sing("只因你太美"); System.out.println(result); } } /* * * 類的作用: * 創(chuàng)建一個(gè)代理 * * */ public class ProxyUtil { /* * * 方法的作用: * 給一個(gè)明星的對象,創(chuàng)建一個(gè)代理 * * 形參: * 被代理的明星對象 * * 返回值: * 給明星創(chuàng)建的代理 * * * * 需求: * 外面的人想要大明星唱一首歌 * 1. 獲取代理的對象 * 代理對象 = ProxyUtil.createProxy(大明星的對象); * 2. 再調(diào)用代理的唱歌方法 * 代理對象.唱歌的方法("只因你太美"); * */ public static Star createProxy(BigStar bigStar){ /* java.lang.reflect.Proxy類:提供了為對象產(chǎn)生代理對象的方法: ? public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 參數(shù)一:用于指定用哪個(gè)類加載器,去加載生成的代理類 參數(shù)二:指定接口,這些接口用于指定生成的代理長什么,也就是有哪些方法 參數(shù)三:用來指定生成的代理對象要干什么事情*/ Star star = (Star) Proxy.newProxyInstance( ProxyUtil.class.getClassLoader(),//參數(shù)一:用于指定用哪個(gè)類加載器,去加載生成的代理類 new Class[]{Star.class},//參數(shù)二:指定接口,這些接口用于指定生成的代理長什么,也就是有哪些方法 //參數(shù)三:用來指定生成的代理對象要干什么事情 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /* * 參數(shù)一:代理的對象 * 參數(shù)二:要運(yùn)行的方法 sing * 參數(shù)三:調(diào)用sing方法時(shí),傳遞的實(shí)參 * */ if("sing".equals(method.getName())){ System.out.println("準(zhǔn)備話筒,收錢"); }else if("dance".equals(method.getName())){ System.out.println("準(zhǔn)備場地,收錢"); } //去找大明星開始唱歌或者跳舞 //代碼的表現(xiàn)形式:調(diào)用大明星里面唱歌或者跳舞的方法 return method.invoke(bigStar,args); } } ); return star; } } public interface Star { //我們可以把所有想要被代理的方法定義在接口當(dāng)中 //唱歌 public abstract String sing(String name); //跳舞 public abstract void dance(); } public class BigStar implements Star { private String name; ? ? public BigStar() { } ? public BigStar(String name) { this.name = name; } ? //唱歌 @Override public String sing(String name){ System.out.println(this.name + "正在唱" + name); return "謝謝"; } ? //跳舞 @Override public void dance(){ System.out.println(this.name + "正在跳舞"); } ? /** * 獲取 * @return name */ public String getName() { return name; } ? /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } ? public String toString() { return "BigStar{name = " + name + "}"; } } ?
2.4 額外擴(kuò)展
動態(tài)代理,還可以攔截方法(重點(diǎn))
比如:
- 在這個(gè)故事中,經(jīng)濟(jì)人作為代理,如果別人讓邀請大明星去唱歌,打籃球,經(jīng)紀(jì)人就增強(qiáng)功能。
- 但是如果別人讓大明星去掃廁所,經(jīng)紀(jì)人就要攔截,不會去調(diào)用大明星的方法。
/* * 類的作用: * 創(chuàng)建一個(gè)代理 * */ public class ProxyUtil { public static Star createProxy(BigStar bigStar){ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) Star star = (Star) Proxy.newProxyInstance( ProxyUtil.class.getClassLoader(), new Class[]{Star.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("cleanWC".equals(method.getName())){ System.out.println("攔截,不調(diào)用大明星的方法"); return null; } //如果是其他方法,正常執(zhí)行 return method.invoke(bigStar,args); } } ); return star; } }
2.5 動態(tài)代理的練習(xí)
對add方法進(jìn)行增強(qiáng),對remove方法進(jìn)行攔截,對其他方法不攔截也不增強(qiáng)
public class MyProxyDemo1 { public static void main(String[] args) { //動態(tài)代碼可以增強(qiáng)也可以攔截 //1.創(chuàng)建真正干活的人 ArrayList<String> list = new ArrayList<>(); ? //2.創(chuàng)建代理對象 //參數(shù)一:類加載器。當(dāng)前類名.class.getClassLoader() // 找到是誰,把當(dāng)前的類,加載到內(nèi)存中了,我再麻煩他幫我干一件事情,把后面的代理類,也加載到內(nèi)存 ? //參數(shù)二:是一個(gè)數(shù)組,在數(shù)組里面寫接口的字節(jié)碼文件對象。 // 如果寫了List,那么表示代理,可以代理List接口里面所有的方法,對這些方法可以增強(qiáng)或者攔截 // 但是,一定要寫ArrayList真實(shí)實(shí)現(xiàn)的接口 // 假設(shè)在第二個(gè)參數(shù)中,寫了MyInter接口,那么是錯(cuò)誤的。 // 因?yàn)锳rrayList并沒有實(shí)現(xiàn)這個(gè)接口,那么就無法對這個(gè)接口里面的方法,進(jìn)行增強(qiáng)或攔截 //參數(shù)三:用來創(chuàng)建代理對象的匿名內(nèi)部類 List proxyList = (List) Proxy.newProxyInstance( //參數(shù)一:類加載器 MyProxyDemo1.class.getClassLoader(), //參數(shù)二:是一個(gè)數(shù)組,表示代理對象能代理的方法范圍 new Class[]{List.class}, //參數(shù)三:本質(zhì)就是代理對象 new InvocationHandler() { @Override //invoke方法參數(shù)的意義 //參數(shù)一:表示代理對象,一般不用(了解) //參數(shù)二:就是方法名,我們可以對方法名進(jìn)行判斷,是增強(qiáng)還是攔截 //參數(shù)三:就是下面第三步調(diào)用方法時(shí),傳遞的參數(shù)。 //舉例1: //list.add("阿瑋好帥"); //此時(shí)參數(shù)二就是add這個(gè)方法名 //此時(shí)參數(shù)三 args[0] 就是 阿瑋好帥 //舉例2: //list.set(1, "aaa"); //此時(shí)參數(shù)二就是set這個(gè)方法名 //此時(shí)參數(shù)三 args[0] 就是 1 args[1]"aaa" public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //對add方法做一個(gè)增強(qiáng),統(tǒng)計(jì)耗時(shí)時(shí)間 if (method.getName().equals("add")) { long start = System.currentTimeMillis(); //調(diào)用集合的方法,真正的添加數(shù)據(jù) method.invoke(list, args); long end = System.currentTimeMillis(); System.out.println("耗時(shí)時(shí)間:" + (end - start)); //需要進(jìn)行返回,返回值要跟真正增強(qiáng)或者攔截的方法保持一致 return true; }else if(method.getName().equals("remove") && args[0] instanceof Integer){ System.out.println("攔截了按照索引刪除的方法"); return null; }else if(method.getName().equals("remove")){ System.out.println("攔截了按照對象刪除的方法"); return false; }else{ //如果當(dāng)前調(diào)用的是其他方法,我們既不增強(qiáng),也不攔截 method.invoke(list,args); return null; } } } ); ? //3.調(diào)用方法 //如果調(diào)用者是list,就好比繞過了第二步的代碼,直接添加元素 //如果調(diào)用者是代理對象,此時(shí)代理才能幫我們增強(qiáng)或者攔截 ? //每次調(diào)用方法的時(shí)候,都不會直接操作集合 //而是先調(diào)用代理里面的invoke,在invoke方法中進(jìn)行判斷,可以增強(qiáng)或者攔截 proxyList.add("aaa"); proxyList.add("bbb"); proxyList.add("ccc"); proxyList.add("ddd"); ? proxyList.remove(0); proxyList.remove("aaa"); ? ? //打印集合 System.out.println(list); } }
總結(jié)
什么是反射?
- 1.反射就是就是一種通過獲取類的字節(jié)碼文件對象的方式進(jìn)而獲取類的構(gòu)造函數(shù),屬性,方法的一種機(jī)制
- 2.優(yōu)點(diǎn)就是可以幫助我們簡化一些操作,例如簡化配置文件,缺點(diǎn)就是我們原本設(shè)置的私有屬性通過反射也能獲取,不安全
什么是動態(tài)代理?
- 一種無侵入式的給方法增強(qiáng)功能的機(jī)制
動態(tài)代理三要素?
- 1.真正干活的對象
- 2.代理對象
- 3.利用代理調(diào)用方法
代理對象和真正干活的對象同方法名,使用代理技術(shù)后,當(dāng)調(diào)用原方法時(shí)不再調(diào)用真正干活的對象,而是去調(diào)用代理對象,代理對象底層會把真正干活的對象和自己的功能進(jìn)行拼接,然后再去執(zhí)行。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java虛擬機(jī)調(diào)用Java主類的main()方法
這篇文章主要介紹了Java虛擬機(jī)調(diào)用Java主類的main()方法,前一篇文章我們介紹了關(guān)于Java虛擬機(jī)HotSpot2021-11-11SpringBoot項(xiàng)目中集成Apollo的方法步驟
本文主要介紹了SpringBoot項(xiàng)目中集成Apollo的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-10-10spring cloud gateway中如何讀取請求參數(shù)
這篇文章主要介紹了spring cloud gateway中如何讀取請求參數(shù)的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java 互相關(guān)聯(lián)的實(shí)體無限遞歸問題的解決
這篇文章主要介紹了Java 互相關(guān)聯(lián)的實(shí)體無限遞歸問題的解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10fastjson全局日期序列化設(shè)置導(dǎo)致JSONField失效問題解決方案
這篇文章主要介紹了fastjson通過代碼指定全局序列化返回時(shí)間格式,導(dǎo)致使用JSONField注解標(biāo)注屬性的特殊日期返回格式失效問題的解決方案2023-01-01