0x00 什么是反射
反射机制是在程序运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
一句话概括:反射可以实现在运行时可以知道任意一个类的属性和方法。
0x01 优点与缺点
优点 :
灵活性高。因为反射属于动态编译,即只有到运行时才动态创建 &获取对象实例。
编译方式说明:
缺点 :
执行效率低,因为反射的操作 主要通过JVM执行,所以时间成本会 高于 直接执行相同操作。
容易破坏类结构,因为反射操作饶过了源码,容易干扰类原有的内部逻辑。
0x02 基本使用方法
获取Class对象有三种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Class Ar1 = Class.forName("top.sh1yan.reflect.Person" );Class Ar2 = Person.class;Person Ar3 = new Person ();Class Br1 = Ar3.getClass(); System.out.println(Ar1==Ar2); System.out.println(Ar2==Br1);
常用的反射方法:
样例代码(下列所有示例均在该样例代码中演示):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package top.sh1yan.test1;public class blogbody { public String name = "shiyan" ; public int age = 1 ; public static void main (String[] args) { } public blogbody () { System.out.println("无参构造函数" ); } public blogbody (String name,int age) { System.out.println("我的名字叫" +name+",我今年" +age+"岁了。" ); } public void addPrintName (String name) { System.out.println("我的名字叫:" + name ); } public void addPrintAge (int age) { System.out.println("我今年" + age +"岁了" ); } }
1、获取成员方法Method
1 2 3 4 5 public Method getDeclaredMethod (String name, Class<?>... parameterTypes) public Method[] getDeclaredMethods() public Method getMethod (String name, Class<?>... parameterTypes) public Method[] getMethods() Method.getName()
getDeclaredMethods()方法展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 步骤代码: Class Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Method[] Ar2 = Ar1.getDeclaredMethods(); for (Method Ar3 : Ar2) { System.out.println(Ar3); } 输出结果:public static void top.sh1yan.test1.blogbody.main(java.lang.String[]) throws java.lang.ClassNotFoundExceptionpublic void top.sh1yan.test1.blogbody.addPrintName(java.lang.String)public void top.sh1yan.test1.blogbody.addPrintAge(int )
getMethods()方法展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 步骤代码: Class Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Method[] Ar2 = Ar1.getMethods(); for (Method Ar3 : Ar2) { System.out.println(Ar3); } 输出结果:public static void top.sh1yan.test1.blogbody.main(java.lang.String[]) throws java.lang.ClassNotFoundExceptionpublic void top.sh1yan.test1.blogbody.addPrintName(java.lang.String)public void top.sh1yan.test1.blogbody.addPrintAge(int )public final void java.lang.Object.wait() throws java.lang.InterruptedExceptionpublic final void java.lang.Object.wait(long ,int ) throws java.lang.InterruptedExceptionpublic final native void java.lang.Object.wait(long ) throws java.lang.InterruptedExceptionpublic boolean java.lang.Object.equals(java.lang.Object)public java.lang.String java.lang.Object.toString()public native int java.lang.Object.hashCode()public final native java.lang.Class java.lang.Object.getClass()public final native void java.lang.Object.notify()public final native void java.lang.Object.notifyAll()
Method.getName() 方法演示
1 2 3 4 5 6 7 8 9 10 11 12 步骤代码: Class Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Method[] Ar2 = Ar1.getDeclaredMethods(); for (Method Ar3 : Ar2) { System.out.println(Ar3.getName()); } 输出结果: main addPrintName addPrintAge
getDeclaredMethod()方法展示:
1 2 3 4 5 6 7 8 9 10 11 步骤代码: Class<?> Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Object Br1 = Ar1.newInstance(); Method Ar2 = Ar1.getDeclaredMethod("addPrintName" ,String.class); Ar2.invoke(Br1, "shiyan" ); 输出结果: 无参构造函数 我的名字叫:shiyan
这里需要注意的是,输出结果中的“无参数构造函数”是有Br1这里产生的,这里我们初始化实例了这个类。
getMethod()展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 步骤代码: Class<?> Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Object Br1 = Ar1.newInstance(); System.out.println("-----------" ); Method Ar2 = Ar1.getMethod("hashCode" ); System.out.println(Ar2.invoke(Br1)); 输出结果: 无参构造函数 -----------1311053135
2、获取成员变量Field
类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。
1 2 3 4 public Field getDeclaredField (String name) public Field getField (String name) public Field[] getDeclaredFields() public Field[] getFields()
getFields()方法展示:
1 2 3 4 5 6 7 8 9 10 11 12 步骤代码: Class<?> Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Field[] Ar2 = Ar1.getFields(); for (Field Ar3 : Ar2) { System.out.println(Ar3.getName()); } 输出结果: name age
因为父类中无公开的成员变量,所以只输出了我们自己原有的参数。
getField()方法展示:
1 2 3 4 5 6 7 8 9 10 11 步骤代码: Class<?> Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Object Br1 = Ar1.newInstance(); Field Ar2 = Ar1.getField("name" ); System.out.println(Ar2.get(Br1)); 输出结果: 无参构造函数 shiyan
3、获取构造函数Constructor
类的成构造函数也是一个对象,它是java.lang.reflect.Constructor的一个对象,所以我们通过java.lang.reflect.Constructor里面封装的方法来获取这些信息。
1 2 3 public Constructor<T> getDeclaredConstructor (Class<?>... parameterTypes) public Constructor<T> getConstructor (Class<?>... parameterTypes) public Constructor[] getDeclaredConstructors()
getDeclaredConstructor()方法展示:
1 2 3 4 5 6 7 8 9 10 步骤代码: Class<?> Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Constructor Ar2 = Ar1.getConstructor(String.class,int .class); Ar2.newInstance("老P" ,2 ); 输出结果: 我的名字叫老P,我今年2 岁了。
注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式:
fromClass.getDeclaredConstructor(String.class).newInstance(“tengj”);
getConstructors()方法演示:
1 2 3 4 5 6 7 8 9 10 11 12 步骤代码: Class<?> Ar1 = Class.forName("top.sh1yan.test1.blogbody" ); Constructor[] Ar2 = Ar1.getConstructors(); for (Constructor Ar3 : Ar2) { System.out.println(Ar3); } 输出结果:public top.sh1yan.test1.blogbody(java.lang.String,int )public top.sh1yan.test1.blogbody()
常用方法名汇总:
获取构造函数:
getConstructors()//获取所有公开的构造函数
getConstructor(参数类型)//获取单个公开的构造函数
getDeclaredConstructors()//获取所有构造函数
getDeclaredConstructor(参数类型)//获取一个所有的构造函数
获取名字:
可以反射类名。
getName()//获取全名 例如:com.test.Demo
getSimpleName()//获取类名 例如:Demo
获取方法:
getMethods()//获取所有公开的方法
获取字段:
getFields()//获取所有的公开字段
getField(String name)//参数可以指定一个public字段
getDeclaredFields()//获取所有的字段
getDeclaredField(String name)//获取指定所有类型的字段
设置访问属性:
默认为false,设置为true之后可以访问私有字段。
Field.setAccessible(true)//可访问
Field.setAccessible(false)//不可访问
以及Method类的invoke方法
invoke(Object obj, Object… args) //传递object对象及参数调用该对象对应的方法
0x03 了解反射原理机制
注 :该章节主要是参考学习了bravo1988 大佬写的浅谈反射机制 的文章内容。
JVM构建实例过程→.class文件解读→类加载器流程→Class类解读→反射API
1、JVM构建实例过程
创建对象:blogbody Ar1 = new blogbody();
编译程序:blogbody.java → blogbody.class
加载类:ClassLoader加载.class文件到内存;*执行静态代码块和静态初始化语句
blogbody.class→ClassLoader()加载进内存→内存方法区[生成Class对象;Clas<blogbody>对象]→创建实例对象到内存中的堆
执行new:申请一片内存空间
调用构造器:创建一个空白对象
程序执行:子类调用父类构造器
构造器执行:执行构造代码块和初始化语句\ 构造器内容
2、.class文件解读
.class 文件主要为把Java代码编译成JVM可识别的字节码文件,可以理解该类文件中的内存均为JVM可以读取执行的指令集。
1 2 3 4 5 6 7 8 9 10 11 12 反编译举例: 0 :iconst_1 1 :istore_1 2 :iconst_2 3 :istore_2 4 :iload_1 5 :iload_2 6 :iadd 7 :istore_3 8 :返回 ...
3、类加载器流程
类加载器中核心方法为loadClass(),输入需要加载的类名,机会进行加载对应的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null ) { long t0 = System.nanoTime(); try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { long t1 = System.nanoTime(); c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException (name); }
加载.class文件大致可以分为3个步骤:
① 检查是否已经加载,有就直接返回,避免重复加载 ② 当前缓存中确实没有该类,那么遵循父优先加载机制,加载.class文件 ③ 上面两步都失败了,调用findClass()方法加载
需要注意的是,ClassLoader类本身是抽象类,而抽象类是无法通过new创建对象的。所以它的findClass()方法写的很随意,直接抛了异常,反正你无法通过ClassLoader对象调用。也就是说,父类ClassLoader中的findClass()方法根本不会去加载.class文件。
正确的做法是,子类重写覆盖findClass(),在里面写自定义的加载逻辑。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public Class<?> findClass(String name) throws ClassNotFoundException { try { byte [] datas = getClassData(name); if (datas == null ) { throw new ClassNotFoundException ("类没有找到:" + name); } return defineClass(name, datas, 0 , datas.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException ("类找不到:" + name); } }
defineClass()是ClassLoader定义的方法,目的是根据.class文件的字节数组byte[] b造出一个对应的Class对象。
类加载器流程导图:
4、Class类解读
每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。
在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。
方法名
说明
forName()
(1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 (2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass()
获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName()
取全限定的类名(包括包名),即类的完整名字。
getSimpleName()
获取类名(不包括包名)
getCanonicalName()
获取全限定的类名(包括包名)
isInterface()
判断Class对象是否是表示一个接口
getInterfaces()
返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss()
返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance()
返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器 。
getFields()
获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
getDeclaredFields
获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。
5、反射API
这里有一点需要先了解下就是newInstance(),newInstance()底层就是调用无参构造对象的newInstance()。
本质上Class对象要想创建实例,其实都是通过构造器对象。如果没有空参构造对象,就无法使用clazz.newInstance(),必须要获取其他有参的构造对象然后调用构造对象的newInstance()。
其次,需要再理解下Class、Field、Method、Constructor四个对象的关系:
Field、Method、Constructor对象内部有对字段、方法、构造器更详细的描述:
关于反射API,主要需要了解两个问题原理:1个是“为什么根据Class对象获取Method时,需要传入方法名+参数的Class类型”、另一个是“调用method.invoke(obj, args);时为什么要传入一个目标对象”。
到这里,我们就把两个重点难点的反射API了解了。
0x04 反射的一些利用特性
1、常规的反射使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package top.sh1yan.test1;import java.lang.reflect.Method;public class forname1 { public static void main (String[] args) throws Exception { Class<?> cls = Class.forName("java.lang.Runtime" ); Method getruntime = cls.getMethod("getRuntime" , null ); Object runtime = getruntime.invoke(null ); Method exec = cls.getMethod("exec" , String.class); exec.invoke(runtime, "calc" ); } }
以上,我们正常启动了一个计算器。
2、在int数据组中添加字符串类型内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package top.sh1yan.test1;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;public class forname3 { public static void main (String[] args) throws Exception { List<Integer> list = new ArrayList <Integer>(); list.add(1 ); addString(list); } public static void addString (List<Integer> Ar1) throws Exception{ Object obj = null ; Class clazz = Ar1.getClass(); Method method = clazz.getMethod("add" , Object.class); obj = method.invoke(Ar1, "shiyan" ); obj = method.invoke(Ar1, "男" ); for (Object i : Ar1) { System.out.println("val:" + i); } } }
0x05 参考链接
[1] https://zhuanlan.zhihu.com/p/66853751
[2] https://mp.weixin.qq.com/s/OMXrFc7uUN8wGv6yHno3Lg
[3] http://tengj.top/2016/04/28/javareflect/
[4] https://blog.csdn.net/carson_ho/article/details/80921333