反射的概念
- 反射(Reflection)是Java在运行时(Run time)可以访问,检测和修改它本身状态或行为的一种能力,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。
具体基本功能:
- 在运行时判定任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判定任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法。
反射的原理
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 包中最常用的几个类的关系如下:
其中最主要的三个类 Field、Method 和 Constructor 分别用于描述类的域、方法和构造器,它们有一个共同的父类 AccessibleObject,它提供了访问控制检查的功能。
一般的反射操作流程如下:
- 获取Class对象;
- 获取Class的实例;
- 获取类的方法和属性;
- 调用类的方法。
下面根据源码来分析上面步骤的实现原理:
1.获取类的对象
这里可以通过三种方法获取:
- 通过实例对象的
getClass()
方法获取。 - 从类对象的
class
属性获取。 - 利用
Class.forName()
方法通过全限定名称获取。
通过getClass()方法获取对象
getClass()
方法是继承自Object类的,这里标注为native方法,说明是基于C语言底层实现的。
public final native Class<?> getClass();
从类对象的class属性获取
Java中所有类对象都具有class属性,这应该是一个入口,类加载阶段,虚拟机在内存中生存一个java.lang.Class对象,用这个对象作为方法区中这个类的外部访问入口。
利用Class.forName()方法通过全限定名称获取
从这里可以看出,forName底层是用过调用者的类加载器来实现对类的加载,然后返回了类对象。
所以,forName本质是一个类加载过程和链接过程,可以呼应,在看Java虚拟机类加载机制中的第一步加载的时候,需要通过一个类的全限定名来获取定义此类的二进制字节流。
在后面调用类加载器的过程中,通过双亲委派机制,保证了加载的永远是同一个类,这样,如果之前加载过类再被加载,就会直接返回类对象。
public static Class<?> forName(String className)
throws ClassNotFoundException {
// 获取调用者的类
Class<?> caller = Reflection.getCallerClass();
// 获取类名,调用者的类加载器和调用者的类来获取反射的类
// 这里的true是指默认需要进行类的初始化
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
// 本地方法
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
2.获取对象实例
获取对象实例一般是通过调用类对象的Class的newInstance()
方法实现的。该方法的源码如下:
这里是Class类中的代码:
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
// 权限判断
if (System.getSecurityManager() != null) {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
// 如果不存在类的构造器,查找类的构造器
if (cachedConstructor == null) {
if (this == Class.class) {
// 不能在这里对Class类本身进行实例
throw new IllegalAccessException(
"Can not call newInstance() on the Class for java.lang.Class"
);
}
try {
Class<?>[] empty = {};
// 获取构造器
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
// 安全检查
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
c.setAccessible(true);
return null;
}
});
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
// 获取构造器
Constructor<T> tmpConstructor = cachedConstructor;
// 安全检查
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
// 利用构造器来实例化对象
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
这里是Constructor类中的源码:
@CallerSensitive
// 再往下就是对象创建的过程了
// 三个点是可变数组的意思
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
// 检查权限?
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
3.获取类的方法和属性
可以根据不同的特性要求,获取类的方法和属性,主要的有getDeclaredFields()
方法来获取类的所有属性,getDeclaredMethods()
方法来获取类的所有方法。
获取类的属性
源码如下:
@CallerSensitive
public Field[] getDeclaredFields() throws SecurityException {
// 检查权限
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
// 重新生成一个Field数组对象,将方法拷贝进去
return copyFields(privateGetDeclaredFields(false));
}
private Field[] privateGetDeclaredFields(boolean publicOnly) {
checkInitted();
Field[] res;
ReflectionData<T> rd = reflectionData();
// 先查看缓存中是否存在
if (rd != null) {
res = publicOnly ? rd.declaredPublicFields : rd.declaredFields;
if (res != null) return res;
}
// 缓存没有命中,那么就从JVM中获取所有属性并存入缓存,所谓缓存就是一张哈希表
res = Reflection.filterFields(this, getDeclaredFields0(publicOnly));
if (rd != null) {
// 这里判断是否只返回public的属性
if (publicOnly) {
rd.declaredPublicFields = res;
} else {
rd.declaredFields = res;
}
}
return res;
}
// 本地方法获取
private native Field[] getDeclaredFields0(boolean publicOnly);
获取类的方法
大致流程和上面获取属性一样。
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
checkInitted();
Method[] res;
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
// 本地方法
private native Method[] getDeclaredMethods0(boolean publicOnly);
4.调用类的方法
一般通过invoke()
函数来实现。
// 需要传入方法和方法参数
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
// 权限检查
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
// 获取方法的使用入口
ma = acquireMethodAccessor();
}
// 调用MethodAccessor的invoke方法,也是本地的
return ma.invoke(obj, args);
}
反射的应用
- 通过xml或注解,实现依赖注入,注解处理,动态代理,单元测试等功能,比如spring。
- 逆向代码 ,例如反编译。
- tomcat中的应用,处理客户端应答的时候,需要调用servlet接口的实现类,就是通过预先设置好的Java反射处理机制解析字节码文件并创建相应的实例对象,再调用方法。
- 实现序列化与反序列化,比如PO的ORM,Json解析等。
- 实现跨平台兼容,比如JDK中的SocketImpl的实现。
反射的使用
可能会用到的类
Java的类反射所需要的类分别是:Field、Constructor、Method、Class、Object:
- Class类:类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
- Object类:每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。
- Field类:提供有关类或接口的属性的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)属性或实例属性,简单的理解可以把它看成一个封装反射类的属性的类。
- Constructor类:提供关于类的单个构造方法的信息以及对它的访问权限。这个类和Field类不同,Field类封装了反射类的属性,而Constructor类则封装了反射类的构造方法。
- Method类:提供关于类或接口上单独某个方法的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。 这个类不难理解,它是用来封装反射类方法的一个类。
1. 对象的获取
//第一种方式 通过对象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();
//第二种方式 通过类的class属性
class1 = Person.class;
try {
//第三种方式 通过Class类的静态方法——forName()来实现
class1 = Class.forName("com.whoislcj.reflectdemo.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2. 获取对象的摘要信息
boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型
boolean isArray = class1.isArray();//判断是否是集合类
boolean isAnnotation = class1.isAnnotation();//判断是否是注解类
boolean isInterface = class1.isInterface();//判断是否是接口类
boolean isEnum = class1.isEnum();//判断是否是枚举类
boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
String className = class1.getName();//获取class名字 包含包名路径
Package aPackage = class1.getPackage();//获取class的包信息
String simpleName = class1.getSimpleName();//获取class类名
int modifiers = class1.getModifiers();//获取class访问权限
Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
Class<?> declaringClass = class1.getDeclaringClass();//外部类
3. 获取对象的属性、方法、构造函数
Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性
Field[] publicFields = class1.getFields();//获取class对象的public属性
try {
Field ageField = class1.getDeclaredField("age");//获取class指定属性
Field desField = class1.getField("des");//获取class指定的public属性
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法
Method[] allMethods = class1.getMethods();//获取class对象的所有方法 包括父类的方法
Class parentClass = class1.getSuperclass();//获取class对象的父类
Class<?>[] interfaceClasses = class1.getInterfaces();//获取class对象的所有接口
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
try {
Constructor<?> constructor = class1.getDeclaredConstructor(new Class[]{String.class});//获取指定声明构造函数
Constructor publicConstructor = class1.getConstructor(new Class[]{});//获取指定声明的public构造函数
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Annotation[] annotations = class1.getAnnotations();//获取class对象的所有注解
Annotation annotation = class1.getAnnotation(Deprecated.class);//获取class对象指定注解
Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的 Type
Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合
4. 对象动态生成
//第一种方式 Class对象调用newInstance()方法生成
Object obj = class1.newInstance();
//第二种方式 对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成
Constructor<?> constructor = class1.getDeclaredConstructor(new Class[]{String.class});//获取指定声明构造函数
obj = constructor.newInstance(new Object[]{"lcj"});
5. 动态调用函数
try {
// 生成新的对象:用newInstance()方法
Object obj = class1.newInstance();
//判断该对象是否是Person的子类
boolean isInstanceOf = obj instanceof Person;
//首先需要获得与该方法对应的Method对象
Method method = class1.getDeclaredMethod("setAge", new Class[]{int.class});
//调用指定的函数并传递参数
method.invoke(obj, 28);
method = class1.getDeclaredMethod("getAge");
Object result = method.invoke(obj, new Class[]{});
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
6. 通过反射机制获取泛型类型
Person<String> person = new Person<>();
//第一种方式 通过对象getClass方法
Class<?> class1 = person.getClass();
Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的 Type
Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的Type集合
getComponentType(genericSuperclass);
getComponentType(interfaceTypes[0]);
7. 通过反射机制获取注解信息
try {
//首先需要获得与该方法对应的Method对象
Method method = class1.getDeclaredMethod("jumpToGoodsDetail", new Class[]{String.class, String.class});
Annotation[] annotations1 = method.getAnnotations();//获取所有的方法注解信息
Annotation annotation1 = method.getAnnotation(RouterUri.class);//获取指定的注解信息
TypeVariable[] typeVariables1 = method.getTypeParameters();
Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();//拿到所有参数注解信息
Class<?>[] parameterTypes = method.getParameterTypes();//获取所有参数class类型
Type[] genericParameterTypes = method.getGenericParameterTypes();//获取所有参数的type类型
Class<?> returnType = method.getReturnType();//获取方法的返回类型
int modifiers = method.getModifiers();//获取方法的访问权限
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
反射的优缺点
反射的优点:
-
可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
-
类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
-
调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
-
动态类加载,运行期间可以判断类型。
反射的缺点:
-
性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
-
安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
-
内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。