反射简介 什么是反射 反射(Reflection
)机制可以在运行时访问 Java 对象的构造方法、属性、方法。相关的类都在 java.lang.reflect
包下。重要的类有:
Class
Member
Constructor
Field
Method
Array
Modifier
反射的应用场景
开发通用框架 :比如 Spring
,为了保证框架的通用性,它们可能需要根据配置文件去加载不同的类,调用不同的方法,这个时候就需要用到反射、
动态代理 :在切面编程(AOP)中,需要拦截特定的方法通常通过动态代理实现,这就会用到反射技术。
注解 :注解本身只是起到标记作用,它需要利用反射机制,根据注解标志去调用注解解释器执行行为。
Java Hook :利用反射和动态代理机制可以实现 Java 层次的 Hook,具体参考:Java基础:基于反射和动态代理的Hook
反射的缺点
性能开销大 :具体有哪些性能开销可以参考:反射的性能开销都在那
不安全 :反射调用的时候可以忽略权限检查,可能会导致安全问题
内部曝光 :反射可以调用私有方法或者字段,所以反射的使用可能会导致意想不到的副作用。
反射的原理 了解反射的原理就需要去了解 Java 虚拟机的类加载机制,这一部分可以参考 深入理解 Java 虚拟机 第三版 中的第七章。
Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称为虚拟机的类加载机制。
类加载的过程:
加载(不是类加载)过程中,虚拟机需要完成: 1.通过一个类的全限定名来获取定义此类的二进制字节流 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 3.在 Java 堆内存中生成一个代表这个类的 java.lang.Class
对象,作为方法区这个类的各种数据的访问入口
正常情况下,Java 对象的创建是通过 new 关键字,创建的原理可以参考 深入理解 Java 虚拟机 第三版 中的 2.3.1 部分。
反射创建对象和正常的对象创建原理一样,都是利用 Java 虚拟机通过类加载生成的 java.lang.Class
对象实现的。
使用反射 这里给出下面的例子中的相关类:
1.需要用到的抽象类:People
、接口:PeopleProtocol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class People { public static final String ID = "People" ; public String name = "" ; public int age = 0 ; abstract boolean isSingle () ; public void show () { System.out.println("name = " + name + " age = " + age); } public int getAge () { return age; } } public interface PeopleProtocol { void eat () ; void chat () ; }
2.实现类 Man
:
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 43 44 45 46 47 public class Man extends People implements PeopleProtocol { public static final String ID = "123" ; public String name = "man" ; private int id = 123 ; public Man () { } public Man (String name) { this .name = name; } public Man (String name, int age) { this .name = name; this .age = age; } private Man (int age) { this .age = age; } @Override boolean isSingle () { return true ; } @Override public void show () { System.out.println("My name is " + name); } private String getPrefixName (String prefix) { return prefix + "name" ; } @Override public void eat () { System.out.println("I can eat everything" ); } @Override public void chat () { System.out.println("I can chat with everyone" ); } }
Class 获取 Class 的方式有三种:
通过全限定名获取
通过类获取
通过对象获取
1 2 3 4 5 6 7 8 9 10 11 12 1. 通过全限定名获取try { Class<?> clazz = Class.forName("com.kotlin.reflection.Man" ); } catch (ClassNotFoundException e) { } Class<Man> clazz = Man.class; Man man = new Man(); Class<? extends Man> clazz = man.getClass();
类型判断 Java 中类型判断也有两种方式:
instanceof
关键字
Class 实例的 instance
方法
1 2 3 4 5 6 7 8 if (man instanceof People) { System.out.println("man is people" ); } if (clazz.isInstance(man)) { System.out.println("man is Man" ); }
创建实例 Java 中利用反射创建对象的方式也有两种:
Class 实例的 newInstance
方法
Constructor 实例的 newInstance
方法
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 try { StringBuilder temp = StringBuilder.class.newInstance(); temp.append("temp is StringBuilder" ); System.out.println(temp); } catch (InstantiationException e) { } catch (IllegalAccessException e) { } try { Constructor<StringBuilder> constructor = StringBuilder.class.getConstructor(String.class); StringBuilder temp = constructor.newInstance("temp is StringBuilder" ); System.out.println(temp); } catch (NoSuchMethodException e) { } catch (IllegalAccessException e) { } catch (InstantiationException e) { } catch (InvocationTargetException e) { } temp is StringBuilder temp is StringBuilder
Member Member
: 用来表示类中的一个成员:构造函数、属性、方法,Constructor
、Field
、Method
都是它的子类。
这里先构建出下文需要用到的实例:
1 2 Man man = new Man(); Class<? extends Man> clazz = man.getClass();
Constructor 获取 Class 实例的 Constructor
有四种方式:
getConstructor
:返回类的特定 public 构造方法,参数为方法参数对应 Class 的实例
getDeclaredConstructor
:返回类的特定构造方法,参数为方法参数对应 Class 的实例
getConstructors
:返回类的所有 public 构造方法
getDeclaredConstructors
:返回类的所有构造方法
getConstructor
在上面创建实例中已经使用过了,getDeclaredConstructor
和其类似。所以这里主要看看 getConstructors
和 getDeclaredConstructors
的使用:
1 2 3 4 5 6 7 8 9 Constructor<?>[] constructors = clazz.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println("name = " + constructor.getName() + " ParameterType = " + Arrays.toString(constructor.getParameterTypes())); } name = com.kotlin.reflection.Man ParameterType = [class java .lang .String , int ] name = com.kotlin.reflection.Man ParameterType = [class java .lang .String ]name = com.kotlin.reflection.Man ParameterType = []
1 2 3 4 5 6 7 8 9 10 constructors = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { System.out.println("name = " + constructor.getName() + " ParameterType = " + Arrays.toString(constructor.getParameterTypes())); } name = com.kotlin.reflection.Man ParameterType = [int ] name = com.kotlin.reflection.Man ParameterType = [class java .lang .String , int ] name = com.kotlin.reflection.Man ParameterType = [class java .lang .String ]name = com.kotlin.reflection.Man ParameterType = []
Field 获取 Class 实例的 Field
有四种方式:
getField
:返回类的特定 public 属性,参数是属性名
getDeclaredField
:返回类的特定属性,参数是属性名,但是无法得到继承的属性
getFields
:返回类的所有 public 属性
getDeclaredFields
:返回类的所有属性,但是无法得到继承的属性
如果想获取 Man
实例的 name
属性,可以使用 getField
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 try { Field field = clazz.getField("name" ); System.out.println("field 的 type = " + field.getType()); field.set(man, "modify name" ); String name = (String) field.get(man); System.out.println("修改后的值 = " + name); } catch (NoSuchFieldException | IllegalAccessException e) { } field 的 type = class java .lang .String 修改后的值 = modify name
如何设置/获取 Field 实例对应的属性值呢? 1.设置值:调用 Field 实例的 set 方法,第一个参数是修改的对象,第二个参数是想设置的值 2.获取值:调用 Fieid 实例的 get 方法,需要传入修改的对象作为参数
如果修改的是静态属性,就不需要传入修改的对象,直接传入 null 即可
如果想获取 Man
实例的 id
属性,就需要使用 getDeclaredField
方法:
1 2 3 4 5 6 7 8 9 10 try { Field field = clazz.getDeclaredField("id" ); field.setAccessible(true ); System.out.println(field.get(man)); } catch (NoSuchFieldException | IllegalAccessException e) { } 123
如果修改的是私有属性,需要使用 setAccessible(true) 避免访问无法访问
接下里看看 getFields
和 getDeclaredFields
的使用:
1 2 3 4 5 6 7 8 9 10 11 12 Field[] fields = clazz.getFields(); for (Field field : fields) { System.out.println("name = " + field.getName() + " type = " + field.getType()); } name = ID type = class java .lang .String name = name type = class java .lang .String name = ID type = class java .lang .String name = name type = class java .lang .String name = age type = int
1 2 3 4 5 6 7 8 9 10 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { System.out.println("name = " + field.getName() + " type = " + field.getType()); } name = ID type = class java .lang .String name = name type = class java .lang .String name = id type = int
Method 获取 Class 实例的 Method
有四种方式:
getMethod
:返回类或者接口的特定 public 方法,第一个参数是方法名,后面的参数是方法参数对应的 Class 对象
getDeclaredMethod
:返回类或者接口的特定方法,第一个参数是方法名,后面的参数是方法参数对应的 Class 对象,但是无法得到继承的方法
getMethods
:返回类或者接口的所有 public 方法
getDeclaredMethods
:返回类或者接口的所有方法,但是无法得到继承的方法
如果想获取 Man
实例的 eat
方法,可以使用 getMethod
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 try { Method method = clazz.getMethod("eat" ); method.invoke(man); } catch (NoSuchMethodException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } I can eat everything
如何调用 Method 实例对应的方法呢? 调用 Method 实例的 invoke 方法,第一个参数需要传入调用的对象,后面的参数是待传入参数的值
如果修改的是静态方法,就不需要传入修改的对象,直接传入 null 即可
如果想获取 Man
实例的 getPrefixName
方法,就需要使用 getDeclaredMethod
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 try { Method method = clazz.getDeclaredMethod("getPrefixName" , String.class); method.setAccessible(true ); String newName = (String) method.invoke(man, "temp" ); System.out.println("newName = " + newName); } catch (NoSuchMethodException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } newName = tempname
和修改私有属性一样,修改私有方法也需要调用 setAccessible(true) 避免无法访问
接下来看看 getMethods
和 getDeclaredMethods
的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println("name = " + method.getName() + " parameter type = " + Arrays.toString(method.getParameterTypes()) + " return type = " + method.getReturnType()); } name = eat parameter type = [] return type = void name = show parameter type = [] return type = void name = chat parameter type = [] return type = void name = getAge parameter type = [] return type = int name = wait parameter type = [long ] return type = void name = wait parameter type = [long , int ] return type = void name = wait parameter type = [] return type = void name = equals parameter type = [class java .lang .Object ] return type = boolean name = toString parameter type = [] return type = class java .lang .String name = hashCode parameter type = [] return type = int name = getClass parameter type = [] return type = class java .lang .Class name = notify parameter type = [] return type = void name = notifyAll parameter type = [] return type = void
1 2 3 4 5 6 7 8 9 10 11 12 Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { System.out.println("name = " + method.getName() + " parameter type = " + Arrays.toString(method.getParameterTypes()) + " return type = " + method.getReturnType()); } name = eat parameter type = [] return type = void name = getPrefixName parameter type = [class java .lang .String ] return type = class java .lang .String name = isSingle parameter type = [] return type = boolean name = show parameter type = [] return type = void name = chat parameter type = [] return type = void
Array 数组 Array
在 Java 中比较特殊,下面看下如何在 Java 中通过反射创建一个数组:
1 2 3 4 5 6 7 Object array = Array.newInstance(clazz, 5 ); Array.set(array, 0 , man); Man index0 = (Man) Array.get(array, 0 ); System.out.println(index0); com.kotlin.reflection.Man@2e5c649
其它 java.lang.reflect
包下还有很多其它的类,这里再介绍一个 Modifier
。 Java 针对类、变量、方法 有很多修饰符,例如public、private、static、final、synchronized、abstract等。 Modifier
中就记录了这些修饰符,下面看看具体的例子:
1 2 3 4 5 6 7 8 9 10 11 12 try { Field field = clazz.getDeclaredField("id" ); int modifier = field.getModifiers(); System.out.println("modifier = " + modifier); System.out.println("modifier = " + Modifier.toString(modifier)); } catch (NoSuchFieldException e) { } modifier = 2 modifier = private
从上面的运行结果来看,是否有这些修饰符是通过一个 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public static String toString (int mod) { StringJoiner sj = new StringJoiner(" " ); if ((mod & 1 ) != 0 ) { sj.add("public" ); } if ((mod & 4 ) != 0 ) { sj.add("protected" ); } if ((mod & 2 ) != 0 ) { sj.add("private" ); } if ((mod & 1024 ) != 0 ) { sj.add("abstract" ); } if ((mod & 8 ) != 0 ) { sj.add("static" ); } if ((mod & 16 ) != 0 ) { sj.add("final" ); } if ((mod & 128 ) != 0 ) { sj.add("transient" ); } if ((mod & 64 ) != 0 ) { sj.add("volatile" ); } if ((mod & 32 ) != 0 ) { sj.add("synchronized" ); } if ((mod & 256 ) != 0 ) { sj.add("native" ); } if ((mod & 2048 ) != 0 ) { sj.add("strictfp" ); } if ((mod & 512 ) != 0 ) { sj.add("interface" ); } return sj.toString(); }
动态代理 静态代理 说到动态代理之前就需要先说一下静态代理,这样方便引出动态代理,下面是一个静态代理的例子:
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 public interface Subject { void request () ; } public class RealSubject implements Subject { @Override public void request () { } } public class ProxySubject implements Subject { private Subject real; @Override public void request () { if (real == null ) { real = new RealSubject(); } real.request(); } }
简单来说,静态代理就是创建一个和真实类具有相同接口的代理类。如果需要调用真实类的相关方法,直接调用代理类即可。
从实现方式来看,可以看出静态代理的弊端:
有多个类需要实现代理的时候,如果选择一个代理类来实现所有的接口,那么会造成这个代理类过大;如果选择多个代理类,那么会造成产生过多的代理类
当真实类的接口修改时,需要同时修改真实类和代理类,不易维护
动态代理 基于静态代理的问题,从而引出了动态代理:让代理类在运行时动态的生成。
动态代理分为两类:
JDK 动态代理:通过实现接口的方式
CGLIB 动态代理:通过继承类的方式
由于 CGLIB 动态代理需要引入相关的依赖,所以这里主要说一下 JDK 动态代理。 JDK 动态代理主要涉及两个类:
InvocationHandler
Proxy
下面将上面提到的静态代理例子修改成动态代理:
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 interface Subject { void request () ; } class RealSubject implements Subject { @Override public void request () { System.out.println("RealSubject request" ); } } class Main { public static void main (String[] args) { final RealSubject realSubject = new RealSubject(); ClassLoader classLoader = realSubject.getClass().getClassLoader(); Class<?>[] interfaces = realSubject.getClass().getInterfaces(); Subject proxy = (Subject) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() { @Override public Object invoke (Object o, Method method, Object[] args) throws Throwable { System.out.println("before " + method.getName()); Object result = method.invoke(realSubject, args); System.out.println("after " + method.getName()); return result; } }); proxy.request(); } }
Kotlin 中使用 动态代理例子 这一篇的标题是 Kotlin 的反射,但是由于当前的 Kotlin 仍然存在一些问题,所以一般情况下还是使用 Java 的反射。下面给出一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 val classWindowManagerGlobal = Class.forName("android.view.WindowManagerGlobal" )val windowSession = classWindowManagerGlobal.getDeclaredMethod("getWindowSession" ).invoke(null )val classWindowSession = windowSession.javaClassval proxy = Proxy.newProxyInstance(classWindowSession.classLoader, classWindowSession.interfaces) { proxy, method, args -> val ret = if (args == null ) { method.invoke(windowSession) } else { method.invoke(windowSession, *args) } if (method.name.startsWith("addToDisplay" )) { try { onAddToDisplayCalled() } catch (e: Exception) { CalidgeLogger.e(TAG, "onAddToDisplayCalled failed" , e) } } ret } val fieldWindowSession = classWindowManagerGlobal.getDeclaredField("sWindowSession" )fieldWindowSession.isAccessible = true fieldWindowSession.set (null , proxy)
javaClass 和 class.java 下面看看各种 Class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 val clazz1 = Boolean .javaClassval clazz2 = Boolean ::class val clazz3 = Boolean ::class .javaval clazz4 = java.lang.Boolean ::class .javaprintln("class1 = $clazz1 " ) println("class2 = $clazz2 " ) println("class3 = $clazz3 " ) println("class4 = $clazz4 " ) class1 = class kotlin .jvm .internal .BooleanCompanionObject class2 = boolean (Kotlin reflection is not available) class3 = boolean class4 = class java .lang .Boolean
Kotlin 中和 Class
对应的叫 KClass
,可以通过 ::class
获取,这里它不是主角。
主角是 .javaClass
和 ::class.java
,从名字上来看它们的获取结果应该是一致的才对,但结果却告诉我们差别很大。
当你使用 Boolean.javaClass
的时候,AndroidStudio 会出现下面的提示:
1 The resulting type of this 'javaClass' call is Class<Boolean .Companion> and not Class<Boolean >. Please use the more clear '::class.java' syntax to avoid confusion
因此,如无必要,不要使用 .javaClass
,尽量使用 ::class.java
。
装箱、拆箱问题 Java 中存在基本类型的装箱、拆箱问题,所以一般都有基本类型和包装类型,例如:boolean
和 java.lang.Boolean
。
Kotlin 中不存在基本类型,只有 Boolean
,但是 JVM 会对其优化,一般情况下都和基本类型等价。所以 Boolean::class.java
的结果才会是 boolean
,当 Boolean
被包装时,会怎么样呢?可以看看下面的例子:
1 2 3 4 5 6 7 8 val boolean1: Boolean = false val boolean2: Boolean ? = false println("Boolean 简单类型 Class = ${boolean1::class.java} " ) println("Boolean 包装类型 Class = ${boolean2!!::class.java} " ) Boolean 简单类型 Class = booleanBoolean 包装类型 Class = class java .lang .Boolean
类型判断 1 2 3 4 5 6 fun isBoolType (clazz: Class <*>) : Boolean { return clazz == java.lang.Boolean .TYPE || clazz == java.lang.Boolean ::class .java || clazz == Boolean ::class .java }
参考文章