Java反射机制
什么是反射?
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键(来源于百度)
JAVA 反射机制是指在运行状态中,
-
对于任意一个类,都能够知道这个类的所有属性和方法;
-
对于任意一个对象,都能够调用它的任意方法和属性;
-
在程序运行过程中,可以操作正在运行的对象
这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制
反射机制的作用
Java反射机制的作用是(百度):1)在运行时判断任意一个对象所属的类。2)在运行时构造任意一个类的对象。3)在运行时判断任意一个类所具有的成员变量和方法。4)在运行时调用任意一个对象的方法
Class 类与 java.lang.reflect 类库一起对反射的概念进行了支持,该类库包含了Field,Method,Constructor 类 (每个类都实现了 Member 接口)。这些类型的对象是由 JVM 在运行时创建的,用以表示未知类里对应的成员。
这样你就可以使用 Constructor 创建新的对象,用 get() 和 set() 方法读取和修改与 Field 对象关联的字段,用 invoke()方法调用与 Method 对象关联的方法。另外,还可以调用 getFields()、getMethods() 和getConstructors() 等便利的方法,以返回表示字段,方法,以及构造器的对象的数组。这样匿名对象的信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
通过反射查看类信息
Java 程序中许多对象在运行时都会出现两种类型:编译时类型和运行时类型
例如:Person p = new Student();
这行代码将会生成一个变量 p,该变量的编译类型为 Person,但运行时类型为 Student,除此之外,还有更极端的情形,程序在运行时接收到外部传入的一个对象,该对象的编译类型是 Object,但程序又需要调用该对象运行时类型的方法,为了解决这个问题,程序需要在运行时发现对象和类的真实信息,方法有两种:
第一种是假设在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instance of 运算符进行判断,再利用强制类型转换将其转换成运行时类型的变量即可
第二种是编译时根本无法预知该对象和类的信息,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射
获取Class对象
Java中每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过Class对象就可以访问到加载到JVM(Java虚拟机)中的这个类的所有信息,一旦获得某个类所对应的 Class对象之后,程序就可以调用 Class 对象的方法来获得该对象和该类的真实信息了
Java程序获得Class对象的三种方式:
-
Class.forName( )方法。该方法需要传入字符串参数,这个字符串参数的值是某个类的类名(必须添加完整包名)
-
调用某个类的 class 属性来获取对于的 Class 对象
-
调用某个对象(Object)的 getClass 方法。该方法将返回该对象所属类对于的 Class 对象
public class Demo1 { public static void main(String[] args) throws ClassNotFoundException { //通过forName方法获取class对象 forName需要抛出一个异常 Class aClass = Class.forName("com.gxy.java.entity.Student"); System.out.println("forName方法获取" + aClass); //通过class属性获取class对象 Class<Student> studentClass = Student.class; System.out.println("class属性获取" + studentClass); //通过某个对象的 getClass 方法获取对应的 Class 对象 Student stu = new Student(); Class class2 = stu.getClass(); System.out.println("某个对象的getClass方法获取" + class2); } }
运行结果:
forName方法获取class com.gxy.java.entity.Student
class属性获取class com.gxy.java.entity.Student
某个对象的getClass方法获取class com.gxy.java.entity.Student
从Class中获取信息
Class 类提供了大量方法,这些方法可以让我们访问 Class 对象对应类的详细信息
1. 访问 Class 对应类所包含的构造器(Constructor):
-
Constructor getConstructor(Class...parameterTypes):返回 Class 对象对应类的指定参数的public 构造器。
-
Constructor[] getConstructors():返回 Class 对象对应类的所有 public 构造器。
-
Constructor getDeclaredConstructor(Class...parameterTypes):返回 Class 对象对应类的指定参数的构造器,与构造器访问级别无关。
-
Constructor[] getDeclaredConstructors():返回 Class 对象对应类的所有构造器,与构造器访问级别无关
import java.lang.reflect.Constructor; public class Person { private Person(){ System.out.println("这是无参构造器"); } public Person(String name){ System.out.println("这是一个有参构造器"); } public Person(String name,int age){ System.out.println("这是有参构造器二号"); } public static void main(String[] args) throws NoSuchMethodException { //获取class对象 Class<Person> clazz = Person.class; //获取person类的参数类型为String的public构造器 Constructor<Person> con = clazz.getConstructor(String.class); System.out.println("person类的参数类型为String的public构造器是:\n" + con); System.out.println("-----------------------------------------"); //获取person类中所有的public构造器 Constructor[] persons = clazz.getConstructors(); //遍历出集合中的所有数据 for (Constructor c: persons) { System.out.println("person类public构造器是" + c); } System.out.println("-------------------------------------------"); //获取person类中的所有构造方法 Constructor[] cons = clazz.getDeclaredConstructors(); for (Constructor x: cons) { System.out.println("获取person类中的所有构造方法有" + x); } } }
运行结果:
person类的参数类型为String的public构造器是: public com.gxy.java.Person(java.lang.String) ----------------------------------------- person类public构造器是public com.gxy.java.Person(java.lang.String,int) person类public构造器是public com.gxy.java.Person(java.lang.String) ------------------------------------------- 获取person类中的所有构造方法有public com.gxy.java.Person(java.lang.String,int) 获取person类中的所有构造方法有public com.gxy.java.Person(java.lang.String) 获取person类中的所有构造方法有private com.gxy.java.Person()
2. 访问class对应类所包含的方法(Method):
-
Method getMethod(String name, Class... parameterTypes) :返回 Class 对应类的指定 public 方法。
-
Method[] getMethods() :返回 Class 对应类的所有 public 方法。
-
Method getDeclaredMethod(String name, Class... parameterTypes) :返回 Class 对应类的指定方法,与访问级别无关。
-
Method[] getDeclaredMethods() :返回 Class 对应类的所有方法,与访问级别无关。
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Student { private Student(){ System.out.println("这是一个无参构造器"); } public Student(String name){ System.out.println("这是一个有参构造器"); } public void info(){ System.out.println("执行无参info方法"); } public void info(String name){ System.out.println("执行有参info方法"); } public static void main(String[] args) throws NoSuchMethodException { //获取student类 Class<Student> student = Student.class; //获取类中的所有方法 Constructor<?>[] students = student.getDeclaredConstructors(); //遍历方法 for (Constructor c: students) { System.out.println("Student类中定义的方法有" + c); } //获取Class对应类指定的public方法 Method info = student.getMethod("info", String.class); System.out.println("student定义了带字符串类型参数的 info 方法:\n" + info); } }
运行结果:
Student类中定义的方法有private com.gxy.java.Student() Student类中定义的方法有public com.gxy.java.Student(java.lang.String) student定义了带字符串类型参数的 info 方法: public void com.gxy.java.Student.info(java.lang.String)
3. 访问 Class 对应类所包含的字段(Field):
➢ Filed getField(String name) :返回 Class 对应类的指定 public 属性。
➢ Filed[] getFields() :返回 Class 对应类的所有 public 属性。
➢ Field getDeclaredField(String name) :返回 Class 对应类的指定属性,与访问级别无关。
➢ Field[] getDeclaredField s() :返回 Class 对应类的所有属性,与访问级别无关
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.lang.reflect.Field; @AllArgsConstructor @NoArgsConstructor @Data public class Student { private Integer id; public String name; public static void main(String[] args) throws NoSuchFieldException { //获取class类 Class student = Student.class; //获取class对应类中的所有属性返回 与访问级别无关 Field[] fields = student.getDeclaredFields(); for (Field f: fields) { System.out.println("student的属性有 " + f); } //获取class对应类指定的public属性 Field field = student.getField("name"); System.out.println("student类中属性为public的为" + field); //获取所有属性为public的值 Field[] fields1 = student.getFields(); for (Field f1: fields1) { System.out.println("类中属性为public的有" + f1); } //返回 Class 对应类的指定属性,与访问级别无关 Field id = student.getDeclaredField("id"); System.out.println("student类中id的值为" + id); } }
4. 访问 Class 对应类所包含的注解(Annotation)
➢ <A extends Annotation> A getAnnotation(Class<A> annotationClass):试图获取该 Class 对应类指定类型的注解,如果该类型的注解不存在则返回 null。
➢ Annotation [] getAnnotations():返回此元素上存在的所有注解。
➢ Annotation [] getDeclaredAnnotations():返回直接存在于此元素上的所有注解
//声明一个简单的controller import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; //告诉编译器忽略指定的警告,不用在编译完成后出现警告信息 @SuppressWarnings("unchecked") @RestController public class HelloWordController { @RequestMapping("/sayHello") public String sayHello(){ return "欢迎来到Java反射机制的学习"; } } //test测试类 package com.gxy.java; import org.junit.Test; import java.lang.annotation.Annotation; public class Demo2_test { //来自junit包 要导入对应的依赖 @Test public void test(){ //获取到HelloWordController类 Class hello = HelloWordController.class; //获取HelloWordController类在运行时的注解 Annotation[] annotations = hello.getAnnotations(); //遍历拿出所有的注解 for (Annotation a: annotations) { System.out.println("HelloWorldController 类上存在的注解有:" + a); } //获取 Class 对应类指定类型的注解 Annotation annotation = hello.getAnnotation(HelloWordController.class); System.out.println("HelloWorldController 类 Property 类型的注解是:\n" + annotation); } } /** * 上面的程序访问 HelloController 类上存在的所有注解信息时,没有访问到@SuppressWarnings, * 因为这个注解并不是运行注解,所以无法通过反射访问 */
运行结果:
HelloWorldController 类上存在的注解有:@org.springframework.stereotype.Controller(value=) HelloWorldController 类 Property 类型的注解是: null
5. 访问 Class 对应类所包含的其他成员
➢ Class[] getDeclaredClasses():返回 Class 对应类的全部内部类。
➢ Class[] getClasses():返回 Class 对应类的全部 public 内部类。
➢ Class<?> getDeclaringClass():返回 Class 对应类的外部类。
➢ Class[] getInterfaces():返回 Class 对应类所实现的全部接口。
➢ int getModifiers():返回 Class 对应类或接口的所有修饰符。修饰符由 public、protected、private、final、static、abstract 等对应的常量组成,返回的整数应使用 Modifier 工具类的方法来解码,才可以获得真实的修饰符。
➢ Package getPackage():获取此类的包。
➢String getName():返回 Class 对应类的名称。
➢ String getSimpleName():返回 Class 对应类的简称。
➢ Class<? super T> getSuperClass():返回 Class 对应类的父类的对应 Class 对象。
使用反射生成对象并操作对象
Class 对象可以获得该类里包括的方法(Method)、构造器(Constructor)、属性(Field)等成员,这就意味着程序可以通过 Method 对象来执行对应的方法,通过 Constructor 对象来调用对应的构造器创建对象,通过 Field 对象直接访问并修改对象的属性值
import java.lang.reflect.Constructor; public class Demo3{ //异常直接抛出顶级父类 不用多次抛出 public static void main(String[] args) throws Exception { Class aClass = Class.forName("java.lang.StringBuffer"); //获取StringBuffer中带字符串参数的构造器 Constructor constructor = aClass.getConstructor(String.class); //通过构造器的newInstance方法创建对象 Object instance = constructor.newInstance("这是一个初始化字符"); System.out.println(instance); } }
1. 创建对象
反射机制生成对象有两种方式:通过第一种方式来创建对象是比较常见的情形,因为在很多 Java EE 框架中都需要根据配置文件信息来创建 Java 对象,从配置文件读取的只是某个类的字符串类名,程序通过反射来创建对应实例
➢ 使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,这种方式要求该 Class 对象的对应类有默认构造器,而执行 newInstance()方法时实际上是利用默认构造器来创建该类实例。
➢ 先使用 Class 对象获取 Constructor 对象,再调用 Constructor 对象的 newInstance()方法来创建该Class 对象对应类的实例。通过这种方式可以选择使用某个类的指定构造器来创建实例
import java.io.FileInputStream; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 这个程序根据配置文件调用 Class 对象的 newInstance 方法创建 Java 对象,并将这些对象放入对象池中然 *后根据存入池中的 name 取出对象。这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式 * 非常有用,鼎鼎大名的 Spring 框架就是采用这种方式大大简化了 JavaEE 应用的开发,当然,Spring 采用的 *是信息丰富的 XML 配置文件。 */ public class ObjectPoolFactory { //定义一个map集合 private Map<String,Object> map = new HashMap<String,Object>(); //定义一个创建对象的方法,该方法只要传入一个字符串类名,程序可以根据该类名生成 Java 对象 private Object createObject(String className) throws Exception{ //根据字符串来获取对应的class对象 Class aClass = Class.forName(className); //使用 aClass 对应类的默认构造器创建实例 //newInstance依赖于构造方法,没有构造方法不能创建成功 return aClass.newInstance(); } public void initPool(String fileName){ // FileInputStream流被称为文件字节输入流 // 意思指对文件数据以字节的形式进行读取操作如读取图片视频等 FileInputStream fis = null; try { //需要读取文件名称 fis = new FileInputStream(fileName); //新建一个Properties 用于读取Java配置文件 Properties pro = new Properties(); //从输入流中读取属性列表 pro.load(fis); for (String name:pro.stringPropertyNames()) { //创建对象并添加到map集合中 map.put(name,createObject(pro.getProperty(name))); } } catch (Exception e) { e.printStackTrace(); } finally { try { //如果当前的字节流里面存在数据 if(fis != null){ //关闭这个字节流 fis.close(); } } catch (Exception e){ e.printStackTrace(); } } } public Object getObject(String name){ //从map集合中取出对象 return map.get(name); } public static void main(String[] args) { //声明一个对象池工厂 ObjectPoolFactory pool = new ObjectPoolFactory(); //给出要读取文件的路径 pool.initPool("src\\main\\resources\\obj.txt"); //声明要读取配置文件中的属性 Object a = pool.getObject("a"); //格式化时间格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //输入所读取到的数据 System.out.println("获取当前时间为:" + sdf.format(a)); } }
配值文件:
a = java.util.Date
b = javax.swing.JFrame
运行结果:
获取当前时间为:2020-10-06 15:41:39
2. 调用方法
当获得某个类的 Class 对象后,就可以通过 Class 对象的 getMethods 方法或者 getMethod 方法来获取全部方法(Method 对象数组)或指定方法(Method 对象,获得 Method 对象后,程序就可以通过 Method对象的 invoke()方法调用目标对象的方法
下面程序对前面的对象池工厂进行加强,程序允许在配置文件增加配置对象的属性值,对象池工厂会读取该对象的属性值,并利用该对象的 setter 方法为对应属性设置值
import com.sun.org.apache.xml.internal.utils.ObjectPool; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties; //通过反射实现对 Person 类的 setName 方法的调用 public class ExtendedObjectPoolFactory { //初始化一个map集合 private Map<String,Object> map = new HashMap<String, Object>(); //从属性文件中初始化properties属性 Properties config = new Properties(); //声明一个方法 public void init(String fileName){ //初始化FileInputStream变量 FileInputStream fis = null; try { fis = new FileInputStream(fileName); config.load(fis); } catch (Exception e) { e.printStackTrace(); } finally { try { if(fis != null){ fis.close(); } } catch (Exception e){ e.printStackTrace(); } } } public Object createObject(String className) throws Exception{ //根据字符串获取对应class对象 Class<?> aClass = Class.forName(className); //使用默认构造器创建对象 return aClass.newInstance(); } //根据配置文件初始化对象池 public void initPool() throws Exception { for (String name : config.stringPropertyNames()){ //每取出一个属性名-属性值对时,如果属性名中不包含%,就根据属性值创建一个对象 if(!name.contains("%")){ map.put(name,createObject(config.getProperty(name))); } else { //将配置文件中的属性按照%分割 String[] split = name.split("%"); //取出要设置属性的目标对象 Object target = getObject(split[0]); //设置该属性的对应的setter方法名 String setName = "set" + split[1].substring(0,1).toUpperCase() +split[1].substring(1); //获取target对应的class对象 Class<?> targetClass = target.getClass(); //获取改属性对应的setter方法 Method m = targetClass.getMethod(setName, String.class); //调用 Method 对象的 invoke 方法执行 setter 方法 //invoke 的第一个参数表示目标对象,第二个参数表示调用时传入的实参 m.invoke(target,config.getProperty(name)); } } } public Object getObject(String name) { return map.get(name); } public static void main(String[] args) throws Exception { ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory(); epf.init("src\\main\\resources\\object.txt"); epf.initPool(); System.out.println("这是一个配置文件" + epf.getObject("a")); } }
//测试类 public class Demo4 { private String name; public String getName(){ return name; } public void setName(String name) { this.name = name; System.out.println("setName方法调用了:" + name); } }
配置文件:
#要写自己的测试类所在位置 a = com.gxy.java.Demo4 #set the name of a a%name = Test Name
运行结果:
setName方法调用了:Test Name
这是一个配置文件com.gxy.java.Demo4@4a574795
3. 访问属性值
通过 Class 对象的 getFields()和 getField()方法可以获得该类所包含的全部 Filed 对象数组或指定Filed 对象
Filed 提供了两种方法来访问属性:
➢ getXxx(Object obj):获取 obj 对象的属性值。此处 Xxx 对应 8 个基本类型,如果是 引用类型则取消get 后面的 Xxx。
➢ setXxx(Object obj , Xxx val):将 obj 对象的属性值设置成 val。此处 Xxx 表示 8 个基本类型,如果是引用类型则取消 set 后面的 Xxx。
import java.lang.reflect.Field; import java.lang.reflect.Method; class User { public String name; private int age; private void print(){ System.out.println("姓名:"+name+"\n年龄:"+age); } } public class Demo5 { public static void main(String[] args) throws Exception { //通过User获取对应的Class对象 Class<User> userClass = User.class; //使用反射创建User实例 User user = userClass.newInstance(); //获取user类的name Field name = userClass.getField("name"); //通过实例修改 name.set(user,"莫离欢"); //获取user类的private属性age Field age = userClass.getDeclaredField("age"); //通过反射访问该属性时取消访问权限检查 //true是关闭 false是开启,默认是false开启状态 age.setAccessible(true); //设置age属性 age.set(user,18); //获取user类的私有方法print方法 Method print = userClass.getDeclaredMethod("print"); //设置通过反射访问该属性时取消权限检查 print.setAccessible(true); //激活方法 -- 类似于打点调用 //参数:obj给哪个对象激活方法 args:这个方法需要的参数 print.invoke(user); } }
运行结果:
姓名:莫离欢
年龄:18
动态代理
1. 代理模式
代理模式是常用的 java 设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等;代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务
比如你要去银行取钱,你就是委托人,你委托银行的雇员(代理人)帮你办理取钱的业务。你和银行雇员的关联关系是:表面上是银行雇员取钱,但实际上是你在取钱,雇员只是为你提供取钱的服务
2.动态代理
在 Java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 Invocationhandler 接口。这个类和接口是实现动态代理所必须用到的
1) Invocationhandler 接口:
每一个动态代理类都必须要实现 InvocationHandler 接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler这个接口的 invoke 方法来进行调用
Object invoke(Object proxy, Method method, Object[] args)
参数解析:
➢ Object proxy:指被代理的对象
➢ Method method:要调用的方法
➢ Object[] args:方法调用时所需的实参
System.out.println("参数一:被指带的对象:" + proxy.getClass() + "\n参数二:调用的方法method" + method.getName() + "\n参数三:所传入的参数args" + args[0]); 参数一:被指带的对象:class com.sun.proxy.$Proxy0 参数二:调用的方法methodpay 参数三:所传入的参数args大房间
2) Proxy 类
Proxy 类提供了一个创建动态代理对象的方法,该方法定义如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数解析:
➢ ClassLoader loader:类加载器,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
➢ Class<?>[] interfaces:代理类要实现的全部接口。
➢ InvocationHandler h:表示代理对象要关联的 InvocationHandler 对象。
Object o = Proxy.newProxyInstance( consumer.getClass().getClassLoader(),//类加载器 ClassLoader loader consumer.getClass().getInterfaces(),//要实现的接口 Class<?>[] interfaces new InvocationHandler()//关联的 InvocationHandler 对象 { @Override public Object invoke(Object proxy, Method method, Object[] args){ return null; } } );
租房案例:
-
定义一个接口
public interface ZuFang { //付款方法 String pay(String claim); }
-
写这个接口的实现类
package com.gxy.java.dyx; public class Consumer implements ZuFang { @Override public String pay(String claim) { return "完成要求" + claim + "-----付款成功"; } }
-
写测试类
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { final Consumer consumer = new Consumer(); ZuFang o = (ZuFang) Proxy.newProxyInstance( consumer.getClass().getClassLoader(),//类加载器 consumer.getClass().getInterfaces(),//接口 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是中介,我可以帮你选房...."); Object invoke = method.invoke(consumer, args); System.out.println("房间已按照您的要求" + args[0] + "选择完毕,请付款"); return invoke; } } ); System.out.println(o.pay("大房间")); } }
本章总结
-
反射机制指的是程序在运行时能够获取自身的信息。在 java 中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
-
可以实现动态创建对象和编译,体现出很大的灵活性,特别是在 J2EE 的开发中它的灵活性就表现的十分明显,但是对性能有所影响。
-
反射机制就是专门帮我们做那些重复的有规则的事情,所以现在很多的自动生成代码的软件就是运用反射机制来完成的。
-
代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等