前言
反射,作为 Java 中的一个重要模块,需要我们熟练掌握。它可以赋予jvm动态编译的能力,在各个框架的源码中也经常出现,如果不能掌握此模块,在学习开源框架的底层设计时,难免会感觉到有一些吃力,今天来简单梳理一下这个模块的核心内容。
一、什么是反射
反射即反向探知,有点类似于考古学家根据发掘的物品来探知以前的事情。
在 Java 程序中,指在程序运行状态中:
1. 对于给定的一个类(Class)对象,可以获得这个类(Class)对象的所有属性和方法;
2. 对于给定的一个对象(new xxxClassName<? extends Object>),都能调用它的任意一个属性和方法
这种动态获取类的内容以及动态调用对象的方法和获取属性的机制,就叫做 Java 的反射机制。
首先写个简单的例子来体验一下反射,创建一个类:
public class Person {
public void jump() {
System.out.println("jump...");
}
}
通过反射来获取 Person 类的属性:
public class MainTest {
public static void main(String[] args) throws Exception {
//获取一个类对象
Class<Person> personClass = Person.class;
// 在 java.lang.reflect 包下面的都是反射相关的,可以参考在线 API 操作 https://www.matools.com/api/java8
// 通过反射中的API 操作,获取类对象的属性和方法
String simpleName = personClass.getSimpleName();
String className = personClass.getName();
ClassLoader classLoader = personClass.getClassLoader();
Field[] fields = personClass.getFields();
Method[] methods = personClass.getMethods();
System.out.println(simpleName);
System.out.println(className);
System.out.println(classLoader);
System.out.println(fields.length);
System.out.println(methods.length);
Person person = personClass.newInstance();
person.jump();
Method jump = personClass.getDeclaredMethod("jump", null);
// 通过反射执行方法
jump.invoke(personClass.newInstance());
}
}
通过上面的例子,可以看到,通过反射可以拿到 Person 对象中相关的属性和方法,也可以执行 jump 方法,这里与我们平时的 new Person().jump() 的形式不同。这种利用反射操作对象的形式就是反向探知。
二、反射的优缺点
优点:
增加程序的灵活性,避免固有逻辑写死到程序中;
代码相对简洁,可以提高代码的复用性。
缺点:
相比直接调用,反射有比较大的性能消耗;
存在内部暴露和安全隐患
下面,我们通过一个案例来体会一下它的优点。
创建一个接口:
public interface Ball {
void playBall();
}
接着创建两个对应的实现类:
public class BasketBall implements Ball {
@Override
public void playBall() {
System.out.println("打篮球...");
}
}
public class FootBall implements Ball{
@Override
public void playBall() {
System.out.println("踢足球...");
}
}
好了,现在根据业务需求,需要根据不同的运动,获取对应的对象来完成一些操作,我们可以像下面这样写:
public class BallMainTest {
public static void main(String[] args) {
Ball football = getInstance("football");
football.playBall();
}
/**
* 根据传入不同的key 获取对应的实例对象
* @param key
* @return
*/
public static Ball getInstance(String key) {
if ("football".equals(key)) {
return new FootBall();
}
if ("basketball".equals(key)) {
return new BasketBall();
}
return null;
}
}
那么问题来了,当我们的业务范围扩大,需要增加排球、乒乓球等,getInstance方法是不是需要继续增加 if 分支呢?这样看来,我们写到程序中的那些 if 判断,就相当于是将程序写死了,不够灵活,我们可以利用反射,进行改造:
/**
* 利用反射来动态获取
*
* @param key
* @return
*/
public static Ball getInstanceReflectByKey(String key) {
String basePackage = "com.custom.fs";
Ball ball = null;
try {
Class<?> clazz = Class.forName(basePackage + "." + key);
ball = (Ball) clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return ball;
}
改造后,我们发现,只需要动态传入你需要的类型名字即可,程序变的更加灵活,即使后续新加了其他的球类运动,只要符合我们的package 路径规则即可。
优点体会了,再体会一下它的缺点,为什么说它存在较大的性能消耗呢?同样,通过案例来分析。
我们分别通过两种方式来创建 100w 个实例对象,看看他们的耗时如何。
- 利用普通的 new 实例来创建
public static void main(String[] args) { // 通过正常调用来创建 100w 个实例 long start = System.currentTimeMillis(); for (int i = 0; i <1000000; i++) { getInstance("football"); } long end = System.currentTimeMillis(); System.out.println("总计耗时:" + (end - start)); }
- 利用发射方式创建
public static void main(String[] args) { // 通过正常调用来创建 100w 个实例 long start = System.currentTimeMillis(); for (int i = 0; i <1000000; i++) { getInstanceReflectByKey("FootBall"); } long end = System.currentTimeMillis(); System.out.println("总计耗时:" + (end - start)); }
可以看到,利用反射方式的耗时是普通方式耗时的将近 100 倍。既然性能这么低,我们为什么还要学习,这就好比考古学家去研究一个已经灭绝的物种,难度肯定要比研究现有的(一只猫,一只狗)物种要难,所以,虽然它性能低,我们还是有学习的必要的。
反射为什么这么慢?通过源码来分析一下,首先看一下Class.forName()方法干了啥:
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
/** Called after security check for system loader access checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
发现,是一个 native 本地方法,看不到具体的实现,那么继续查看class.newInstance()方法:
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
if (System.getSecurityManager() != null) {
// 进行安全检查
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
// NOTE: the following code may not be strictly correct under
// the current Java memory model.
// Constructor lookup
if (cachedConstructor == null) {
if (this == 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);
// Disable accessibility checks on the constructor
// since we have to do the security check here anyway
// (the stack depth is wrong for the Constructor's
// security check to work)
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;
// Security check (same as in java.lang.reflect.Constructor)
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
// 进行安全检查
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
// Run constructor
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
可以看到,每一次反射,都进行了2-3 次的安全检验,所以我们总结一下它慢的原因:
1. 调用了 native 方法
2. 每次反射都进行了安全检验
三、反射的基础操作
先来看一张图,了解 Class内部都有什么东西
1. 获取类对象的四种方式
Class<Person> clazz1 = Person.class;
Class<? extends Person> clazz2 = new Person().getClass();
Class<?> clazz3 = Class.forName("com.custom.fs.Person");
Class<?> clazz4 = MainTest.class.getClassLoader().loadClass("com.custom.fs.Person");
2. 基本操作
/**
* 获取类的修饰符 public\private...
* 返回 int 类型,具体值的代表含义,参考 API,有详细说明
*/
int modifiers = clazz1.getModifiers();
// 获取类对象的包名
Package classPackage = clazz1.getPackage();
String name = clazz1.getName();
String simpleName1 = clazz1.getSimpleName();
ClassLoader classLoader1 = clazz1.getClassLoader();
AnnotatedType[] annotatedInterfaces = clazz1.getAnnotatedInterfaces();
3. 字段操作
先定义一个父类Person,注意,它里面有 public 修饰的成员变量和 private 修饰的成员变量:
public class Person {
private String phone;
public static String name;
public void jump() {
System.out.println("jump...");
}
}
再创建一个子类,继承父类,注意子类中成员变量的修饰符:
public class Student extends Person{
public String idCard;
private String lastName;
public static int age;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
让我们来测试一下,通过 class 类对象来获取字段及操作字段:
public class FieldTest {
public static void main(String[] args) throws Exception {
Class<Student> clazz = Student.class;
//获取类型中定义的字段 公有的以及父类公有的
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field.getModifiers() + " " + field.getName());
}
System.out.println("___________________");
// 只能获取到对象自己定义的字段,可以获取到私有的
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field.getModifiers() + " " + field.getName());
}
Student student = clazz.newInstance();
//获取对象中的指定字段并赋值
Field lastName = clazz.getDeclaredField("lastName");
//通过反射修改私有变量,需要先进行授权,强吻,不让吻也得吻
lastName.setAccessible(true);
//意思是,修改 student 对象中的 lastName 字段,赋值为 John
lastName.set(student,"John");
System.out.println(student.getLastName());
}
}
4. 类中方法的操作
同样的,我们在 Student 类和 Person 类中添加公有和私有的方法,通过反射来获取一下,看看结果:
public class Person {
private String phone;
public static String name;
public void jump() {
System.out.println("jump...");
}
private String getPhone() {
return phone;
}
private void setPhone(String phone) {
this.phone = phone;
}
}
public class Student extends Person{
public String idCard;
private String lastName;
public static int age;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void say(String msg) {
System.out.println("hello:" + msg);
}
private void workHome(String type,int longTime) {
System.out.println("写" + type + "作业...." + longTime + "小时...");
}
}
测试一下:
public class MethodTest {
public static void main(String[] args) throws Exception {
Student student = new Student();
Class<? extends Student> studentClass = student.getClass();
//通过反射获取 student 中的方法,与 Field 字段的操作类似,可以获取到父类的公有方法
Method[] methods = studentClass.getMethods();
for (Method method : methods) {
System.out.println(method.getModifiers() + " " + method.getName());
}
System.out.println("=====================");
// 只能获取到当前类自己的方法,包括私有的
Method[] declaredMethods = studentClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getModifiers() + " " + declaredMethod.getName());
}
// 调用方法
Method say = studentClass.getDeclaredMethod("workHome", String.class, int.class);
say.setAccessible(true);
say.invoke(student,"Java",2);
}
}
5. 构造方法操作
其实构造方法的操作,同 Field、Method 一样,了解了上面的内容,构造器的操作,这里不进行过多演示了。
四、反射破坏单例
先来写一个简单的单例代码,如下:
public class UserSingle {
private static UserSingle instance;
private UserSingle () {
}
public static UserSingle getInstance() {
if (instance == null) {
instance = new UserSingle();
}
return instance;
}
}
测试一下正常调用,和利用反射破坏单例:
public class SingleTest {
public static void main(String[] args) throws Exception {
UserSingle instance1 = UserSingle.getInstance();
UserSingle instance2 = UserSingle.getInstance();
UserSingle instance3 = UserSingle.getInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
System.out.println("================");
// 利用反射破坏单例
Class<UserSingle> userSingleClass = UserSingle.class;
Constructor<UserSingle> constructor = userSingleClass.getDeclaredConstructor();
constructor.setAccessible(true);
UserSingle userSingle = constructor.newInstance(null);
System.out.println(userSingle);
}
}
可以看到,正常使用单例可以满足,当我们使用反射来进行构造的时候,破坏了单例模式,那么该如何处理呢?将单例代码,稍加改造:
public class UserSingle {
private static UserSingle instance;
private UserSingle () {
// 防止反射破坏单例
if (instance != null) {
throw new RuntimeException("实例已初始化,不允许重复操作");
}
}
public static UserSingle getInstance() {
if (instance == null) {
instance = new UserSingle();
}
return instance;
}
}
如上,我们在单例类的私有构造方法中,加入判断,来限制通过反射破坏单例。
这里的单例写的比较简单,感兴趣的童鞋,可以查看我的另外一篇关于单例模式的详细讲解。设计模式二(单例模式)_搬运Gong的博客-CSDN博客https://blog.csdn.net/qq_20315217/article/details/114130214
五、反射的应用场景
反射在各大框架中,使用的也是比较多。
JDBC 的封装
Spring IOC
MyBaits
...
这里不对其他框架源码使用反射进行分析,主在理解反射,后面会单独出一篇关于 Spring 源码分析的内容,到时再进行详细描述。