Appearance
Java 反射
1. 反射
1.1. Class 类
获取 Class 对象的三种方式:
通过
Object类中的getClass()方法获取对象对应的Class对象:JavaClass cl = o.getClass();通过
Class的forName静态方法获取类名对应的Class对象:JavaString className = "java.util.Random"; Class cl = Class.forName(className);1
2通过
T.class(T是任意的 Java 类型,也可以是void关键字)获取Class对象:JavaClass cl1 = Random.class; // if you import java.util.*; Class cl2 = int.class; Class cl3 = Double[].class;1
2
3
通过 Class 的 getName() 方法可以获取类的完整名称:
Java
var generator = new Random();
Class cl1 = generator.getClass();
String name1 = cl1.getName(); // name is set to "java.util.Random"
Class cl2 = int[].class;
String name2 = cl2.getName(); // name is set to "[I"
Class cl3 = Double[].class;
String name3 = cl3.getName(); // name is set to "[Ljava.lang.Double"1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Note 1:需要注意的是,
Class对象实际上表示的是一个 “类型”,这个 “类型” 可能是 “类”,也可能不是。例如:int不是类,但也可以通过int.class获取一个Class对象。
Note 2:
Class类实际上是一个泛型类。例如,Employee.class的类型是Class<Employee>。在大多数情况下,我们可以忽略它的类型参数,而直接使用原始的Class类就行了。
JVM 为每个类型管理一个唯一的 Class 对象。因此,可以利用 == 运算符实现两个类对象的比较。例如:
Java
var e = new Employee();
if (e.getClass() == Employee.class) // true1
2
2
1.2. Field、Method、Constructor
Class 对象中的 getFields、getMethods、getConstructors 方法分别返回这个类支持的公共字段、方法和构造器数组,其中包括超类的公共成员。
Class 对象中的 getDeclareFields、getDeclareMethods、getDeclareConstructors 方法分别返回这个类中声明的全部字段、方法和构造器数组,其中包括私有成员、包成员和受保护成员,但不包括超类的成员。
1.2.1. Field
当 Field 对象表示一个私有字段时,在默认情况下,如果我们直接调用 Field 的 get、set 方法会得到一个 IllegalAccessException。不过,我们可以调用 Field、Method 或 Constructor 对象的 setAccessible 方法覆盖 Java 的访问控制,例如:
Java
var harry = new Employee("Harry Hacker", 50000, 10, 1, 1989);
Class cl = harry.getClass();
Field f = cl.getDeclaredField("name");
f.setAccessible(true); // now OK to call f.get(harry)
Object v = f.get(harry);1
2
3
4
5
2
3
4
5
Note 1:
setAccessible可以被模块系统或安全管理器拒绝。
Note 2
因为有太多的库都使用了反射,所以当我们使用反射访问一个模块中非公共特性时,Java 9 和 10 只会给出一个警告。例如:
TextWARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by objectAnalyzer.ObjectAnalyzer (file:/home/cay/books/cj11/code/v1ch05/bin/) to field java.util.ArrayList.serialVersionUID WARNING: Please consider reporting this to the maintainers of objectAnalyzer.ObjectAnalyzer WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release1
2
3
4
5对目前而言,可以禁用这些警告。需要把
java.base模块中的java.util和java.lang包 “打开” 为 “无名的模块”。语法如下:Bash$ java -add-opens java.base/java.util=ALL-UNNAMED \ -add-opens java.base/java.lang=ALL-UNNAMED \ objectAnalyzer.ObjectAnalyzerTest1
2
3或者,也可以运行以下命令来查看这个程序在将来的 Java 版本中有何表现:
Bash$ java -illegal-access=deny objectAnalyzer/ObjectAnalyzerTest
1.2.2. Method
Method 类有一个 invoke 方法,用来调用包装在当前 Method 对象中的方法:
Java
Object invoke(Object obj, Object ... args);obj 为隐式参数,args 为显示参数。对于静态方法,obj 参数可以忽略,也就是设为 null 值。下面是一个调用示例:
Java
var harry = new Employee("Harry Hacker", 50000, 10, 1, 1989);
Class cl = harry.getClass();
Method m1 = cl.getMethod("getName");
String n = (String) m1.invoke(harry);
Method m2 = cl.getMethod("raiseSalary", double.class);
double s = (Double) m2.invoke(harry);1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
注意下第 8 行,raiseSalary 原始方法返回的是基本类型 double,而通过 invoke 方式调用时返回的对象实际上是 Double,所以我们必须先强制转换为 Double,再自动拆箱为我们需要的 double 值。
1.2.3. Constructor
通过 Constructor 的 newInstance 方法直接构造对象实例:
Java
Class cl = Random.class;
Constructor cons = cl.getConstructor(long.class);
Object obj = cons.newInstance(42L);1
2
3
2
3
2. IOC
2.1. ServiceLoader
定义一个接口或超类:
Javapackage serviceLoader; public interface Cipher { byte[] encrypt(byte[] source, byte[] key); byte[] decrypt(byte[] source, byte[] key); int strength(); }1
2
3
4
5
6
7
8提供一个或多个实现这个服务的类:
Javapackage serviceLoader.impl; public class CaesarCipher implements Cipher { public byte[] encrypt(byte[] source, byte[] key) { var result = new byte[source.length]; for (int i = 0; i < source.length; i++) result[i] = (byte) (source[i] + key[0]); return result; } public byte[] decrypt(byte[] source, byte[] key) { return encrypt(source, new byte[]{(byte) - key[0]}); } public int strength() { return 1; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19实现类可以放在任意的包中,而不一定是服务接口所在的包。每个实现类必须有一个无参数构造器。
现在把这些类的类名增加到
META-INF/services目录下的一个 UTF-8 编码文本文件中,文件名必须与完全限定类名一致。在我们的例子中,文件META-INF/services/serviceLoader.Cipher必须包含这样一行:TextserviceLoader.impl.CaesarCipher完成以上准备工作之后,程序可以如下初始化一个服务加载器:
Javapublic static ServiceLoader<Cipher> cipherLoader = ServiceLoader.load(Cipher.class);还可以通过遍历所有的服务实现,以此来选择一个适合的对象来完成服务:
Javapublic static Cipher getCipher(int minStrength) { for (Cipher cipher : cipherLoader) { // implicitly calls cipherLoader. if (cipher.strength() >= minStrength) return cipher; } return null; }1
2
3
4
5
6或者也可以使用流的方式查找所需的服务。
stream方法会生成ServiceLoader.Provider实例的一个流,该接口包含type和get方法。例如,以下示例通过type查找所需的服务,在查找过程中没有实例化任何服务实例:Javapublic static Optional<Cipher> getCipher2(int minStrength) { return cipherLoader.stream() .filter(descr -> descr.type() == serviceLoader.impl.CaesarCipher.class) .findFirst() .map(ServiceLoader.Provider::get); }1
2
3
4
5
6再或者,直接返回任意一个服务:
JavaOptional<Cipher> cipher = cipherLoader.findFirst();
3. 动态代理
3.1. Proxy
可以使用 Proxy 的 newProxyInstance 方法创建一个代理对象,这个方法有三个参数:
ClassLoader:定义代理类的类加载器;Class<?>[]:代理类需要实现的接口列表;InvocationHandler:调用处理器;
下面来看一个示例:
Java
public class ProxyTest {
public static void main(String[] args) {
var elements = new Object[1000];
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
elements[i] = Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Comparable.class},
new TraceHandler(value)
);
}
Integer key = new Random().nextInt(elements.length) + 1;
int result = Arrays.binarySearch(elements, key);
if (result >= 0) System.out.println(elements[result]);
}
}
class TraceHandler implements InvocationHandler {
private Object target;
public TraceHandler(Object t) {
target = t;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
System.out.print(target);
System.out.print("." + m.getName() + "(");
if (args != null) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i < args.length - 1) System.out.print(", ");
}
}
System.out.println(")");
// invoke actual method
return m.invoke(target, args);
}
}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
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
程序将会输出:
Text
500.compareTo(288)
250.compareTo(288)
375.compareTo(288)
312.compareTo(288)
281.compareTo(288)
296.compareTo(288)
288.compareTo(288)
288.toString()1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Note:可以看到,尽管
toString不属于compareTo接口的方法,但是它也被代理了。实际上某些Object的方法总是会被代理。
代理类是在程序运行过程中动态创建的。然而,一旦被创建,它们就变成了常规类,与虚拟机中的任何其他类没有什么区别。所有的代理类都扩展 Proxy 类。一个代理类只有一个实例字段一一即调用处理器,它在 Proxy 超类中定义。完成代理对象任务所需要的任何额外数据都必须存储在调用处理器中。例如,在上述示例程序中,代理 Comparable 对象时,TraceHandler 就包装了实际的对象 target。
所有的代理类都要覆盖 Object 类的 toString、equals 和 hashCode 方法。如同所有代理方法一样,这些方法只是在调用处理器上调用 invoke。Object 类中的其他方法(如 clone 和 getClass)没有重新定义。
对于一个特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次 newProxyInstance 方法,将得到同一个类的两个对象。也可以利用 getProxyClass 方法获得这个类:
Java
Class proxyClass = Proxy.getProxyClass(null, interfaces);代理类总是 public 和 final。如果代理类实现的所有接口都是 public,这个代理类就不属于任何特定的包;否则,所有非公共的接口都必须属于同一个包,同时,代理类也属于这个包。
可以通过调用 Proxy 类的 isProxyClass 方法检测一个特定的 Class 对象是否表示一个代理类。