一、介绍
Java反射是指在运行时检查或操作类、接口、字段或方法的能力。通过使用反射,可以在运行时获取类的信息、构造对象、执行方法、访问或修改字段等,而不需要事先知道类的结构。在Java中,反射功能由java.lang.reflect包提供支持。通过使用java.lang.reflect类中的Class、Constructor、Method、Field等类,可以实现对类的各种操作。使用反射可以实现一些高级的动态操作,比如动态代码生成、插件系统、对象序列化等。
public class Student {
public String name;
private int age;
final boolean isGraduation = Boolean.FALSE;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
private void study() {
System.out.println("学生正在学习!!!");
}
public void setName(String name) {
System.out.println("setName: " + name);
this.name = name;
}
public void setAge(int age) {
System.out.println("setAge: " + age);
this.age = age;
}
@Override
public String toString() {
return "Student " + name + " is " + age + "-years old";
}
}
【正射】:
我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的,然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。
Student s1 = new Student("小明", 12);
打印:Student 小明 is 12-years old
【反射】:
反射就是,一开始并不知道我们要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这就需要借助反射工具(api)来实例化我们需要访问的类。JDK从1.1开始就支持反射,使用java.lang.reflect包里的工具。
Class<?> studentClass = Class.forName("reflectiontest.Student");
Constructor<?> studentClassConstructor = studentClass.getConstructor(String.class, int.class);
Object s2 = studentClassConstructor.newInstance("小红", 10);
打印:Studentg 小红 is 10-years old
所以,这两段代码的区别就在于,正射是在运行前就已经知道要运行的类是Student;反射是在程序运行的时候才通过字符串"reflectiontest.Student"知道操作的类。
Class类
一个类中有属性,方法,构造器等,每个类可能都不一样,所以需要一个类,用来描述类,这就是Class。Class是用来描述类的类,它封装了当前对象所对应的类的信息。Java通过以下两种方式在运行时识别对象的类型和类的信息:
- RTTI(Run-Time Type Identification)运行时类型识别
- 反射机制
Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等,对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个类(而不是一个对象)在 JVM 中只会有一个Class实例。
获取Class对象的四种方式:
- 类的.class方法
Class<Student> clazz = Student.class;
- 使用
Class.forName()
方法
Class<?> clazz= Class.forName("reflectiontest.Student");
- 使用实例对象的
getClass()
方法
Student s1 = new Student("小明", 12);
Class<Student> clazz = s1.getClass();
- 根据类加载器获取
URL url = new URL("file:XXX\\Student.java");
ClassLoader classLoader = new URLClassLoader(new URL[]{url});
Class<Student> clazz= classLoader.loadClass("reflectiontest.Student");
下面列举Class类常用方法和说明,
方法 | 说明 |
---|---|
static Class forName(String className) | 返回指定类名name的Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例(已过时) |
String getName() | 返回该Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperclass() | 返回当前Class对象的父类Class对象 |
String getPackageName() | 返回当前Class对象所在的包名 |
Class[] getInterfaces() | 返回当前Class对象实现的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[] getConstructors() | 返回该类的所有公开构造方法 |
Constructor[] getDeclaredConstructors() | 返回该类的所有构造方法 |
Method[] getMethods() | 返回该类的所有公开方法,包括继承的公开方法 |
Method[] getDeclaredMethods() | 返回该类的所有方法,不包括继承的方法 |
Field[] getFields() | 返回该类的所有公开属性,包括继成的公开属性 |
Field[] getDeclaredFields() | 返回该类的所有属性,不包括集成的属性 |
上面方法中的Constructor、Method、Field均来自java.lang.reflect包。
Class对象是反射机制的核心,只有获取到一个类的Class对象,才能通过Class对象包含的类信息还原最初的类,即反射的核心要义。下面列举几个常见的例子。
- 反射实例化对象
// Class.forName反射获取class
Class<?> studentClass = Class.forName("reflectiontest.Student");
// 获取指定参数类型的构造方法
Constructor<?> constructor = studentClass.getConstructor(String.class, int.class);
// 通过newInstance实例化class
Object student = constructor.newInstance("小红", 10);
- 反射获取私有方法
Method study = studentClass.getDeclaredMethod("study");
// 私有方法需要设置accessible=true以开放其访问权限
study.setAccessible(true);
// 调用study方法
study.invoke(student);
- 反射获取私有属性
Field age = studentClass.getDeclaredField("age");
// 私有属性设置accessible=true以开放其访问权限
age.setAccessible(true);
// 获取student实例的age值
int intAge = (int) age.get(student);
// 设置实例student的age=9
age.set(student, 9);
- 修改final修饰的属性值(危险操作)
Field isGraduation = studentClass.getDeclaredField("isGraduation");
isGraduation.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
// 把isGraduation中的final修饰符去掉
modifiersField.setInt(isGraduation, isGraduation.getModifiers() & ~Modifier.FINAL);
// 修改student中isGraduation=true
isGraduation.setBoolean(student, true);
注意,只有非内联优化的变量才能通过反射修改值,内联优化的final属性是无法被修改的。这种做法是不推荐的,因为它会破坏final属性的设计初衷,可能会导致不可预测的行为和安全问题。在实际开发中,应该遵循final属性的设计原则,不要试图绕过它的限制。
二、原理
参考 https://www.cnblogs.com/yougewe/p/10125073.html
三、应用
1. Android LayoutInlater
我们在使用xml进行界面布局的时候,layout.xml里的每个节点就是通过反射转换成对应View的Java类的。在Activity的onCreate
生命周期函数中调用setContentView(layoutResId)
来绑定布局,最终会调到LayoutInflater.inflate(layoutResId)
中,
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
...
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
view = createView(context, name, null, attrs);
...
}
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
...
// 缓存中查找View的构造方法
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
if (constructor == null) {
// 反射获取View的Class对象
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
// 获取构造方法
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
...
}
// 实例化View
final View view = constructor.newInstance(args);
return view;
...
}
2. 动态代理
动态代理是通过反射机制动态生成代理者对象的一种设计模式,区别于静态代理,动态代理在运行时按需动态生成目标代理对象,解决了静态代理模式服务对象单一、扩展性低的问题。JDK提供了实现动态代理的顶层类java.util.reflect.Proxy
,可以通过Proxy类的静态方法Proxy.newProxyInstance()
动态创建一个类的代理类,并实例化。由它创建的代理类都是Proxy类的子类。
/**
* 定义目标对象的抽象接口
*/
public interface ISubject {
void buy();
}
/**
* 实现目标类(实现 ISubject 接口)
*/
public class Buyer implements ISubject {
@Override
public void buy() {
System.out.println("Buyer buy");
}
}
/**
* 声明调用处理类(实现 InvocationHandler 接口)
*/
public class SubjectInvocationHandler implements InvocationHandler {
private final ISubject mTarget;
public SubjectInvocationHandler(ISubject target) {
this.mTarget = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy invoke, proxy = " + proxy.getClass() + ", mTarget = " + mTarget.getClass());
return method.invoke(mTarget, args);
}
}
public class DynamicProxyTest {
public static void main(String[] args) {
Buyer buyer = new Buyer();
SubjectInvocationHandler handler = new SubjectInvocationHandler(buyer);
ISubject buyerProxy = (ISubject) Proxy.newProxyInstance(
buyer.getClass().getClassLoader(),
buyer.getClass().getInterfaces(),
handler);
buyerProxy.buy();
}
}
打印:
proxy invoke, proxy = class com.sun.proxy.$Proxy0, mTarget = class reflectiontest.dynamicproxy.realobj.Buyer
Buyer buy
打印结果显示生成了中间com.sun.proxy.$Proxy0
,我们通过在main方法中加下面这段代码,保留生成的代码。变量为 true 时,将会在工程目录下生成 $Proxy0 的 class 文件。
// JDK 1.8以前
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 1.8以后
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
// 或
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
package com.sun.proxy;
public final class $Proxy0 extends Proxy implements ISubject {
...
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
...
public final void buy() throws {
try {
// h是父类Proxy中的InvocationHandler类型成员变量
// 通过代理类访问目标对象的方法
// 最终会通过 super.h.invoke() 回调到我们重写的 InvocationHandler 实现类的 invoke() 中
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
...
// 通过反射获取目标类实现方法
m3 = Class.forName("reflectiontest.dynamicproxy.ISubject").getMethod("buy");
...
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
下面简单看看这个$Proxy0是怎么生成的,
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
/*
* Invoke its constructor with the designated invocation handler.
*/
...
// 通过构造器实例化代理对象
return cons.newInstance(new Object[]{h});
...
}
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces) {
// optimization for single interface
if (interfaces.length == 1) {
...
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
...
}
}
/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
Constructor<?> build() {
// 根据module和interfaces信息构造代理的Class对象,代理默认含有有个InvocationHandler参数类型的构造方法
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
...
cons = proxyClass.getConstructor(constructorParams);
...
return cons;
}
3. 注解
四、思考
1. 反射解决了什么问题
- 动态加载类:Java反射允许在运行时动态加载类,而不是在编译时就确定需要使用的类。
- 运行时获取类信息:通过反射可以在运行时获得类的信息,如类名、方法名、字段名、注解等,这使得可以在运行时动态地操作类和对象。
- 动态调用方法:使用反射可以在运行时动态调用类的方法,这对于框架和库的设计和使用非常有用。
- 修改私有字段和方法:反射允许访问和修改类的私有字段和方法,这在某些情况下是很有用的,但也需要小心使用,以避免破坏类的封装性。
应用场景一:有的类是我们在编写程序的时候无法使用new一个对象来实例化对象的。例如:
- 调用的是来自网络的二进制.class文件,而没有其.java代码
- 注解。运行时注解包含的元数据只能通过反射获取
应用场景二:动态加载(可以最大限度的体现Java的灵活性,并降低类的耦合性:多态)。有的类可以在用到时再动态加载到jvm中,这样可以减少jvm的启动时间,同时更重要的是可以动态的加载需要的对象(多态)
- 动态代理
应用场景三:避免将程序写死到代码里。比如Java通过new关键字创建了一个对象并编译成class字节码文件,如果我想替换这个对象,就得重新编译一个新的class文件,但如果使用反射Class.forName(String name)就可以从配置文件中读取要实例化的对象而无需重新编译。
- Spring框架中通过xml配置JavaBean
2. 效率问题
下面举个例子,
public class DynamicProxyTest {
public static long timeDiff(long old) {
return System.currentTimeMillis() - old;
}
public static void main(String[] args) {
long numTrials = (long) Math.pow(10, 7);
long millis = System.currentTimeMillis();
for (int i = 0; i < numTrials; i++) {
new Student();
}
System.out.println("Normal instaniation took: "
+ timeDiff(millis) + "ms");
millis = System.currentTimeMillis();
Class<Student> c = Student.class;
for (int i = 0; i < numTrials; i++) {
c.getConstructor().newInstance();
}
System.out.println("Reflecting instantiation took:"
+ timeDiff(millis) + "ms");
}
}
打印:
Normal instaniation took: 83ms
Reflecting instantiation took:510ms
Java反射机制性能低下有以下几个原因:
- 动态类型检查:在使用反射时,编译器无法对代码进行类型检查,而是在运行时才能确定访问对象的类型。这意味着在每次使用反射时都需要做类型检查和转换操作,增加了运行时的开销。比如对方法参数的装包和拆包。
- 方法调用开销:通过反射调用方法时,通常需要使用Method对象的invoke()方法,这会涉及方法查找、参数类型匹配等操作,相比直接调用方法会更耗时。
- 访问控制检查:反射机制允许改变类的私有字段或方法的访问权限,因此在访问私有成员时需要进行额外的访问控制检查,增加了开销。
- 缓存未命中:由于反射操作是动态的,不同于直接调用方法或访问字段,因此缓存机制可能失效,导致性能下降。
- 无法使用编译器优化:由于反射操作是动态的,所以无法使用某些编译器优化,比如内联优化,因为调用的具体方法在运行时才确定。
3. 安全问题
反射允许在运行时动态调用类的私有资源,这可能会引起意料之外的结果,造成程序功能失常。下面以单例为例,单例允许全局有且仅有一个实例,因此一般私有化其构造方法,但反射能通过修改构造方法权限实例化对象,造成单例失效。下面尝试一下单例模式的防反射攻击。
- 饿汉模式
public class SingleClass {
private final static SingleClass mInstance = new SingleClass();
private SingleClass() {
if (mInstance != null) {
throw new RuntimeException("单例模式不允许重复创建!");
}
}
public static SingleClass getInstance() {
return mInstance;
}
}
public static void main(String[] args) {
Constructor<SingleClass> constructor = SingleClass.class.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
异常:
- 懒汉模式
public class SingleClass {
private volatile static SingleClass mInstance;
private SingleClass() {
if (mInstance != null) {
throw new RuntimeException("单例模式不允许重复创建!");
}
}
public static SingleClass getInstance() {
if (mInstance == null) {
synchronized (SingleClass.class) {
if (mInstance == null) {
mInstance = new SingleClass();
}
}
}
return mInstance;
}
}
public static void main(String[] args) {
SingleClass singleClass = SingleClass.getInstance();
log(singleClass.toString());
Constructor<SingleClass> constructor = SingleClass.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingeClass reflectionSingeClass = (SingeClass) constructor.newInstance();
log(reflectionSingeClass.toString());
}
但是如果将getInstance和反射初始化调整顺序,单例就会被破坏。
如果在单例中增加标志位flag,如下,
private SingleClass() {
if (flag) {
throw new RuntimeException("单例模式不允许重复创建!");
} else {
flag = true;
}
}
这样也是不行的,因为变量flag值可以通过反射修改。
综上,这种局限于构造顺序的防反射攻击方法仅对饿汉模式有作用,对于懒汉模式是没有作用的。