JAVA反射机制
概述
java.lang.Class:是反射的源头。
过程:源文件经过编译(javac.exe)以后,得到一个或多个.class文件。.class文件经过运行(java.exe)这步,就需要进行类的加载(通过JVM的类的加载器),加载到内存中的缓存。每一个放入缓存中的.class文件就是一个Class的实例!
1. 每一个运行时类只加载一次!
2. Class本身也是一个类
3. Class对象只能由系统建立对象
4. 一个类在JVM中只会有一个Class实例
5. 一个Class对象对应的是一个加载到JVM中的一个.class文件
6. Class类的一个对象,对应着一个运行时类。相当于一个运行时类本身充当了Class的一个实例。
7. 通过Class对象可以完整地得到一个类中的完整结构
8. 有了Class的实例以后,我们才可以进行如下的操作:
① 创建对应的运行时类的对象
② 获取对应的运行时类的完整结构(属性、方法、构造器、内部类、父类、所在的包、异常、注解、...)
③ 调用对应的运行时类的指定的结构(属性、方法、构造器)
④ 反射的应用:动态代理
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
通过反射调用类的完整结构的方法
-
static Class<?> forName(String className)
返回与带有给定字符串名的类或接口相关联的 Class 对象。 -
T newInstance()
- 创建此 Class 对象所表示的类的一个新实例。 (即 创建对应的运行时类的对象)
- 使用newInstance(),实际上就是调用了运行时类的空参的构造器。
- 要想能够创建成功:
①要求对应的运行时类要有空参的构造器。
②构造器的权限修饰符的权限要足够。 -
Class<?> getClass(): 返回此 Object 的运行时类。
-
String getName()
以String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 -
ClassLoader getClassLoader(): 返回该类的类加载器。
用于获取类属性
获取运行时类中及其父类中声明为public的属性
- Field[] getFields()
返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
获取运行时类本身声明的所有的属性
- Field[] getDeclaredFields()
返回一个Field对象数组,反映由这个class对象表示的类或接口声明的所有字段。
用于获取属性的权限修饰符
- int getModifiers()
以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符。
(应该使用 Modifier 类对这些修饰符进行解码。 ) - static String toString(int mod) 【Modifier类的方法】
返回描述指定修饰符中的访问修饰符标志的字符串。
用于获取属性的类型
- Class<?> getType() : 返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
用于获取属性名
- String getName() :返回此 Field 对象表示的字段的名称。
用于获取类方法
用于获取运行时类及其所有父类中所有的声明为public的方法
- Method[] getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口,(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
用于获取运行时类本身声明的所有的方法
- Method[] getDeclaredMethods()
返回一个包含Method对象的数组,该数组反映由这个class对象表示的类或接口的所有声明方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
用于获取注解
- Annotation[] getAnnotations():返回此元素上存在的所有注解。
用于获取方法的权限修饰符
-
int getModifiers()
以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符。
(应该使用 Modifier 类对这些修饰符进行解码。 ) -
static String toString(int mod) — 【Modifier类的方法】
返回描述指定修饰符中的访问修饰符标志的字符串。 -
Class<?> getReturnType()
返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。
用于获取方法名
- String getName()
以 String 形式返回此 Method 对象表示的方法名称。
用于获取方法的参数列表
- Class<?>[] getParameterTypes()
按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。
用于获取方法的异常类型
- Class<?>[] getExceptionTypes()
返回 Class 对象的数组,这些对象描述了声明将此 Method 对象表示的底层方法抛出的异常类型。
用于获取类构造器
用于获取类的所有公共构造器
- Constructor<?>[] getConstructors()
返回一个包含构造函数对象的数组,该对象反映由这个Class对象表示的类的所有公共构造函数。用于获取类声明的所有构造器
- Constructor<?>[] getDeclaredConstructors()
返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。
用于获取类的Other
用于获取运行时类的父类
- Class<? super T> getSuperclass()
返回表示此 Class类的对象 所表示的实体(类、接口、基本类型或 void)的父类的 Class类的对象。
用于获取带泛型的父类
- Type getGenericSuperclass()
返回表示此 Class类对象 所表示的实体(类、接口、基本类型或 void)的父类的 Type。
用于获取父类的泛型
- Type[] getActualTypeArguments(): 返回表示此类型实际类型参数的 Type 对象的数组。
用于获取实现类的接口
- Class<?>[] getInterfaces(): 确定此对象所表示的类实现的接口 或 接口继承的接口。
用于获取所在的包
- Package getPackage(): 获取Class类对象所表示的实体所在的包。
用于获取注解
- Annotation[] getAnnotations(): 返回出现在此元素上的注释。
实例化Class类对象(三种方法)
package com.tong.java;
import org.junit.Test;
/**
* @author tong
* @create 2022/6/23-14:46
*/
public class TestRecord {
//+++++++++++++++ 如何获取Class的实例(3种)++++++++++++++++++++++++
@Test
public void getClassInstance() throws ClassNotFoundException {
// 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
// 1.调用运行时类本身的.class属性
Class clazz1 = Person.class;
/* String getName()
以String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 */
System.out.println(clazz1.getName());// 运行结果:com.tong.java.Person
Class clazz2 = Integer.class;
System.out.println(clazz2.getName());// 运行结果:java.lang.Integer
//前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
// 2.通过运行时类的对象获取
Person p = new Person();
/* Class<?> getClass()
* 返回此对象的运行时类。 */
Class clazz3 = p.getClass();
System.out.println(clazz3.getName());//运行结果:com.tong.java.Person
//前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException.
// 3.通过Class的静态方法获取指定类的运行时类对象. 通过此方式,体会一下,反射的动态性。
String className = "com.tong.java.Person";
/* static Class<?> forName(String className)
返回与带有给定字符串名的类或接口相关联的 Class 对象。 */
Class clazz4 = Class.forName(className);
System.out.println(clazz4.getName());//运行结果:com.tong.java.Person
}
}
通过反射调用类中的指定构造器、指定方法、指定属性
1. 调用运行时类中指定的构造器
package com.test.java;
import java.lang.reflect.Constructor;
/**
* @author tong
* @create 2022/6/23-20:59
*/
public class TestConstructors {
/*+++++++++++++++++++++++++调用运行时类中指定的构造器+++++++++++++++++++++++++++*/
public static void main(String[] args) throws Exception {
//============== 1. 创建Class类的实例clazz,其对应的是运行时类 =============================
Friend friend = new Friend();
Class clazz = friend.getClass();
//============== 2. 通过反射调用运行时类的指定的构造器,创建运行时类的对象 =====================
// 【-------该构造器是用public修饰的-----------】
/* Constructor<T> getConstructor(Class<?>... parameterTypes)
* 返回一个构造函数对象,该对象反映由这个class对象表示的类的指定公共构造函数。*/
Constructor constructor1 = clazz.getConstructor(int.class, String.class, String.class);
/* T newInstance(Object... initargs)
* 使用此constructor对象表示的构造函数,使用指定的初始化参数创建并初始化构造函数声明类的新实例。
* 返回值:通过调用该对象表示的构造函数创建的新对象 */
Friend friend1 = (Friend) constructor1.newInstance(18, "赵丽颖", "13965327756");
System.out.println(friend1);//结果:Friend{age=18, name='赵丽颖', phone='13965327756', id=0, test='null'}
// 【-------该构造器是用private修饰的-----------】
/* Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
* 返回一个构造函数对象,该对象反映由这个class对象表示的类或接口的指定构造函数。*/
Constructor constructor2 = clazz.getDeclaredConstructor(String.class, int.class);
/* void setAccessible(boolean flag) -----【用于需要传递参数的方法调用前 具体看情况使用】
将该对象的可访问标志设置为指定的布尔值。
--解释--【由于属性权限修饰符的限制,为了保证可以给属性赋值,需要在操作前使得此属性可被操作。】
throws SecurityException
将此对象的 accessible 标志设置为指示的布尔值。
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
值为 false 则指示反射的对象应该实施 Java 语言访问检查。*/
constructor2.setAccessible(true);
Friend friend2 = (Friend) constructor2.newInstance( "刘芳",20);
System.out.println(friend2);//结果:Friend{age=20, name='刘芳', phone='null', id=0, test='null'}
}
}
运行结果:
Friend{age=18, name='赵丽颖', phone='13965327756', id=0, test='null'}
Friend{age=20, name='刘芳', phone='null', id=0, test='null'}
2. 调用运行时类指定的属性
package com.test.java;
import java.lang.reflect.Field;
/**
* @author tong
* @create 2022/6/22-19:58
*/
public class TestFields {
/*+++++++++++++++++++++++++调用运行时类指定的属性+++++++++++++++++++++++++++*/
public static void main(String[] args) throws Exception {
//============== 1. 创建clazz对应的运行时类的对象 =============================
/* T newInstance() --解释--【创建对应的运行时类的对象。】
创建由这个class对象表示的类的新实例。*/
Class clazz = Class.forName("com.test.java.Friend");
Friend friend = (Friend) clazz.newInstance();
//============== 2. 通过反射获取对应的运行时类指定的属性 ==================================
/*---------------------------调用public的属性--------------------------*/
/* Field getField(String name) --解释--【获取运行时类中声明为public的,指定属性名为name的属性】
返回一个Field对象,该对象反映由这个class对象表示的类或接口的指定公共成员字段。*/
Field id = clazz.getField("id"); //该Class对象所表示的运行时类的属性id为public
/*---------------------------调用private的属性--------------------------*/
/* Field getDeclaredField(String name)
返回一个Field对象,该对象反映由这个class对象表示的类或接口的指定声明字段。 */
Field name = clazz.getDeclaredField("name"); //该Class对象所表示的运行时类的属性name为private
Field phone = clazz.getDeclaredField("phone"); //同上
Field age = clazz.getDeclaredField("age"); //同上---private
Field test = clazz.getDeclaredField("test"); //该属性test为缺省
System.out.println(test.get(friend)); // 结果:null (属性test未显式赋值)
/*---------------------------调用静态的属性--------------------------*/
Field desc = clazz.getDeclaredField("desc"); //属性desc为 public static的
/* Object get(Object obj)
返回指定对象上此 Field 表示的字段的值。*/
System.out.println(desc.get(Friend.class)); // 结果:这是一个public的静态属性!
/* void setAccessible(boolean flag) -----【属性赋值前 具体看情况使用】
将该对象的可访问标志设置为指定的布尔值。
--解释--【由于属性权限修饰符的限制,为了保证可以给属性赋值,需要在操作前使得此属性可被操作。】
声明异常:throws SecurityException
将此对象的 accessible 标志设置为指示的布尔值。
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
值为 false 则指示反射的对象应该实施 Java 语言访问检查。*/
name.setAccessible(true);
age.setAccessible(true);
phone.setAccessible(true);
//============== 3. 将运行时类的指定的属性赋值 ================================
/* void set(Object obj, Object value)
将指定对象参数obj上的field对象表示的字段value设置为指定的新值。*/
id.set(friend, 1);
test.set(friend, "测试语句");
name.set(friend, "王浩浩");
age.set(friend, 24);
phone.set(friend, "13623651254");
System.out.println(friend);
//输出结果:Friend{age=24, name='王浩浩', phone='13623651254', id=1, test='测试语句'}
}
}
运行结果:
null
这是一个public的静态属性!
Friend{age=24, name='王浩浩', phone='13623651254', id=1, test='测试语句'}
Process finished with exit code 0
3. 调用运行时类中指定的方法
package com.test.java;
/**
* @author tong
* @create 2022/6/22-21:54
*/
import java.lang.reflect.Method;
public class TestMethods {
/*+++++++++++++++++++++++++调用运行时类中指定的方法+++++++++++++++++++++++++++*/
public static void main(String[] args) throws Exception {
//============== 1. 创建clazz对应的运行时类的对象 =============================
/* T newInstance() --解释--【创建对应的运行时类的对象。】
创建由这个class对象表示的类的新实例。*/
Class clazz = Friend.class;
Friend friend = (Friend) clazz.newInstance();
//============== 2. 通过反射调用运行时类的指定的方法 ==================================
/*---------------------------调用public的属性--------------------------*/
/* Method getMethod(String name, Class<?>... parameterTypes)
* 返回一个Method对象,该对象反映由这个class对象表示的类或接口的指定公共成员方法。
* --解释--【获取运行时类中声明为public的指定的方法】*/
Method sName = clazz.getMethod("setName", String.class);//该setName()的返回值类型为:void
/*---------------------------调用private的方法--------------------------*/
/* Method getDeclaredMethod(String name, Class<?>... parameterTypes)
* 返回一个Method对象,该对象反映由这个class对象表示的类或接口的指定声明方法。
* --解释--【获取运行时类中声明了的指定的方法】*/
//该getTest()的返回值类型为:String
Method gTest = clazz.getDeclaredMethod("getTest", String.class, int.class);
/* void setAccessible(boolean flag) -----【用于需要传递参数的方法调用前 具体看情况使用】
将该对象的可访问标志设置为指定的布尔值。
--解释--【由于属性权限修饰符的限制,为了保证可以给属性赋值,需要在操作前使得此属性可被操作。】
throws SecurityException
将此对象的 accessible 标志设置为指示的布尔值。
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
值为 false 则指示反射的对象应该实施 Java 语言访问检查。*/
gTest.setAccessible(true);
/* Object invoke(Object obj, Object... args)
* 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。*/
Object val1 = sName.invoke(friend, "李沁");
Object val2 = gTest.invoke(friend, "测试语句!!!", 6);
System.out.println(val1);// 结果:null
System.out.println(val2);// 结果:测试语句!!!
/*---------------------------调用静态的方法--------------------------*/
//对于运行时类中静态方法的调用 (此处使用了私有的静态方法)
//该sMethod()的返回值类型为:String
Method sm = clazz.getDeclaredMethod("sMethod", String.class);
sm.setAccessible(true);
Object sval = sm.invoke(Friend.class, "测试静态语句!");
System.out.println(sval);// 结果:测试静态语句
System.out.println(friend);// 结果:Friend{age=0, name='李沁', phone='null', id=0, test='null'}
}
}
运行结果:
null
测试语句!!!
测试静态语句!
Friend{age=0, name='李沁', phone='null', id=0, test='null'}
Process finished with exit code 0
4. 续 (Friend类)
package com.test.java;
/**
* @author tong
* @create 2022/6/22-20:08
*/
public class Friend {
private int age;
private String name;
private String phone;
public int id;
String test;
public static String desc = "这是一个public的静态属性!";
public Friend() {
}
private Friend(String name, int age) {
this.age = age;
this.name = name;
}
public Friend(int age, String name, String phone) {
this.age = age;
this.name = name;
this.phone = phone;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Friend{" +
"age=" + age +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
", id=" + id +
", test='" + test + '\'' +
'}';
}
private String getTest(String str, int num) {
return str;
}
private static String sMethod(String str) {
return str;
}
}
通过Properties类读取信息和写入信息(通过类加载器返回用于读取指定资源的输入流)
- Properties类是properties文件和程序的中间桥梁,不论是从properties文件读取信息还是写入信息到properties文件都要经由Properties类。
- Properties类表示了一个持久的属性集。
- Properties可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
- 类加载器 (ClassLoader) 是用来把类(class)装载进内存的。
- 因为 Properties类 继承于 Hashtable类,所以可对 Properties 对象应用 put 和 putAll 方法。但不建议使用这两个方法,因为它们允许调用者插入其键或值不是 String 的项。相反,应该使用 setProperty 方法。
- Properties类是线程安全的:多个线程可以共享单个 Properties 对象而无需进行外部同步。
简介:该demo通过使用类加载器返回指定资源的输入流,然后通过Properties类实现读取配置文件、修改配置文件的操作。
package com.test.java;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author tong
* @create 2022/6/24-15:04
*/
public class TestProperties {
public static void main(String[] args) throws IOException {
/*=============================读取指定属性文件的内容=================================*/
// 获取TestProperties类的运行时类
Class clazz = TestProperties.class;
// ClassLoader getClassLoader():返回该类的类加载器。
ClassLoader loader = clazz.getClassLoader();
// InputStream getResourceAsStream(String name): 返回用于读取指定资源的输入流。
InputStream is = loader.getResourceAsStream("com\\test\\java\\jdbc.properties");
Properties p = new Properties();
p.load(is); //提示:若不读取流内的数据,直接getProperty获取为null
/* String getProperty(String key):用指定的键在此属性列表中搜索属性。
返回:属性列表中具有指定键值的值。*/
System.out.println(p.getProperty("jdbc.username"));// 结果:root
System.out.println(p.getProperty("jdbc.driver"));// 结果:com.mysql.jdbc.Driver
p.list(System.out);//把属性列表显示到控制台(jdbc.properties的内容)
/* 打印结果:-- listing properties --
jdbc.url=jdbc:mysql://localhost/mysql
jdbc.username=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.password=root123 */
/*=============================写入指定属性文件的内容=================================*/
/* Object setProperty(String key,String value)
调用 Hashtable 的方法 put。使用 getProperty 方法提供并行性。强制要求为属性的键和值使用字符串。
返回值是 Hashtable 调用 put 的结果。
返回:属性列表中指定键的旧值,如果没有值,则为 null。*/
System.out.println(p.setProperty("jdbc.driver", "jdbc.Driver"));//结果:com.mysql.jdbc.Driver
/*注意:
1. 如果jdbc.properties的key存在,就是修改该value
2. 如果jdbc.properties的key不存在,就是创建该K-V对
3. setProperty()并不会改变原文件(jdbc.properties),而是让目标文件(jdbc1.properties)发生变化
4. 通过方法setProperty可以创建或更改指定键对应的值 */
p.setProperty("jdbc.username", "newRoot");
p.setProperty("jdbc.test", "text");
/* void store(OutputStream out, String comments)
以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
形参comment:相当于一个注释(例:#--内容---),可不写(填入null) */
p.store(new FileOutputStream("jdbc1.properties"), null);//将读取到并修改后的K-V数据存储在"jdbc1.properties"文件中
/* void list(PrintStream out):将属性列表输出到指定的输出流。
void list(PrintWriter out):将属性列表输出到指定的输出流。*/
p.list(System.out);//把属性列表显示到控制台(jdbc1.properties的内容)
/* 打印结果:-- listing properties --
jdbc.url=jdbc:mysql://localhost/mysql
jdbc.test=text
jdbc.username=newRoot
jdbc.driver=jdbc.Driver
jdbc.password=root123 */
}
}
jdbc.properties
#Fri Jun 24 17:04:45 CST 2022
jdbc.url=jdbc\:mysql\://localhost/mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root123
jdbc1.properties
#Fri Jun 24 17:59:52 CST 2022
jdbc.url=jdbc\:mysql\://localhost/mysql
jdbc.test=text
jdbc.username=newRoot
jdbc.driver=jdbc.Driver
jdbc.password=root123
补充:
代理模式
- 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
- 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理设计模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理: 要求被代理类和被代理类都实现相同的一套接口;当通过代理类的对象调用重写接口的方法时,实际上执行的是被代理类重写的同样方法的调用。
动态代理: 在程序运行时,根据被代理类及其实现的接口,动态的创建一个代理类的对象。当通过代理类的对象调用实现的接口的抽象方法时,就会发起对被代理类重写接口的同样方法的调用。
静态代理
package com.test.java;
/**
* @author tong
* @create 2022/6/24-19:57
* <p>
* 静态代理 demo演示
*/
/*静态代理的特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。
静态代理类需要实现 被代理类的接口,并且重写接口方法。当代理多个接口时,容易造成代码冗余。
最好可以通过一个代理类动态完成全部的代理功能---【即动态代理】
*/
public class TestStaticProxy {
public static void main(String[] args) {
Consignor1 cs1 = new Consignor1();//创建被代理类1的对象
Consignor2 cs2 = new Consignor2();//创建被代理类2的对象
Consignor3 cs3 = new Consignor3();//创建被代理类3的对象
MyProxy proxy1 = new MyProxy(cs1);//创建代理类的对象
MyProxy proxy2 = new MyProxy(cs2);//创建代理类的对象
MyProxy proxy3 = new MyProxy(cs3);//创建代理类的对象
proxy1.example();
proxy2.example();
proxy3.newExample();
}
}
//接口
interface Case {
void example();
}
interface NewCase {
void newExample();
}
//被代理类1
class Consignor1 implements Case {
@Override
public void example() {
System.out.println("被代理类1开始执行!");
}
}
//被代理类2
class Consignor2 implements Case {
@Override
public void example() {
System.out.println("被代理类2开始执行!");
}
}
//被代理类3
class Consignor3 implements NewCase {
@Override
public void newExample() {
System.out.println("被代理类3开始执行!");
}
}
//代理类
class MyProxy implements Case, NewCase {
Case aCase;//相当于Case aCase = new Consignor1() ... new Consignor2();
NewCase newCase;//相当于NewCase newCase = new Consignor3();
//创建代理类的对象时,实际传入一个被代理类的对象
public MyProxy(Case aCase) {
this.aCase = aCase;
}
public MyProxy(NewCase nc) {
this.newCase = nc;
}
@Override
public void example() {
System.out.println("案件完成验证操作");
System.out.println("代理类开始执行!");
aCase.example();//接口的多态性:实际执行的是接口实现类的重写的方法
}
@Override
public void newExample() {
System.out.println("新案件审核完毕");
System.out.println("代理类开始执行!!!");
newCase.newExample();
}
}
运行结果:
案件完成验证操作
代理类开始执行!
被代理类1开始执行!
案件完成验证操作
代理类开始执行!
被代理类2开始执行!
新案件审核完毕
代理类开始执行!!!
被代理类3开始执行!
动态代理 (AOP)
注意:
-
interface InvocationHandler 是代理实例的调用处理程序 实现的接口
-
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它关联的调用处理程序的 invoke 方法。
-
Proxy类 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
-
代理类是公共的、最终的,而不是抽象的。
-
动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为。
-
代理接口 是代理类实现的一个接口。
-
代理实例 是代理类的一个实例。
-
每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,并传递代理实例、识别调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。
涉及到的方法:
- Object invoke(Object proxy, Method method, Object[] args)
在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
注意:此处invoke方法是接口InvocationHandler里的
参数:
proxy - 在其上调用方法的代理实例 method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。 args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
返回:
从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出ClassCastException。
- static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
- Object invoke(Object obj, Object… args)
在具有指定参数的指定对象上调用此“方法”对象表示的基础方法。
注意:此处invoke方法是Method类里的
形参:
obj - 调用基础方法的对象
args - 用于方法调用的参数
返回值:
在obj上调度该对象所表示的带有参数args的方法的结果
- Class<?>[] getInterfaces():确定由此对象表示的类或接口实现的接口。
- ClassLoader getClassLoader()
返回该类的类加载器。一些实现可能使用null来表示引导类加载器。如果这个类是由引导类加载器加载的,这个方法将在这样的实现中返回null
package com.test.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author tong
* @create 2022/6/24-22:54
*
* 动态代理 demo演示
*/
public class TestAOP {
public static void main(String[] args) {
//创建被代理类的对象
Student student1 = new Student("李耀");
Student student2 = new Student("罗峰");
Student student3 = new Student("徐欣");
Teacher teacher1 = new Teacher("王");
Teacher teacher2 = new Teacher("洪");
DynamicProxy dp = new DynamicProxy();
//返回一个带有代理类的指定调用处理程序的代理实例(即obj),它由被代理类的类加载器定义,并实现被代理类所实现的接口
Object obj = dp.ProxyInstance(student1);//获得一个对应student1的代理类对象
School school = (School)obj;//接口的多态性:接口引用名 指向 代理类的实例
school.info(student1.getName());//通过代理类的对象调用重写的抽象方法
//同上
((School)dp.ProxyInstance(student2)).info(student2.getName());
((School)dp.ProxyInstance(student3)).info(student3.getName());
((School)dp.ProxyInstance(teacher1)).info(teacher1.getSurname());
((School)dp.ProxyInstance(teacher2)).info(teacher2.getSurname());
}
}
interface School {
void info(String name);
}
//被代理类1
class Student implements School {
//学生名字
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void info(String name) {
System.out.println(name + "同学毕业了!!!");
}
}
//被代理类2
class Teacher implements School {
//老师姓氏
private String surname;
public Teacher() {
}
public Teacher(String surname) {
this.surname = surname;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
@Override
public void info(String name) {
System.out.println(name + "老师辛苦了!");
}
}
//动态代理在切点前后可插入的方法
class General {
public void before() {
System.out.println("处理方法1");
}
public void after() {
System.out.println("处理方法2");
}
}
//当通过代理类的对象发起对被重写的方法的调用时,都会转换为对如下的invoke方法的调用
//代理实例的调用处理程序
class MyInvocationHandler implements InvocationHandler {
// 被代理类对象的声明
Object obj;//相当于Object obj = new Sthdent(name)或new Teacher(surname);
public void setObject(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
General gen = new General();
gen.before();
//切点
Object returnVal = method.invoke(obj, args);// obj相当于被代理类的对象,args为代理类对象调用对应接口方法所传入的形参
gen.after();
System.out.println();
return returnVal;
}
}
//动态代理类
class DynamicProxy {
// 动态的创建一个代理类的对象
public Object ProxyInstance(Object obj) {
MyInvocationHandler handler = new MyInvocationHandler();//创建一个代理实例的调用处理程序的对象
handler.setObject(obj);
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
运行结果:
处理方法1
李耀同学毕业了!!!
处理方法2
处理方法1
罗峰同学毕业了!!!
处理方法2
处理方法1
徐欣同学毕业了!!!
处理方法2
处理方法1
王老师辛苦了!
处理方法2
处理方法1
洪老师辛苦了!
处理方法2