Java
的反射是指在运行期可以拿到一个对象的所有信息。反射支持在运行期间对某个实例一无所知的情况下,调用其方法。
Class 类
动态加载
JVM
在运行 Java
程序是,不是一次性把所有用到的 class
全部加载到内存中,JVM
只有在第一次读取到某一种 class
类型时将其加载进内存,每加载一种 class
,JVM
就为其创建一个相关联的 Class
类型的实例。
以 String
类为例,当 JVM
加载 String
类时,它首先读取 String.class
文件到内存,然后为 String
类创建一个 Class
实例并与之相关联
Class class = new Class(String);
上述 Class
实例是 JVM
内部创建的,在 JDK
源码中 Class
类的构造方法是 private
,只有 JVM
能创建 Class
实例。这个实例中保存了该 class
的所有信息,包括类名、包名、实现的接口、所有方法、字段等。因此,只要获取到某个 Class
实例就可以获取到该实例对应的 class
的所有信息。通过 Class
实例获取 class
信息的方法就称之为反射 Reflection
三种获取 Class 实例的方法
(推荐使用)方法一:直接通过一个 class 的静态变量 class 获取
Class class = String.class
方法二:如果持有一个实例变量, 可以通过该实例变量提供的 getClass() 方法获取
String str = "Hello World";
Class class = str.getClass();
方法三;如果知道一个 class 的完整类名,通过 Class.forName() 方法获取
Class class = Class.forName("java.lang.String");
读写类中字段
Class 类提供获取字段的方法
Field getField(name)
: 根据字段名获取某个public
的field
(包括父类)Field getDeclareField(name)
: 根据字段名获取当前类的某个field
(不包括父类、不受public
限制)Field[] getFields()
: 获取所有public
的field
(包括父类)Field[] getDeclareFields()
: 获取当前类的所有field
(不包括父类、不受public
限制) ```java public final class String { private final byte[] value; }
public static void main(String[] args) throws Exception { Field f = String.class.getDeclaredField(“value”); f.getName(); // “value” f.getType(); // class [B 表示byte[]类型 int m = f.getModifiers(); Modifier.isFinal(m); // true Modifier.isPublic(m); // false Modifier.isProtected(m); // false Modifier.isPrivate(m); // true Modifier.isStatic(m); // false }
<a name="5PhDy"></a>
### 读写字段值
通过 `Field` 实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用 `setAccessible(true)` 来访问非 `public` 字段。`Field.get(Object)` 获取指定实例的指定字段的值,`Field.set(Object, Object)` 设置指定实例的指定字段的值
```java
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
public static void main(String[] args) throws Exception {
Object p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
f.setAccessible(true);
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"
f.set(p, "Xiao Hong");
System.out.println(value); // "Xiao Hong"
}
读写类中方法
Class 类提供获取 Method 的方法
Method getMethod(name, Class...)
: 获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
: 获取当前类的某个Method
(不包括父类)Method[] getMethods()
: 获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
: 获取当前类的所有Method
(不包括父类) ```java public class Main { public static void main(String[] args) throws Exception {
} }Class stdClass = Student.class;
// 获取public方法getScore,参数为String:
System.out.println(stdClass.getMethod("getScore", String.class));
// 获取继承的public方法getName,无参数:
System.out.println(stdClass.getMethod("getName"));
// 获取private方法getGrade,参数为int:
System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
class Student extends Person { public int getScore(String type) { return 99; } private int getGrade(int year) { return 1; } }
class Person { public String getName() { return “Person”; } }
一个 `Method` 对象包含一个方法的所有信息:
- `getName()`:返回方法名称,例如:`"getScore"`;
- `getReturnType()`:返回方法返回值类型,也是一个 `Class` 实例,例如:`String.class`;
- `getParameterTypes()`:返回方法的参数类型,是一个 `Class` 数组,例如:`{String.class, int.class}`;
- `getModifiers()`:返回方法的修饰符,它是一个`int`,不同的 `bit` 表示不同的含义。
<a name="0ff3q"></a>
### 调用方法
对 `Method` 实例调用 `invoke` 方法就相当于调用方法。`invoke` 的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错
```java
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
// String对象:
String s = "Hello world";
// 获取String substring(int)方法,参数为int:
Method m = String.class.getMethod("substring", int.class);
// 在s对象上调用该方法并获取结果:
String r = (String) m.invoke(s, 6);
// 打印调用结果:
System.out.println(r);
}
}
如果获取到的
Method
表示一个静态方法,调用静态方法时,由于无须指定实例对象,所以invoke
方法传入的第一个参数永远是null
如果需要调用非public
方法,处理手段和Field
相似。获取Method
实例后,通过Method.setAccessible(true)
允许其调用,如果在 setAccessible(true) 方法前调用则会抛出IllegalAccessException
setAccessible(true) 并不是“银弹”
setAccessible(true)
可能会失败。如果 JVM
运行期存在 SecurityManager
,那么它会根据规则进行检查,有可能阻止 setAccessible(true)
。例如,某个 SecurityManager
可能不允许对 java
和 javax
开头的 package
的类调用 setAccessible(true)
,这样可以保证 JVM
核心库的安全。
多态
使用反射调用方法时,仍然遵循多态原则:即总是调用实例类型的覆写方法(如果存在)
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
// 获取Person的hello方法:
Method h = Person.class.getMethod("hello");
// 对Student实例调用hello方法:
h.invoke(new Student());
}
}
class Person {
public void hello() {
System.out.println("Person:hello");
}
}
class Student extends Person {
public void hello() {
System.out.println("Student:hello");
}
}
调用构造方法创建对象
通常情况下,创建对象新的实例时使用 new
操作符;但在反射中,可以调用 Class
提供的 newInstance()
方法创建对象。
Person p = Person.class.newInstance();
指定构造方法创建对象
Class.newInstance()
这种方式实际调用的是该类的 public
无参数构造方法。因此,如果构造方法带有参数或者不是 public
,那么就无法使用这种方式创建对象。
有些使用我们需要通过调用指定构造方法来创建对象。因此,Java
的反射 API
提供了 Constructor
对象,它包含一个构造方法的所有信息,每个 Constructor
都对应一个构造方法。因此,可以通过获取对象的指定 Constructor
来实现指定构造方法创建对象。
public class Main {
public static void main(String[] args) throws Exception {
// 获取构造方法Integer(int):
Constructor cons1 = Integer.class.getConstructor(int.class);
// 调用构造方法:
Integer n1 = (Integer) cons1.newInstance(123);
System.out.println(n1);
// 获取构造方法Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2);
}
}
Spring 框架的 IoC 核心代码就是利用反射实现创建对象。使用 Spring 框架时先按照约定在配置文件中配置对象,然后程序在启动读取配置文件来创建并配置对象
instanceof 和 isAssignableFrom()
当我们判断一个实例是否是某个类型时,正常情况下,使用 instanceof
操作符:
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true
如果是两个 Class
实例,要判断一个向上转型是否成立,可以调用 isAssignableFrom()
:
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
动态代理 Dynamic Proxy
Java 标准库提供来一种动态代理的机制,可以在运行期动态创建某个 interface 的实例。通过 JDK 提供的 Proxy 类和 InvocationHandler 接口实现动态代理,在运行期动态创建一个 interface 实例的方法如下:
- 定义一个 InvocationHandler 实例,它负责实现接口的方法调用
- 通过 Proxy.newProxyInstance() 创建 interface 实例,它需要 3 个参数:
- 使用的 ClassLoader,通常就是接口类的 ClassLoader;
- 需要实现的接口数组,至少需要传入一个接口;
- 用来处理接口方法调用的 InvocationHandler 实例;
- 将返回的 Object 强制转型为接口
```java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
} }InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
interface Hello { void morning(String name); }
<a name="WWnwa"></a>
### 动态代理和 AOP
借助 `Proxy` 和 `InvocationHandler` 可以实现 AOP 代理,在方向执行前后增加额外的操作。如方法执行耗时,方法参数校验等操作。简单示例如下:
1. 接口 `IPerson` 定义 `sayHello(String name)` 方法
```java
public interface IPerson {
void sayHello(String name);
}
- 简单实现类
Student
```java import lombok.extern.slf4j.Slf4j;
@Slf4j public class Student implements IPerson { @Override public void sayHello(String name) { log.debug(“Student.sayHello: {}”, name); } }
3. `InvocationHandler` 简单实现类 `MyInvocationHandler`,记录调用方法、传入形参、方法执行耗时
```java
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Objects;
@Slf4j
public class MyInvocationHandler implements InvocationHandler {
@Setter
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Objects.requireNonNull(target,"MyInvocationHandler:target can't be null");
log.debug("开始执行方法:{}", method.getName());
if (ArrayUtil.isNotEmpty(args)) {
for (Object arg : args) {
log.debug("参数类型:{},参数值:{}", arg.getClass(), arg);
}
} else {
log.debug("未传入参数");
}
final TimeInterval timer = DateUtil.timer();
timer.start();
final Object result = method.invoke(target, args);
final long interval = timer.interval();
log.debug("方法执行结束,用时:{} ms", interval);
return result;
}
}
Proxy
工厂类 MyProxyFactory 提供getProxy
方法为target
对象创意动态代理对象 ```java import java.lang.reflect.Proxy; import java.util.Objects;
public class MyProxyFactory {
public static Object getProxy(Object target) {
Objects.requireNonNull(target);
MyInvocationHandler handler = new MyInvocationHandler();
handler.setTarget(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
}
}
5. 调用示例
```java
public class Main {
public static void main(String[] args) {
final Student student = new Student();
final IPerson proxy = (IPerson)MyProxyFactory.getProxy(student);
proxy.sayHello("Hello World");
}
}
- 打印输出
23:21:44.778 [main] DEBUG com.dev.aop.MyInvocationHandler - 开始执行方法:sayHello
23:21:44.781 [main] DEBUG com.dev.aop.MyInvocationHandler - 参数类型:class java.lang.String,参数值:Hello World
23:21:44.784 [main] DEBUG com.dev.aop.Student - Student.sayHello: Hello World
23:21:44.784 [main] DEBUG com.dev.aop.MyInvocationHandler - 方法执行结束,用时:0 ms