深入分析Java反射(八)-优化反射调用性能

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

前一篇文章已经介绍了反射调用的底层原理,其实在实际中对大多数Java使用者来说更关系的是如何提升反射调用的性能,本文主要提供几个可行的方案。另外,由于方法调用时频率最高的反射操作,会着重介绍方法的反射调用优化。

方法一:选择合适的API

选择合适的API主要是在获取反射相关元数据的时候尽量避免使用遍历的方法,例如:

  • 获取Field实例:尽量避免频繁使用Class#getDeclaredFields()或者Class#getFields(),应该根据Field的名称直接调用Class#getDeclaredField()或者Class#getField()
  • 获取Method实例:尽量避免频繁使用Class#getDeclaredMethods()或者Class#getMethods(),应该根据Method的名称和参数类型数组调用Class#getDeclaredMethod()或者Class#getMethod()
  • 获取Constructor实例:尽量避免频繁使用Class#getDeclaredConstructors()或者Class#getConstructors(),应该根据Constructor参数类型数组调用Class#getDeclaredConstructor()或者Class#getConstructor()

其实思路很简单,除非我们想要获取Class的所有Field、Method或者Constructor,否则应该避免使用返回一个集合或者数组的API,这样子能减少遍历或者判断带来的性能损耗。

方法二:缓存反射操作相关元数据

使用缓存机制缓存反射操作相关元数据的原因是因为反射操作相关元数据的实时获取是比较耗时的,这里列举几个相对耗时的场景:

  • 获取Class实例:Class#forName(),此方法可以查看源码,耗时相对其他方法高得多。
  • 获取Field实例:Class#getDeclaredField()Class#getDeclaredFields()Class#getField()Class#getFields()
  • 获取Method实例:Class#getDeclaredMethod()Class#getDeclaredMethods()Class#getMethod()Class#getMethods()
  • 获取Constructor实例:Class#getDeclaredConstructor()Class#getDeclaredConstructors()Class#getConstructor()Class#getConstructors()

这里举个简单的例子,需要反射调用一个普通JavaBean的Setter和Getter方法:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// JavaBean
@Data
public class JavaBean {

private String name;
}

public class Main {

private static final Map<Class<?>, List<ReflectionMetadata>> METADATA = new HashMap<>();
private static final Map<String, Class<?>> CLASSES = new HashMap<>();

// 解析的时候尽量放在<cinit>里面
static {
Class<?> clazz = JavaBean.class;
CLASSES.put(clazz.getName(), clazz);
List<ReflectionMetadata> metadataList = new ArrayList<>();
METADATA.put(clazz, metadataList);
try {
for (Field f : clazz.getDeclaredFields()) {
ReflectionMetadata metadata = new ReflectionMetadata();
metadataList.add(metadata);
metadata.setTargetClass(clazz);
metadata.setField(f);
String name = f.getName();
Class<?> type = f.getType();
metadata.setReadMethod(clazz.getDeclaredMethod(String.format("get%s%s", Character.toUpperCase(name.charAt(0)), name.substring(1))));
metadata.setWriteMethod(clazz.getDeclaredMethod(String.format("set%s%s", Character.toUpperCase(name.charAt(0)), name.substring(1)), type));
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}

public static void main(String[] args) throws Exception {
String fieldName = "name";
Class<JavaBean> javaBeanClass = JavaBean.class;
JavaBean javaBean = new JavaBean();
invokeSetter(javaBeanClass, javaBean, fieldName , "Doge");
System.out.println(invokeGetter(javaBeanClass,javaBean, fieldName));
invokeSetter(javaBeanClass.getName(), javaBean, fieldName , "Throwable");
System.out.println(invokeGetter(javaBeanClass.getName(),javaBean, fieldName));
}

private static void invokeSetter(String className, Object target, String fieldName, Object value) throws Exception {
METADATA.get(CLASSES.get(className)).forEach(each -> {
Field field = each.getField();
if (field.getName().equals(fieldName)) {
try {
each.getWriteMethod().invoke(target, value);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
});
}

private static void invokeSetter(Class<?> clazz, Object target, String fieldName, Object value) throws Exception {
METADATA.get(clazz).forEach(each -> {
Field field = each.getField();
if (field.getName().equals(fieldName)) {
try {
each.getWriteMethod().invoke(target, value);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
});
}

private static Object invokeGetter(String className, Object target, String fieldName) throws Exception {
for (ReflectionMetadata metadata : METADATA.get(CLASSES.get(className))) {
if (metadata.getField().getName().equals(fieldName)) {
return metadata.getReadMethod().invoke(target);
}
}
throw new IllegalStateException();
}

private static Object invokeGetter(Class<?> clazz, Object target, String fieldName) throws Exception {
for (ReflectionMetadata metadata : METADATA.get(clazz)) {
if (metadata.getField().getName().equals(fieldName)) {
return metadata.getReadMethod().invoke(target);
}
}
throw new IllegalStateException();
}

@Data
private static class ReflectionMetadata {

private Class<?> targetClass;
private Field field;
private Method readMethod;
private Method writeMethod;
}
}

简单来说,解析反射元数据进行缓存的操作最好放在静态代码块或者首次调用的时候(也就是懒加载),这样能够避免真正调用的时候总是需要重新加载一次反射相关元数据。

方法三:反射操作转变为直接调用

"反射操作转变为直接调用"并不是完全不依赖于反射的类库,这里的做法是把反射操作相关元数据直接放置在类的成员变量中,这样就能省去从缓存中读取反射相关元数据的消耗,而所谓"直接调用"一般是通过继承或者实现接口实现。有一些高性能的反射类库也会使用一些创新的方法:例如使用成员属性缓存反射相关元数据,并且把方法调用通过数字建立索引[Number->Method]或者建立索引类(像CGLIBFastClass),这种做法在父类或者接口方法比较少的时候会有一定的性能提升,但是实际上性能评估需要从具体的场景通过测试分析结果而不能盲目使用,使用这个思想的类库有CGLIBReflectASM等。"反射操作转变为直接调用"的最典型的实现就是JDK的动态代理,这里翻出之前动态代理那篇文章的例子来说:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// 接口
public interface Simple {

void sayHello(String name);
}
// 接口实现
public class DefaultSimple implements Simple {

@Override
public void sayHello(String name) {
System.out.println(String.format("%s say hello!", name));
}
}
// 场景类
public class Main {

public static void main(String[] args) throws Exception {
Simple simple = new DefaultSimple();
Object target = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Simple.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before say hello...");
method.invoke(simple, args);
System.out.println("After say hello...");
return null;
}
});
Simple proxy = (Simple) target;
proxy.sayHello("throwable");
}
}

// 代理类
public final class $Proxy0 extends Proxy implements Simple {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final void sayHello(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("club.throwable.jdk.sample.reflection.proxy.Simple").getMethod("sayHello", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

这样做的话Simple接口实例虽然最终是通过反射调用sayHello(String var1)方法,但是相关元数据在静态代码块中创建并且已经缓存在类成员属性中,那么反射调用方法的性能已经优化到极致,剩下的都只是Native方法的耗时,这一点使用者在编码层面已经没有办法优化,只能通过升级JVM(JDK)、使用JIT编译器等非编码层面的手段提升反射性能。

小结

本文主要从编码层面分析反射操作一些性能优化的可行经验或者方案,或许有其他更好的优化方案,具体还是需要看使用场景。

(本文完 e-a-20181216 c-2-d)

深入分析Java反射(七)-简述反射调用的底层实现

前提

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

本文主要介绍反射调用的底层实现,当然还没有能力分析JVM的实现,这里只分析到最终Native方法的调用点。底层会依赖到Unsafe类,可以的话可以看下笔者之前写的一篇文章《神奇的魔法类和双刃剑-Unsafe》。

反射调用的底层实现探究

主要考虑下面的情况:

  • 属性操作:java.lang.reflect.Field#set(Object obj, Object value)java.lang.reflect.Field#get(Object obj)
  • 构造器调用:java.lang.reflect.Constructor#newInstance(Object ... initargs)
  • 方法调用:java.lang.reflect.Method#invoke(Object obj, Object... args)

处理属性操作的底层实现

属性操作方法Field#set(Object obj, Object value)Field#get(Object obj)底层都是委托到jdk.internal.reflect.FieldAccessor实现:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public interface FieldAccessor {
/** Matches specification in {@link java.lang.reflect.Field} */
public Object get(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public boolean getBoolean(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public byte getByte(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public char getChar(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public short getShort(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public int getInt(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public long getLong(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public float getFloat(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public double getDouble(Object obj) throws IllegalArgumentException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setBoolean(Object obj, boolean z)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setByte(Object obj, byte b)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setChar(Object obj, char c)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setShort(Object obj, short s)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setInt(Object obj, int i)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setLong(Object obj, long l)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setFloat(Object obj, float f)
throws IllegalArgumentException, IllegalAccessException;

/** Matches specification in {@link java.lang.reflect.Field} */
public void setDouble(Object obj, double d)
throws IllegalArgumentException, IllegalAccessException;
}

FieldAccessor接口有很多的实现,FieldAccessor接口实例是通过jdk.internal.reflect.ReflectionFactory这个工厂构造的:

1
2
3
4
5
6
7
8
9
10
11
12
13
public FieldAccessor newFieldAccessor(Field field, boolean override) {
checkInitted();

Field root = langReflectAccess.getRoot(field);
if (root != null) {
// FieldAccessor will use the root unless the modifiers have
// been overrridden
if (root.getModifiers() == field.getModifiers() || !override) {
field = root;
}
}
return UnsafeFieldAccessorFactory.newFieldAccessor(field, override);
}

最终委托到UnsafeFieldAccessorFactory#newFieldAccessor()

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
class UnsafeFieldAccessorFactory {
static FieldAccessor newFieldAccessor(Field field, boolean override) {
Class<?> type = field.getType();
boolean isStatic = Modifier.isStatic(field.getModifiers());
boolean isFinal = Modifier.isFinal(field.getModifiers());
boolean isVolatile = Modifier.isVolatile(field.getModifiers());
boolean isQualified = isFinal || isVolatile;
boolean isReadOnly = isFinal && (isStatic || !override);
if (isStatic) {
// This code path does not guarantee that the field's
// declaring class has been initialized, but it must be
// before performing reflective operations.
UnsafeFieldAccessorImpl.unsafe.ensureClassInitialized(field.getDeclaringClass());

if (!isQualified) {
if (type == Boolean.TYPE) {
return new UnsafeStaticBooleanFieldAccessorImpl(field);
} else if (type == Byte.TYPE) {
return new UnsafeStaticByteFieldAccessorImpl(field);
} else if (type == Short.TYPE) {
return new UnsafeStaticShortFieldAccessorImpl(field);
} else if (type == Character.TYPE) {
return new UnsafeStaticCharacterFieldAccessorImpl(field);
} else if (type == Integer.TYPE) {
return new UnsafeStaticIntegerFieldAccessorImpl(field);
} else if (type == Long.TYPE) {
return new UnsafeStaticLongFieldAccessorImpl(field);
} else if (type == Float.TYPE) {
return new UnsafeStaticFloatFieldAccessorImpl(field);
} else if (type == Double.TYPE) {
return new UnsafeStaticDoubleFieldAccessorImpl(field);
} else {
return new UnsafeStaticObjectFieldAccessorImpl(field);
}
} else {
if (type == Boolean.TYPE) {
return new UnsafeQualifiedStaticBooleanFieldAccessorImpl(field, isReadOnly);
} else if (type == Byte.TYPE) {
return new UnsafeQualifiedStaticByteFieldAccessorImpl(field, isReadOnly);
} else if (type == Short.TYPE) {
return new UnsafeQualifiedStaticShortFieldAccessorImpl(field, isReadOnly);
} else if (type == Character.TYPE) {
return new UnsafeQualifiedStaticCharacterFieldAccessorImpl(field, isReadOnly);
} else if (type == Integer.TYPE) {
return new UnsafeQualifiedStaticIntegerFieldAccessorImpl(field, isReadOnly);
} else if (type == Long.TYPE) {
return new UnsafeQualifiedStaticLongFieldAccessorImpl(field, isReadOnly);
} else if (type == Float.TYPE) {
return new UnsafeQualifiedStaticFloatFieldAccessorImpl(field, isReadOnly);
} else if (type == Double.TYPE) {
return new UnsafeQualifiedStaticDoubleFieldAccessorImpl(field, isReadOnly);
} else {
return new UnsafeQualifiedStaticObjectFieldAccessorImpl(field, isReadOnly);
}
}
} else {
if (!isQualified) {
if (type == Boolean.TYPE) {
return new UnsafeBooleanFieldAccessorImpl(field);
} else if (type == Byte.TYPE) {
return new UnsafeByteFieldAccessorImpl(field);
} else if (type == Short.TYPE) {
return new UnsafeShortFieldAccessorImpl(field);
} else if (type == Character.TYPE) {
return new UnsafeCharacterFieldAccessorImpl(field);
} else if (type == Integer.TYPE) {
return new UnsafeIntegerFieldAccessorImpl(field);
} else if (type == Long.TYPE) {
return new UnsafeLongFieldAccessorImpl(field);
} else if (type == Float.TYPE) {
return new UnsafeFloatFieldAccessorImpl(field);
} else if (type == Double.TYPE) {
return new UnsafeDoubleFieldAccessorImpl(field);
} else {
return new UnsafeObjectFieldAccessorImpl(field);
}
} else {
if (type == Boolean.TYPE) {
return new UnsafeQualifiedBooleanFieldAccessorImpl(field, isReadOnly);
} else if (type == Byte.TYPE) {
return new UnsafeQualifiedByteFieldAccessorImpl(field, isReadOnly);
} else if (type == Short.TYPE) {
return new UnsafeQualifiedShortFieldAccessorImpl(field, isReadOnly);
} else if (type == Character.TYPE) {
return new UnsafeQualifiedCharacterFieldAccessorImpl(field, isReadOnly);
} else if (type == Integer.TYPE) {
return new UnsafeQualifiedIntegerFieldAccessorImpl(field, isReadOnly);
} else if (type == Long.TYPE) {
return new UnsafeQualifiedLongFieldAccessorImpl(field, isReadOnly);
} else if (type == Float.TYPE) {
return new UnsafeQualifiedFloatFieldAccessorImpl(field, isReadOnly);
} else if (type == Double.TYPE) {
return new UnsafeQualifiedDoubleFieldAccessorImpl(field, isReadOnly);
} else {
return new UnsafeQualifiedObjectFieldAccessorImpl(field, isReadOnly);
}
}
}
}
}

这里注意一下属性修饰符的判断:

  • isStatic:静态属性,也就是static关键字修饰的属性。
  • isFinal:final关键字修饰的属性。
  • isVolatile:valatile关键字修饰的属性。
  • isQualified:valatile关键字或者final关键字修饰的属性。
  • isReadOnly:是否只读属性,final关键字修饰的属性或者static关键字修饰并且不能覆盖(override = false)的属性。

通过上面修饰符做判断,得到最终的FieldAccessor实现。这里挑一个例子进行分析,例如一个普通非静态没有volatile和final关键字修饰属性最终就会得到UnsafeObjectFieldAccessorImpl的实例:

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
class UnsafeObjectFieldAccessorImpl extends UnsafeFieldAccessorImpl {
UnsafeObjectFieldAccessorImpl(Field field) {
super(field);
}

public Object get(Object obj) throws IllegalArgumentException {
ensureObj(obj);
return unsafe.getObject(obj, fieldOffset);
}

public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException{
ensureObj(obj);
if (isFinal) {
throwFinalFieldIllegalAccessException(value);
}
if (value != null) {
if (!field.getType().isAssignableFrom(value.getClass())) {
throwSetIllegalArgumentException(value);
}
}
unsafe.putObject(obj, fieldOffset, value);
}

public boolean getBoolean(Object obj) throws IllegalArgumentException {
throw newGetBooleanIllegalArgumentException();
}

public byte getByte(Object obj) throws IllegalArgumentException {
throw newGetByteIllegalArgumentException();
}

// 省略其他直接抛出异常的方法
}

可见UnsafeObjectFieldAccessorImpl中除了get(Object obj)set(Object obj, Object value)方法,其他方法都是直接抛出IllegalArgumentException。而get(Object obj)set(Object obj, Object value)底层分别依赖于jdk.internal.misc.UnsafeputObject(obj, fieldOffset, value)getObject(obj, fieldOffset)方法。而属性的内存偏移地址是在UnsafeObjectFieldAccessorImpl的父类UnsafeFieldAccessorImpl的构造函数中计算出来的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class UnsafeFieldAccessorImpl extends FieldAccessorImpl {
static final Unsafe unsafe = Unsafe.getUnsafe();

protected final Field field;
protected final long fieldOffset;
protected final boolean isFinal;

UnsafeFieldAccessorImpl(Field field) {
this.field = field;
if (Modifier.isStatic(field.getModifiers()))
fieldOffset = unsafe.staticFieldOffset(field);
else
fieldOffset = unsafe.objectFieldOffset(field);
isFinal = Modifier.isFinal(field.getModifiers());
}
// 省略其他方法
}

这里可以做个小结,属性反射操作FieldsetXXgetXX方法最终委托到jdk.internal.misc.UnsafeputXXgetXX方法,而属性的内存偏移地址是通过jdk.internal.misc.UnsafestaticFieldBase()staticFieldOffsetobjectFieldOffset几个方法计算的。

处理构造器调用的底层实现

Constructor#newInstance()方法调用依赖到ConstructorAccessor

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
    public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, clazz, 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;
}

// ConstructorAccessor接口
public interface ConstructorAccessor {
/** Matches specification in {@link java.lang.reflect.Constructor} */
public Object newInstance(Object[] args)
throws InstantiationException,
IllegalArgumentException,
InvocationTargetException;
}

而获取ConstructorAccessor实例也是通过反射工厂类ReflectionFactory,具体是ReflectionFactory#newConstructorAccessor

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
public ConstructorAccessor newConstructorAccessor(Constructor<?> c) {
checkInitted();
Class<?> declaringClass = c.getDeclaringClass();
// 抽象方法会进入此if分支
if (Modifier.isAbstract(declaringClass.getModifiers())) {
return new InstantiationExceptionConstructorAccessorImpl(null);
}
// 宿主类直接是Class类型,则无法实例化
if (declaringClass == Class.class) {
return new InstantiationExceptionConstructorAccessorImpl
("Can not instantiate java.lang.Class");
}

// use the root Constructor that will not cache caller class
Constructor<?> root = langReflectAccess.getRoot(c);
if (root != null) {
c = root;
}

// 当前声明构造的宿主类是ConstructorAccessorImpl的子类
if (Reflection.isSubclassOf(declaringClass,
ConstructorAccessorImpl.class)) {
return new BootstrapConstructorAccessorImpl(c);
}
//
if (noInflation && !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) {
return new MethodAccessorGenerator().
generateConstructor(c.getDeclaringClass(),
c.getParameterTypes(),
c.getExceptionTypes(),
c.getModifiers());
} else {
NativeConstructorAccessorImpl acc =
new NativeConstructorAccessorImpl(c);
DelegatingConstructorAccessorImpl res =
new DelegatingConstructorAccessorImpl(acc);
acc.setParent(res);
return res;
}
}

可见最终得到的ConstructorAccessor实例为DelegatingConstructorAccessorImpl,而DelegatingConstructorAccessorImpl只是一个委托实现,底层是调用NativeConstructorAccessorImpl

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
class NativeConstructorAccessorImpl extends ConstructorAccessorImpl {
private final Constructor<?> c;
private DelegatingConstructorAccessorImpl parent;
private int numInvocations;

NativeConstructorAccessorImpl(Constructor<?> c) {
this.c = c;
}

public Object newInstance(Object[] args)
throws InstantiationException,
IllegalArgumentException,
InvocationTargetException
{
// We can't inflate a constructor belonging to a vm-anonymous class
// because that kind of class can't be referred to by name, hence can't
// be found from the generated bytecode.
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) {
ConstructorAccessorImpl acc = (ConstructorAccessorImpl)
new MethodAccessorGenerator().
generateConstructor(c.getDeclaringClass(),
c.getParameterTypes(),
c.getExceptionTypes(),
c.getModifiers());
parent.setDelegate(acc);
}

return newInstance0(c, args);
}

void setParent(DelegatingConstructorAccessorImpl parent) {
this.parent = parent;
}
// 这个就是最终构造实例化对象的native方法
private static native Object newInstance0(Constructor<?> c, Object[] args)
throws InstantiationException,
IllegalArgumentException,
InvocationTargetException;
}

NativeConstructorAccessorImpl#newInstance0()就是最终构造实例化对象的Native方法。当然有例外的情况,例如非正常调用下,如果构造器的宿主类是一个抽象类,那么最终会返回一个InstantiationExceptionConstructorAccessorImpl实例,里面直接抛出InstantiationException异常。

处理方法调用的底层实现

Method#invoke()调用依赖于MethodAccessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// MethodAccessor接口
public interface MethodAccessor {
/** Matches specification in {@link java.lang.reflect.Method} */
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}

public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}

获取MethodAccessor实例的逻辑和前两节类似,是通过ReflectionFactory#newMethodAccessor()

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
public MethodAccessor newMethodAccessor(Method method) {
checkInitted();

if (Reflection.isCallerSensitive(method)) {
Method altMethod = findMethodForReflection(method);
if (altMethod != null) {
method = altMethod;
}
}

// use the root Method that will not cache caller class
Method root = langReflectAccess.getRoot(method);
if (root != null) {
method = root;
}

if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}

最终会委托到NativeMethodAccessorImpl#invoke(Object obj, Object[] 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
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;

NativeMethodAccessorImpl(Method method) {
this.method = method;
}

public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
// We can't inflate methods belonging to vm-anonymous classes because
// that kind of class can't be referred to by name, hence can't be
// found from the generated bytecode.
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}

return invoke0(method, obj, args);
}

void setParent(DelegatingMethodAccessorImpl parent) {
this.parent = parent;
}

private static native Object invoke0(Method m, Object obj, Object[] args);
}

NativeMethodAccessorImpl#invoke0()就是方法调用的最终调用的Native方法。

小结

学习知识过程总是阶梯式上升的,JDK中的类库设计也类似这样,如果提前熟悉Unsafe类的相关方法,其实反射调用的底层实现也能够相对轻易地理解。属性、构造和方法反射调用底层的实现(只考虑正常调用的情况下)如下:

  • 对于属性(Field):Field#setXX()Field#getXX()分别对应UnsafeputXX()getXX()方法,也就是说完全依赖Unsafe中的Native方法。
  • 对于构造(Constructor):Constructor#newInstance()底层调用NativeConstructorAccessorImpl#newInstance0()
  • 对于方法(Method):Method#invoke()底层调用NativeMethodAccessorImpl#invoke0()

(本文完 e-a-20181216 c-1-d)

深入分析Java反射(六)-反射调用异常处理

前提

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

本文主要介绍一个使用反射一定会遇到的问题-反射调用异常处理。

反射调用异常处理

反射调用出现异常的方法主要考虑下面的情况:

  • 属性操作:java.lang.reflect.Field#set(Object obj, Object value)java.lang.reflect.Field#get(Object obj)
  • 构造器调用:java.lang.reflect.Constructor#newInstance(Object ... initargs)
  • 方法调用:java.lang.reflect.Method#invoke(Object obj, Object... args)

处理属性操作异常

先看设置属性的方法:

1
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException

实际上,通过方法注释可以得知会抛出四种异常:

  • IllegalAccessException:非法访问异常,注意它是检查(checked)异常,也就是需要显示捕获,此异常会在修饰符禁用访问的时候抛出,可以通过setAccessible(true)抑制修饰符检查来避免抛出此异常。
  • IllegalArgumentException:非法参数异常,它是运行时异常,当入参实例obj不是当前Field所在类(包括父类、子类和接口)的时候会抛出此异常。
  • NullPointerException:空指针异常,当入参实例obj为null的时候会抛出此异常。
  • ExceptionInInitializerError:初始化器调用异常导致的错误,如果由于set(Object obj, Object value)方法引发的初始化失败会包装成ExceptionInInitializerError,此异常的父类为Error,常见的发生情况就是静态成员或者静态代码块依赖到反射属性设置。

前面三种异常都很好理解,最后一个ExceptionInInitializerError可能有点陌生,它的抛出条件是:在静态代码块初始化解析过程总抛出异常或者静态变量初始化的时候抛出异常。笔者尝试了很多例子都没办法造出案例,从Stackoverflow找到一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Example {
public static void main(String[] args) throws Exception {
Field field = Fail.class.getDeclaredField("number");
field.set(null, 42); // Fail class isn't initialized at this point
}
}

class Fail {
static int number;
static {
boolean val = true;
if (val)
throw new RuntimeException(); // causes initialization to end with an exception
}
}

简单来说就是:静态代码块和静态变量的初始化顺序和它们在类文件编写的顺序是一致的,如果一个类未初始化直接使用它的静态代码块和静态变量通过Field#set(Object obj, Object value)调用就会出现ExceptionInInitializerError异常。

属性的获取方法抛出的异常和设置值方法是一致的,这里不做详细展开:

1
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException

处理构造器调用异常

构造器调用主要是用于对象的实例化,先看newInstance方法的签名:

1
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
  • InstantiationException:实例化异常,抛出此异常的一般情况是:当前构造所在类型为一个抽象类型。
  • IllegalAccessException:非法访问异常。
  • IllegalArgumentException:非法参数异常,下面的情况会抛出此异常:参数数量或者类型不匹配,参数列表为原始类型但是实际使用了包装类型、参数列表为原始类型但是实际使用了包装类型、构造所在的类是枚举类型等。
  • InvocationTargetException:目标调用异常,这个是需要处理的重点异常,在下一节"处理方法调用异常"详细探讨。

这里只举个例子说明一下InstantiationException出现的场景:

1
2
3
4
5
6
7
8
9
10
public abstract class AbstractSample {

public AbstractSample() {
}

public static void main(String[] args) throws Exception{
Constructor<AbstractSample> declaredConstructor = AbstractSample.class.getDeclaredConstructor();
declaredConstructor.newInstance();
}
}

像上面的抽象类AbstractSample包含一个默认的公有构造,使用Constructor#newInstance()会抛出InstantiationException异常:

1
2
3
4
Exception in thread "main" java.lang.InstantiationException
at java.base/jdk.internal.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at club.throwable.jdk.sample.reflection.reflect.AbstractSample.main(AbstractSample.java:18)

处理方法调用异常

方法调用是反射中使用频率最高的反射操作,主要是Method#invoke(Object obj, Object... args)方法:

1
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

主要包括以下几种异常:

  • IllegalAccessException:非法访问异常。
  • IllegalArgumentException:非法参数异常,下面的情况会抛出此异常:入参obj并不是当前实例方法对应的实例对象、参数数量或者类型不匹配,参数列表为原始类型但是实际使用了包装类型、参数列表为原始类型但是实际使用了包装类型等等。
  • NullPointerException:空指针异常,入参obj为null时候会抛出此异常。
  • ExceptionInInitializerError:初始化器调用异常导致的错误。
  • InvocationTargetException:目标调用异常。

重点看InvocationTargetException(继承自ReflectiveOperationException,而ReflectiveOperationException继承自Exception,也就是它是checked异常,必须显式捕获):

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
public class InvocationTargetException extends ReflectiveOperationException {

private static final long serialVersionUID = 4085088731926701167L;

// 持有的目标异常实例
private Throwable target;

public InvocationTargetException(Throwable target) {
super((Throwable)null); // Disallow initCause
this.target = target;
}

public InvocationTargetException(Throwable target) {
super((Throwable)null); // Disallow initCause
this.target = target;
}

public Throwable getTargetException() {
return target;
}

public Throwable getCause() {
return target;
}
}

从注释中得知:方法(Method)或者构造(Constructor)调用异常会抛出此InvocationTargetException异常,用于包装源异常,源异常实例作为目标被InvocationTargetException通过成员target持有,可以通过InvocationTargetException#getTargetException()或者InvocationTargetException#getCause()获取原始的目标异常。这里注意到,InvocationTargetException在覆盖父类构造的时候使用了null,所以调用其getMessage()方法会得到null。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class InvocationTargetExceptionMain {

public void method() {
throw new NullPointerException("Null");
}

public static void main(String[] args) throws NoSuchMethodException, SecurityException {
InvocationTargetExceptionMain main = new InvocationTargetExceptionMain();
Method method = InvocationTargetExceptionMain.class.getDeclaredMethod("method");
try {
method.invoke(main);
} catch (IllegalAccessException e) {
//no-op
} catch (InvocationTargetException e) {
System.out.println("InvocationTargetException#message:" + e.getMessage());
if (e.getTargetException() instanceof NullPointerException) {
NullPointerException nullPointerException = (NullPointerException) e.getTargetException();
System.out.println("NullPointerException#message:" + nullPointerException.getMessage());
}
}
}
}

运行后输出:

1
2
InvocationTargetException#message:null
NullPointerException#message:Null

构造器Constructor#newInstance()中抛出InvocationTargetException的场景是类似的。

小结

在反射操作中,方法调用的频次是最高的,其次是通过构造器实例化对象。需要重点关注这两个地方的异常处理,特别是异常类型InvocationTargetException,紧记需要获取原始目标异常类型再进行判断,否则很容易导致逻辑错误(最近笔者在做一个功能的时候刚好踩了这个坑)。

(本文完 e-a-20181215 c-2-d)

深入分析Java反射(五)-类实例化和类加载

前提

其实在前面写过的《深入分析Java反射(一)-核心类库和方法》已经介绍过通过类名或者java.lang.Class实例去实例化一个对象,在《浅析Java中的资源加载》中也比较详细地介绍过类加载过程中的双亲委派模型,这篇文章主要是加深一些对类实例化和类加载的认识。

类实例化

在反射类库中,用于实例化对象只有两个方法:

  • T java.lang.Class#newInstance():这个方法只需要提供java.lang.Class<T>的实例就可以实例化对象,如果提供的是无限定类型Class<?>则得到的是Object类型的返回值,可以进行强转。这个方法不支持任何入参,底层实际上也是依赖无参数的构造器Constructor进行实例化。
  • T java.lang.reflect.Constructor#newInstance(Object ... initargs):这个方法需要提供java.lang.reflect.Constructor<T>实例和一个可变参数数组进行对象的实例化,上面提到的T java.lang.Class#newInstance()底层也是依赖此方法。这个方法除了可以传入构造参数之外,还有一个好处就是可以通过``抑制修饰符访问权限检查,也就是私有的构造器也可以用于实例化对象。

在编写反射类库的时候,优先选择T java.lang.reflect.Constructor#newInstance(Object ... initargs)进行对象实例化,目前参考很多优秀的框架(例如Spring)都是用这个方法进行对象实例化。

类加载

类加载实际上由类加载器(ClassLoader)完成,protected Class<?> java.lang.ClassLoader#loadClass(String name, boolean resolve)方法提现了类加载过程中遵循了双亲委派模型,实际上,我们可以覆写此方法完全不遵循双亲委派模型,实现同一个类(这里指的是全类名完全相同)重新加载。JDK中提供类加载相关的特性有两个方法:

  • protected Class<?> java.lang.ClassLoader#loadClass(String name, boolean resolve):通过类加载器实例去加载类,一般应用类路径下的类是由jdk.internal.loader.ClassLoaders$AppClassLoader加载,也可以自行继承java.lang.ClassLoader实现自己的类加载器。
  • public static Class<?> forName(String name, boolean initialize, ClassLoader loader):通过全类名进行类加载,可以通过参数控制类初始化行为。

ClassLoader中的类加载

类加载过程其实是一个很复杂的过程,主要包括下面的步骤:

  • 1、加载过程:使用(自定义)类加载器去获取类文件字节码字节类的过程,Class实例在这一步生成,作为方法区的各种数据类型的访问入口。
  • 2、验证过程:JVM验证字节码的合法性。
  • 3、准备过程:为类变量分配内存并且设置初始值。
  • 4、解析过程:JVM把常量池中的符号替换为直接引用。
  • 5、初始化过程:执行类构造器<cinit>()方法,<cinit>()方法是编译器自动收集所有类变量的赋值动作和静态代码块中的语句合并生成,收集顺序由语句在源文件中出现的顺序决定,JVM保证在子类<cinit>()方法调用前父类的<cinit>()方法已经执行完毕。

ClassLoader#loadClass()方法就是用于控制类加载过程的第一步-加载过程,也就是控制字节码字节数组和类名生成Class实例的过程。ClassLoader中还有一个protected final Class<?> defineClass(String name, byte[] b, int off, int len)方法用于指定全类名和字节码字节数组去定义一个类,我们再次看下loadClass()的源码:

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
42
43
44
45
46
47
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// 检查类是否已经加载过,如果已经加载过,则直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
// 委派父类加载器去加载类
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 委派父类加载器如果加载失败则调用findClass方法进行加载动作
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
// 扩展点-1
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
// 扩展点-2
protected final void resolveClass(Class<?> c) {
if (c == null) {
throw new NullPointerException();
}
}

实际上,loadClass()方法留下了两个扩展点用于改变类加载的行为,而findClass()方法就是用于扩展父类加载器加载失败的情况下,子类加载器的行为。当然,实际上Class<?> loadClass(String name, boolean resolve)方法是非final的方法,可以整个方法覆写掉,这样子就有办法完全打破双亲委派机制。但是注意一点,即使打破双亲委派机制,子类加载器也不可能重新加载一些由Bootstrap类加载器加载的类库如java.lang.String,这些是由JVM验证和保证的。自定义类加载器的使用在下一节的"类重新加载"中详细展开。

最后还有两点十分重要:

  • 1、对于任意一个类,都需要由加载它的类加载器和这个类本身一起确立其在Java虚拟机中的唯一性,也就是一个类在JVM中的签名是加载它的类加载器和它本身,对于每一个类加载器,都拥有一个独立的类命名空间
  • 2、比较两个类是否"相等",只有这两个类是由同一个类加载器加载的前提下才有意义。即使这两个类的全类名一致、来源于同一个字节码文件、被同一个Java虚拟机加载,但是加载它们的类加载器不同,那么它们必定不相等。这里相等的范畴包括:Class对象的equals()方法、isAssignableForm()方法、isInstance()方法的返回结果以及使用instanceof关键字做对象所属关系时候的判定等情况。

Class中的类加载

java.lang.Class中的类加载主要由public static Class<?> forName(String name, boolean initialize, ClassLoader loader)方法完成,该方法可以指定全类名、是否初始化和类加载器实例。源码如下:

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
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
caller = Reflection.getCallerClass();
if (loader == null) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}

private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller) throws ClassNotFoundException;

它最终调用的是JVM的本地接口方法,由于暂时没有能力分析JVM的源码,只能通过forName方法的注释理解方法的功能:

返回给定字符串全限定名称、指定类加载器的类或者接口的Class实例,此方法会尝试对类或者接口进行locate、load and link操作,如果loader参数为null,则使用bootstrap类加载器进行加载,如果initialize参数为true同时类或者接口在早期没有被初始化,则会进行初始化操作。

也就是说initialize参数对于已经初始化过的类或者接口来说是没有意义的。这个方法的特性还可以参考Java语言规范的12章中的内容,这里不做展开。

虽然暂时没法分析JVM本地接口方法native Class<?> forName0()的功能,但是它依赖一个类加载器实例入参,可以大胆猜测它也是依赖于类加载器的loadClass()进行类加载的。

类重新加载

先提出一个实验,如果定义一个类如下:

1
2
3
4
5
6
public class Sample {

public void say() {
System.out.println("Hello Doge!");
}
}

如果使用字节码工具修改say()方法的内容为System.out.println("Hello Throwable!");,并且使用自定义的ClassLoader重新加载一个同类名的Sample类,那么通过new关键字实例化出来的Sample对象调用say()到底打印"Hello Doge!“还是"Hello Throwable!”?

先引入字节码工具javassist用于修改类的字节码:

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.0-GA</version>
</dependency>

下面是测试代码:

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
42
43
44
45
46
47
// 例子
public class Demo {

public void say() {
System.out.println("Hello Doge!");
}
}

// 一次性使用的自定义类加载器
public class CustomClassLoader extends ClassLoader {

private final byte[] data;

public CustomClassLoader(byte[] data) {
this.data = data;
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (!Demo.class.getName().equals(name)) {
return super.loadClass(name);
}
return defineClass(name, data, 0, data.length);
}
}

public class Main {

public static void main(String[] args) throws Exception {

String name = Demo.class.getName();
CtClass ctClass = ClassPool.getDefault().getCtClass(name);
CtMethod method = ctClass.getMethod("say", "()V");
method.setBody("{System.out.println(\"Hello Throwable!\");}");
byte[] bytes = ctClass.toBytecode();
CustomClassLoader classLoader = new CustomClassLoader(bytes);
// 新的Demo类,只能反射调用,因为类路径中的Demo类已经被应用类加载器加载
Class<?> newDemoClass = classLoader.loadClass(name);
// 类路径中的Demo类
Demo demo = new Demo();
demo.say();
// 新的Demo类
newDemoClass.getDeclaredMethod("say").invoke(newDemoClass.newInstance());
// 比较
System.out.println(newDemoClass.equals(Demo.class));
}
}

执行后输出:

1
2
3
Hello Doge!
Hello Throwable!
false

这里得出的结论是:

  • new关键字只能使用在当前类路径下的类的实例化,而这些类都是由应用类加载器加载,如果上面的例子中newDemoClass.newInstance()强制转换为Demo类型会报错。
  • 通过自定义类加载器加载的和当前类路径相同名全类名的类只能通过反射去使用,而且即使全类名相同,由于类加载器隔离,它们其实是不相同的类。

如何避免类重新加载导致内存溢出

实际上,JDK没有提供方法去卸载一个已经加载的类,也就是类的生命周期是由JVM管理的,因此要解决类重新加载导致内存溢出的问题归根结底就是解决重新加载的类被回收的问题。由于创建出来是的java.lang.Class对象,如果需要回收它,则要考虑下面几点:

  • 1、java.lang.Class对象反射创建的实例需要被回收。
  • 2、java.lang.Class对象不能被任何地方强引用。
  • 3、加载java.lang.Class对象的ClassLoder已经被回收。

基于这几点考虑可以做个试验验证一下:

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
42
43
44
45
public class Demo {
// 这里故意建立一个数组占用大量内存
private int[] array = new int[1000];

public void say() {
System.out.println("Hello Doge!");
}
}

public class Main {

private static final Map<ClassLoader, List<Class<?>>> CACHE = new HashMap<>();

public static void main(String[] args) throws Exception {
String name = Demo.class.getName();
CtClass ctClass = ClassPool.getDefault().getCtClass(name);
CtMethod method = ctClass.getMethod("say", "()V");
method.setBody("{System.out.println(\"Hello Throwable!\");}");
for (int i = 0; i < 100000; i++) {
byte[] bytes = ctClass.toBytecode();
CustomClassLoader classLoader = new CustomClassLoader(bytes);
// 新的Demo类,只能反射调用,因为类路径中的Demo类已经被应用类加载器加载
Class<?> newDemoClass = classLoader.loadClass(name);
add(classLoader, newDemoClass);
}
// 清理类加载器和它加载过的类
clear();
System.gc();
Thread.sleep(Integer.MAX_VALUE);
}

private static void add(ClassLoader classLoader, Class<?> clazz) {
if (CACHE.containsKey(classLoader)) {
CACHE.get(classLoader).add(clazz);
} else {
List<Class<?>> classes = new ArrayList<>();
CACHE.put(classLoader, classes);
classes.add(clazz);
}
}

private static void clear() {
CACHE.clear();
}
}

使用VM参数-XX:+PrintGC -XX:+PrintGCDetails执行上面的方法,JDK11默认使用G1收集器,由于Z收集器还在实验阶段,不是很建议使用,执行main方法后输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[11.374s][info   ][gc,task       ] GC(17) Using 8 workers of 8 for full compaction
[11.374s][info ][gc,start ] GC(17) Pause Full (System.gc())
[11.374s][info ][gc,phases,start] GC(17) Phase 1: Mark live objects
[11.429s][info ][gc,stringtable ] GC(17) Cleaned string and symbol table, strings: 5637 processed, 0 removed, symbols: 135915 processed, 0 removed
[11.429s][info ][gc,phases ] GC(17) Phase 1: Mark live objects 54.378ms
[11.429s][info ][gc,phases,start] GC(17) Phase 2: Prepare for compaction
[11.429s][info ][gc,phases ] GC(17) Phase 2: Prepare for compaction 0.422ms
[11.429s][info ][gc,phases,start] GC(17) Phase 3: Adjust pointers
[11.430s][info ][gc,phases ] GC(17) Phase 3: Adjust pointers 0.598ms
[11.430s][info ][gc,phases,start] GC(17) Phase 4: Compact heap
[11.430s][info ][gc,phases ] GC(17) Phase 4: Compact heap 0.362ms
[11.648s][info ][gc,heap ] GC(17) Eden regions: 44->0(9)
[11.648s][info ][gc,heap ] GC(17) Survivor regions: 12->0(12)
[11.648s][info ][gc,heap ] GC(17) Old regions: 146->7
[11.648s][info ][gc,heap ] GC(17) Humongous regions: 3->2
[11.648s][info ][gc,metaspace ] GC(17) Metaspace: 141897K->9084K(1062912K)
[11.648s][info ][gc ] GC(17) Pause Full (System.gc()) 205M->3M(30M) 273.440ms
[11.648s][info ][gc,cpu ] GC(17) User=0.31s Sys=0.08s Real=0.27s

可见FullGC之后,元空间(Metaspace)回收了(141897-9084)KB,一共回收了202M的内存空间,初步可以认为元空间的内存被回收了,接下来注释掉main方法中调用的clear()方法,再调用一次main方法:

1
2
3
4
5
....
[4.083s][info ][gc,heap ] GC(17) Humongous regions: 3->2
[4.083s][info ][gc,metaspace ] GC(17) Metaspace: 141884K->141884K(1458176K)
[4.083s][info ][gc ] GC(17) Pause Full (System.gc()) 201M->166M(564M) 115.504ms
[4.083s][info ][gc,cpu ] GC(17) User=0.84s Sys=0.00s Real=0.12s

可见元空间在FullGC执行没有进行回收,而堆内存的回收率也比较低,由此可以得出一个经验性的结论:只需要通过ClassLoader对象做映射关系保存使用它加载出来的新的类,只需要确保这些类没有没强引用、类实例都已经销毁,那么只需要移除ClassLoader对象的引用,那么在JVM进行GC的时候会把ClassLoader对象以及使用它加载的类回收,这样做就可以避免元空间的内存泄漏。

小结

通过一些资料和实验,深化了类加载过程的一些认识。

参考资料:

  • 《深入理解Java虚拟机-第二版》
  • JDK11部分源码

(本文完 e-2018129 c-2-d r-20181212)

深入分析Java反射(四)-动态代理

动态代理的简介

Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。Java动态代理实际上通过反射技术,把代理对象和被代理对象(真实对象)的代理关系建立延迟到程序运行之后,动态创建新的代理类去完成对真实对象的代理操作(可以改变原来真实对象的方法行为),这一点成为了当前主流的AOP框架和延迟加载功能的基础。本文在查看和编写动态代理相关的代码使用的是JDK11,不过JDK动态代理相关的功能和接口已经相对稳定,不必担心JDK版本升级带来的兼容性问题,但是需要注意由于JDK9引入了模块概念,动态代理的源码也有不少的改动。下文先介绍设计模式中的代理模式,接着会分析JDK动态代理的核心类库、流程和机制,最后分析其底层源码级别实现。

设计模式中的代理模式

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

j-r-d-p-2.png

代理模式主要包括三种角色:

  • Subject抽象主题角色:一般定义为抽象类或者接口,是作为功能的定义,提供一系列抽象的功能方法。
  • RealSubject具体(真实)主题角色:一般称为被委托角色或者被代理角色,它是Subject的一个具体实现。
  • ProxySubject代理主题角色:一般称为委托角色或者代理角色,一般ProxySubject也实现(或者继承)Subject,接收一个具体的Subject实例RealSubject,在RealSubject处理前后做预定义或者后置操作,甚至可以直接忽略RealSubject原来的方法。

把上面的类图编写成代码如下:

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
public interface Subject {

void doSomething();
}

public class RealSubject implements Subject {

@Override
public void doSomething() {
System.out.println("RealSubject doSomething...");
}
}

public class ProxySubject implements Subject {

private final Subject subject;

public ProxySubject(Subject subject) {
this.subject = subject;
}

@Override
public void doSomething() {
subject.doSomething();
doOtherThing();
}

private void doOtherThing() {
System.out.println("ProxySubject doOtherThing...");
}
}

public class Client {

public static void main(String[] args) throws Exception {
Subject subject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(subject);
proxySubject.doSomething();
}
}

运行Client#main()输出:

1
2
RealSubject doSomething...
ProxySubject doOtherThing...

代理模式在日常的场景中也经常碰到,比较常见的一个场景就是游戏代练,套进去上面的代码可以写个比较生动的例子:

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
public interface Player {

void playGame();
}

public class I implements Player {

@Override
public void playGame() {
System.out.println("操作Throwable游戏角色打怪升级");
}
}

public class ProxyPlayer implements Player {

private final Player player;

public ProxyPlayer(Player player) {
this.player = player;
}

@Override
public void playGame() {
login();
this.player.playGame();
logout();
}

private void login() {
System.out.println("登录Throwable游戏角色");
}

private void logout() {
System.out.println("退出Throwable游戏角色");
}
}

代理模式有几个比较大的优点:

  • 职责清晰:也就是真实主题角色只需要实现具体的逻辑,不需关注代理类的职责,而代理类也只需要处理预处理和后置的逻辑,类的职责分明。
  • 高扩展性:由于职责分明,也就是真实主题角色可以随时修改实现,这样就能通过更新或者替换真实主题的实现并且不改变代理主题角色的情况下改变具体功能。
  • 高灵活性:主要体现在后面提到的动态代理。

JDK动态代理的核心API

JDK动态代理提供外部使用的主要依赖两个类:

  • java.lang.reflect.Proxy:可以理解为代理类的工厂类(其实也是父类,见下文)。
  • java.lang.reflect.InvocationHandler:代理实例需要实现的调用处理器接口。

Proxy

java.lang.reflect.Proxy是JDK动态代理的核心类,它的核心功能是提供静态方法来为一组接口动态地生成代理类并且返回代理实例对象,类似于代理类实例的工厂类。java.lang.reflect.Proxy主要提供四个public静态方法:

1
2
3
4
5
6
7
8
9
10
11
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy)

// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces)

// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl)

// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • InvocationHandler getInvocationHandler(Object proxy):通过制定的代理类实例查找它关联的调用处理器实例。
  • Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces):用于获取关联于指定类装载器和一组接口的动态代理类的类对象,也就是获取$ProxyXXX的类型,此方法在JDK9以后标记为过期,原因是:在命名模块中生成的代理类是封闭的,模块外的代码无法访问这些类(违反模块规则调用了会抛异常)。
  • boolean isProxyClass(Class<?> cl):用于判断指定类是否是一个动态代理类。
  • Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):这个是JDK动态代理最核心的方法,用于为指定类装载器、一组接口及调用处理器生成动态代理类实例,也就是生成$ProxyXXX的实例。此方法需要指定类加载器java.lang.ClassLoader,Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是在运行时动态生成的而非预存在于任何一个.class文件中。interfaces是Class数组,也就是需要使用InvocationHandler进行代理访问的接口类型数组,这里的h参数就是调用处理器的实例。

InvocationHandler

java.lang.reflect.InvocationHandler是调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

1
2
3
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

参数说明:

  • proxy:Object类型,此参数即是代理类实例,也就是$ProxyXXX的实例。
  • method:java.lang.reflect.Method类型,被调用的方法的实例。
  • args:Object[]类型,被调用方法的参数数组。

实现java.lang.reflect.InvocationHandler接口,通过实现invoke方法即可添加代理访问的逻辑,在这个逻辑代码块中除了可以调用委托类的方法,还可以织入额外的自定义逻辑,AOP就是这样实现的。

JDK动态代理的流程

JDK动态代理的使用流程如下:

  • 1、通过实现java.lang.reflect.InvocationHandler接口创建自定义的调用处理器。
  • 2、通过为java.lang.reflect.Proxy类指定ClassLoader对象和一组interface来创建动态代理类。
  • 3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型。
  • 4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..);

// 通过Proxy为包括Interface接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });

// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });

// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

上面的过程比较复杂,可以进行精简。简化后的伪代码如下:

1
2
3
4
5
// InvocationHandlerImpl实现了InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..);

// 通过Proxy直接创建动态代理类实例
Interface proxy = (Interface) Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);

JDK动态代理的机制

首先是JDK动态代理生成的代理类本身的特点:

  • 1、包(或者JDK9引入的模块):如果所代理的接口都是public的,那么它将被定义在包com.sun.proxy;如果所代理的接口中有非public的接口(因为接口不能被定义为protect或private,所以除public之外就是默认的package访问级别,修饰符为default),那么它将被定义在该接口所在包(假设代理了throwable.club包中的某非public接口A,那么新生成的代理类所在的包就是throwable.club),值得注意的是,如果接口数组中存在非public的接口,那么它们必须在同一个包路径下,否则会抛异常。这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问。
  • 2、类修饰符:该代理类具有final和public修饰符,意味着它可以被所有的类访问,但是不能被再度继承
  • 3、类名:代理类名称格式是$ProxyN,其中N是一个逐一递增的阿拉伯数字,代表java.lang.reflect.Proxy类第N次生成的动态代理类,值得注意的一点是,并不是每次调用Proxy的静态方法创建动态代理类都会使得N值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会从缓存中获取先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
  • 4、类继承关系:代理类的继承关系图如下:

j-r-d-p-1.png

由图可知,java.lang.reflect.Proxy类是代理类的父类,这个规则适用于所有由java.lang.reflect.Proxy创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

代理类实例的特点

每个代理类实例都会关联一个调用处理器对象,可以通过java.lang.reflect.Proxy提供的静态方法getInvocationHandler()去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类java.lang.Object中有三个方法也同样会被分派到调用处理器的invoke方法执行,它们是hashCodeequalstoString,可能的原因有:

  • 一、因为这些方法为public且非final类型,能够被代理类覆盖。
  • 二、因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

被代理的一组接口的特点

首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非public的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过65535,这是JVM设定的限制,这一点在代理类生成的时候也做了判断。

异常处理

从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于Throwable接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛Throwable异常。那么如果在invoke方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Jdk动态代理类已经为我们设计好了解决方法:它将会抛出UndeclaredThrowableException 异常。这个异常是一个RuntimeException类型,所以不会引起编译错误。通过该异常的getCause方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

JDK动态代理源码分析

因为JDK动态代理核心逻辑都在java.lang.reflect.Proxy类中,下面简单分析一下这个类的源码。先看Proxy类中的几个重要的静态变量:

1
2
3
4
5
6
7
8
// 接口组中接口都为为public时候代理类创建的包路径:com.sun.proxy
private static final String PROXY_PACKAGE_PREFIX = ReflectUtil.PROXY_PACKAGE;

// 代理类的构造方法参数类型数组,可见代理类的构造参数只有InvocationHandler类型
private static final Class<?>[] constructorParams = { InvocationHandler.class };

// 缓存了所有已经调用过setAccessible(true)的代理类的构造(Constructor)实例
private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();

这里注意到ClassLoaderValue,下文会调用到它的一个很复杂的调用链:

1
2
3
4
5
6
7
8
9
10
//intf是Class<?>类型
//loader是类加载器实例
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);

public V computeIfAbsent(ClassLoader cl,
BiFunction<? super ClassLoader,? super CLV,? extends V> mappingFunction)
throws IllegalStateException {

上面的computeIfAbsent中使用了函数式接口和Lambda表达式,如果Lambda表达式玩的比较熟练看起来应该没问题,它的功能可以解读为:通过接口类型和类加载器实例计算通过接口类型和类加载器实例构建ProxyBuilder实例并且调用ProxyBuilder#build()得到的结果,如果结果已经存在则直接返回缓存。其实computeIfAbsentMap接口中也定义了同样的方法,功能是相似的。

接着看Proxy的构造函数:

1
2
3
4
5
6
7
8
9
protected InvocationHandler h;

private Proxy() {
}

protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}

到此可以明确一点,既然所有动态代理类都是java.lang.reflect.Proxy的子类,那么它们一定具备一个包含InvocationHandler参数的构造器。接着查看``方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 空判断
Objects.requireNonNull(h);
// 当前调用类获取
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
// 获取代理类的构造器实例
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
// 生成代理实例
return newProxyInstance(caller, cons, h);
}

先看getProxyConstructor方法:

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
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces){
// 这里需要区分代理接口数组中只有单个接口和多个接口的逻辑
// 而基本的逻辑都是先校验当前调用类的权限,后续获取Constructor实例委托到ProxyBuilder
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// 接口克隆
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}

可以明确,核心的逻辑都交给了Proxy的内部类ProxyBuilder完成,先看ProxyBuilder的静态成员变量:

1
2
3
4
5
6
7
8
9
10
11
// Unsafe实例
private static final Unsafe UNSAFE = Unsafe.getUnsafe();

// 代理类的简单类名的前置字符串
private static final String proxyClassNamePrefix = "$Proxy";

// 用于生成下一个代理类的数字计数器,记住它是静态的
private static final AtomicLong nextUniqueNumber = new AtomicLong();

// 记录了已经生成的代理类-Boolean的映射,已经生成过对应代理类则记录为true
private static final ClassLoaderValue<Boolean> reverseProxyCache = new ClassLoaderValue<>();
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
// 单个代理接口的情况,其实也是把接口转换为List
ProxyBuilder(ClassLoader loader, Class<?> intf) {
this(loader, Collections.singletonList(intf));
}
// 多个代理接口的情况
ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
// 通过JVM参数强制关闭动态代理功能则抛出异常
if (!VM.isModuleSystemInited()) {
throw new InternalError("Proxy is not supported until "
+ "module system is fully initialized");
}
// 代理接口数量不能超过65535,也就是最多代理65535个接口
if (interfaces.size() > 65535) {
throw new IllegalArgumentException("interface limit exceeded: "
+ interfaces.size());
}
// 收集接口数组中所有接口的非静态方法的返回值类型、共享(shared)参数类型和共享(shared)异常类型,注释说是收集代理接口的方法签名
Set<Class<?>> refTypes = referencedTypes(loader, interfaces);

// 确保上一步得到的代理接口方法签名的类型都是"可见(其实就是类型都存在)"的,通过遍历调用Class.forName(type.getName(), false, ld)去判断
validateProxyInterfaces(loader, interfaces, refTypes);

this.interfaces = interfaces;
// 获取代理类最终生成的模块,规则如下:
// 1、所有代理接口的修饰符都为public,接口所在模块都能公开访问,则返回unnamed模块
// 2、如果有任意的代理接口是包私有,则返回该包所在的模块 、
// 3、所有代理接口的修饰符都为public,有任意至少一个接口所在模块不能公开访问,则返回该不能公开访问的模块,
this.module = mapToModule(loader, interfaces, refTypes);
assert getLoader(module) == loader;
}

一个构造器处理的逻辑也是相对复杂,主要是因为引入模块管理的概念,接着看ProxyBuilder#build()的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Constructor<?> build() {
// 定义代理类,实际上是动态生成代理类字节码和缓存它的类型的过程
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
try {
// 返回代理类的构造
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
return cons;
}

最后到逻辑最复杂的代理类的生成过程ProxyBuilder#defineProxyClass()

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 这里就是定义代理类包路径的逻辑,规则如下:
// 1、代理接口数组所有接口都是public修饰,则代理类包路径为com.sun.proxy
// 2、代理接口数组有任意接口是包私有的,则代理类包路径为该私有包的路径
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL; // non-public, final
String pkg = intf.getPackageName();
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 下面几个if都是包路径的合法性判断
if (proxyPkg == null) {
// all proxy interfaces are public
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
: PROXY_PACKAGE_PREFIX;
} else if (proxyPkg.isEmpty() && m.isNamed()) {
throw new IllegalArgumentException(
"Unnamed package cannot be added to " + m);
}
if (m.isNamed()) {
if (!m.getDescriptor().packages().contains(proxyPkg)) {
throw new InternalError(proxyPkg + " not exist in " + m.getName());
}
}
// 计数器加1返回新的计数值
long num = nextUniqueNumber.getAndIncrement();
// 生成代理类全类名,一个常见的格式是:com.sun.proxy.$Proxy1
String proxyName = proxyPkg.isEmpty()
? proxyClassNamePrefix + num
: proxyPkg + "." + proxyClassNamePrefix + num;
ClassLoader loader = getLoader(m);
trace(proxyName, m, loader, interfaces);
// 动态生成代理类字节码字节数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
// 通过Unsafe定义代理类-这里是通过字节码定义新的类
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
// 缓存代理类已经生成过的标记
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}

到这一步为止,代理类的生成过程已经大致分析完毕,ProxyGenerator中涉及到大量字节码操作,这里就不深入分析了。那么回到最前面的方法,得到代理类和它的构造实例,接着就可以生成代理实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
try {
if (caller != null) {
checkNewProxyPermission(caller, cons.getDeclaringClass());
}
// 这里简单反射调用Constructor#newInstance(h)
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
}
}

小结一下:

  • 接口数组中所有接口元素的类修饰符最好一致为public。如果接口数组中存在非default修饰的接口元素,那么接口数组中的所有接口类都要放在同一个包下,并且都要使用default修饰。
  • 很少情况下我们修改接口的修饰符,默认为public,那么所有代理类的包路径都是com.sun.proxy,全类名是:com.sun.proxy.$ProxyN
  • 代理接口数量不能超过65535。

JDK动态代理类的源代码

前面已经分析完了代理类的生成过程,这里举个简单的使用例子,并且观察生成的动态代理类的源代码。

使用例子:

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
// 接口
public interface Simple {

void sayHello(String name);
}
// 接口实现
public class DefaultSimple implements Simple {

@Override
public void sayHello(String name) {
System.out.println(String.format("%s say hello!", name));
}
}
// 场景类
public class Main {

public static void main(String[] args) throws Exception {
Simple simple = new DefaultSimple();
Object target = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Simple.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before say hello...");
method.invoke(simple, args);
System.out.println("After say hello...");
return null;
}
});
Simple proxy = (Simple) target;
proxy.sayHello("throwable");
}
}

调用后输出:

1
2
3
Before say hello...
throwable say hello!
After say hello...

可以看到,我们在被代理类DefaultSimple实例的方法调用前后织入了自定义的逻辑,这就是通过JDK动态代理实现AOP的底层原理。在JDK8中可以直接使用sun.misc.ProxyGenerator去输出代理类的class文件,但是JDK11中这个代理类生成器已经变成java.lang.reflect.ProxyGenerator,并且这个类是包私有的,我们无法使用,但是它提供了jdk.proxy.ProxyGenerator.saveGeneratedFiles这个VM参数让我们可以保存代理类的class文件:

1
2
# JVM参数
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

配置好VM参数后,再次调用mian方法就能看到在项目的顶层包路径下看到对应的类com.sun.proxy.$Proxy0,目前从java.lang.reflect.ProxyGenerator源码看无法控制代理类文件的输出路径,生成的代理类内容如下:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public final class $Proxy0 extends Proxy implements Simple {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final void sayHello(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("club.throwable.jdk.sample.reflection.proxy.Simple").getMethod("sayHello", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

代理类的代码比较简单,有如下几个特点:

  • 1、代理类继承于java.lang.reflect.Proxy,实现了接口数组中的接口元素类,构造函数只有一个InvocationHandler类型的参数。
  • 2、接口中的所有被代理方法包括equalstoStringhashCode都建立了一个对应的Method私有静态实例,在最后面的静态代码块中实例化。
  • 3、所有代理方法都是用public final修饰,也就是代理类中的代理方法是不能覆盖的。
  • 4、所有代理方法都是通过InvocationHandler实例的invoke方法进行调用的,记得第一个参数是代理类实例本身,如果用了在InvocationHandler#invoke()方法实现过程中使用了这个参数有可能造成死循环。

小结

诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的单继承机制注定了这些动态代理类们无法实现对class的动态代理(所以只能代理接口,实际上是基于反射对方法级别的逻辑进行编织)。有很多条理由,可以否定对class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。但是,不完美并不等于不伟大,伟大是一种本质,JDK动态代理就是佐例。

参考资料:

(本文完 e-20181208 c-3-d)

深入分析Java反射(三)-泛型

前提

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

本文主要介绍反射中一个比较难的问题-泛型。

泛型的简介

泛型是在2004年JavaSE 5.0(JDK1.5)版本中添加到Java编程语言中的泛型编程工具。泛型的设计是为了应用在Java的类型系统,提供"用类型或者方法操作各种类型的对象从而提供编译期的类型安全功能(原文:a type or method to operate on objects of various types while providing compile-time type safety)"。但是在2016年的一些研究表明,泛型并不是在所有的情况下都能保证编译期的类型安全,例如切面(Aspect)编程的编译期类型安全并没有完全实现。

泛型的一个最大的优点就是:提供编译期的类型安全。举个很简单的例子,在引入泛型之前,ArrayList内部只维护了一个Object数组引用,这种做法有两个问题:

  • 从数组列表获取一个元素的时候必须进行类型的强转。
  • 向数组列表中可以添加任何类型的对象,导致无法得知数组列表中存放了什么类型的元素。

引入泛型之后,我们可以通过类型参数明确定义ArrayList

1
2
3
4
ArrayList<String> list = new ArrayList<String>();

// JavaSE 7以后的版本中构造函数可以省略类型,编译器可以推导出实际类型
ArrayList<String> list = new ArrayList<>();

下面先列举出Java中泛型的一些事实:

  • Java虚拟机中不存在泛型,只有普通的类和方法,但是字节码中存放着泛型相关的信息
  • 所有的类型参数都使用它们的限定类型替换。
  • 桥方法(Bridge Method)由编译器合成,用于保持多态(Java虚拟机利用方法的参数类型、方法名称和方法返回值类型确定一个方法)。
  • 为了保持类型的安全性,必要时需要进行类型的强制转换。

理解类型擦除

类型擦除是什么

类型擦除(或者更多时候喜欢称为"泛型擦除")的具体表现是:无论何时定义一个泛型类型,都自动提供一个相应的原始类型(Raw Type,这里的原始类型并不是指int、boolean等基本数据类型),原始类型的类名称就是带有泛型参数的类删去泛型参数后的类型名称,而原始类型会擦除(Erased)类型变量,并且把它们替换为限定类型(如果没有指定限定类型,则擦除为Object类型),举个例子Pair<T>带有泛型参数的类型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Pair<T>{

private T first;
private T second;

public Pair(T first,T second){
this.first = first;
this.second = second;
}

public T getFirst(){
return first;
}

public T getSecond(){
return second;
}
}

擦除类型后的Pair<T>的原始类型为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Pair{

private Object first;
private Object second;

public Pair(Object first,Object second){
this.first = first;
this.second = second;
}

public Object getFirst(){
return first;
}

public Object getSecond(){
return second;
}
}

举个更复杂的例子,如果泛型参数类型是有上限的,变量会擦除为上限的类型:

1
2
3
4
5
6
7
8
9
10
11
12
public class Interval<T extends Comparable & Serializable> implements Serializable {

private T lower;
private T upper;

public Interval(T lower, T upper) {
this.lower = lower;
this.upper = upper;
}

//省略其他方法
}

类型擦除后的Interval<T extends Comparable & Serializable>原始类型:

1
2
3
4
5
6
7
8
9
10
11
12
public class Interval implements Serializable {

private Comparable lower;
private Comparable upper;

public Interval(Comparable lower, Comparable upper) {
this.lower = lower;
this.upper = upper;
}

//省略其他方法
}

像上面这种多个泛型上限的类型,应该尽量把标识接口上限类型放在边界列表的尾部,这样做可以提高效率。

为什么需要擦除类型

在JDK1.5之前,也就是在泛型出现之前,所有的类型包括基本数据类型(int、byte等)、包装类型、其他自定义的类型等等都可以使用类文件(.class)字节码对应的java.lang.Class描述,也就是java.lang.Class类的一个具体实例对象就可以代表任意一个指定类型的原始类型。这里把泛型出现之前的所有类型暂时称为"历史原始类型"。

在JDK1.5之后,数据类型得到了扩充,出历史原始类型扩充了四种泛型类型:参数化类型(ParameterizedType)、类型变量类型(TypeVariable)、限定符类型(WildcardType)、泛型数组类型(GenericArrayType)。历史原始类型和新扩充的泛型类型都应该统一成各自的字节码文件类型对象,也就应该把泛型类型归并进去java.lang.Class中。但是由于JDK已经迭代了很多版本,泛型并不属于当前Java中的基本成分,如果JVM中引入真正的泛型类型,那么必须涉及到JVM指令集和字节码文件的修改(这个修改肯定不是小的修改,因为JDK当时已经迭代了很多年,而类型是编程语言的十分基础的特性,引入泛型从项目功能迭代角度看可能需要整个JVM项目做回归测试),这个功能的代价十分巨大,所以Java没有在Java虚拟机层面引入泛型。

Java为了使用泛型,于是使用了类型擦除的机制引入了"泛型的使用",并没有真正意义上引入和实现泛型。Java中的泛型实现的是编译期的类型安全,也就是泛型的类型安全检查是在编译期由编译器(常见的是javac)实现的,这样就能够确保数据基于类型上的安全性并且避免了强制类型转换的麻烦(实际上,强制类型转换是由编译器完成了,只是不需要人为去完成而已)。一旦编译完成,所有的泛型类型都会被擦除,如果没有指定上限,就会擦除为Object类型,否则擦除为上限类型。

既然Java虚拟机中不存在泛型,那么为什么可以从JDK中的一些类库获取泛型信息?这是因为类文件(.class)或者说字节码文件本身存储了泛型的信息,相关类库(可以是JDK的类库,也可以是第三方的类库)读取泛型信息的时候可以从字节码文件中提取,例如比较常用的字节码操作类库ASM就可以读取字节码中的信息甚至改造字节码动态生成类。例如前面提到的Interval<T extends Comparable & Serializable>类,使用javap -c -v命令查看其反编译得到的字节码信息,可以看到其签名如下:

1
Signature: #22                          // <T::Ljava/lang/Comparable;:Ljava/io/Serializable;>Ljava/lang/Object;Ljava/io/Serializable;

这里的签名信息实际上是保存在常量池中的,关于字节码文件的解析将来会出一个系列文章详细展开。

Type体系

前文提到了在JDK1.5中引入了四种新的泛型类型java.lang.reflect.ParameterizedTypejava.lang.reflect.TypeVariablejava.lang.reflect.WildcardTypejava.lang.reflect.GenericArrayType,包括原来存在的java.lang.Class,一共存在五种类型。为了程序的扩展性,引入了java.lang.reflect.Type类作为这五种类型的公共父接口,这样子就可以使用java.lang.reflect.Type类型参数去接收以上五种子类型的实参或者返回值,由此从逻辑上统一了泛型相关的类型和原始存在的java.lang.Class描述的类型。Type体系如下:

j-r-g-1.png

注意:

  • ParameterizedType、TypeVariable、WildcardType、GenericArrayType都是接口,它们位于java.lang.reflect包中。
  • ParameterizedTypeImpl、TypeVariableImpl、WildcardTypeImpl、GenericArrayTypeImpl是四种泛型类型的实现,位于sun.reflect.generics.reflectiveObjects包中。

Type体系虽然看似很美好解决了泛型相关的类型和原始存在的java.lang.Class描述的类型的统一问题,但是引入了新的问题:如果一个方法返回值为java.lang.reflect.Type类型,或者一个方法的入参类型为java.lang.reflect.Type类型,这两种情况下,可能需要对java.lang.reflect.Type类型的对象做子类型判断,因为它的子类型有可能是上面提到的五种类型中的其中一种,这一点提高了编码的复杂性。

ParameterizedType

ParameterizedType,parameterized type,也就是参数化类型,注释里面说到ParameterizedType表示一个参数化类型,例如Collection<String>,实际上只要带有参数化(泛型)标签<ClassName>的参数或者属性,都属于ParameterizedType。例如下面的类型都是ParameterizedType:

1
2
3
4
5
6
7
8
Set<String> set;
Class<Integer> clazz;
MyClass<String> myClass;
List<String> list;

class MyClass<V>{

}

而像下面的忽略泛型参数或者基本数据类型和基本数据类型的包装类都不是ParameterizedType:

1
2
3
4
5
6
7
8
String name = "throwbale";
int age = 25;
Set set;
List list;

public String method(int age,String name){

}

java.lang.reflect.ParameterizedType接口继承自java.lang.reflect.Type接口,实现类是sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl,其实,必要的时候,我们也可以自行实现ParameterizedType,像一些Json解析工具都是自行实现ParameterizedType的。ParameterizedType接口的方法如下:

1
2
3
4
5
6
7
8
public interface ParameterizedType extends Type {

Type[] getActualTypeArguments();

Type getRawType();

Type getOwnerType();
}
  • Type[] getActualTypeArguments():返回这个ParameterizedType类型的参数的实际类型Type数组,Type数组里面的元素有可能是Class、ParameterizedType、TypeVariable、GenericArrayType或者WildcardType之一。值得注意的是,无论泛型符号<>中有几层<>嵌套,这个方法仅仅脱去最外层的<>,之后剩下的内容就作为这个方法的返回值。
  • Type getRawType():返回的是当前这个ParameterizedType的原始类型,从ParameterizedTypeImpl的源码看来,原始类型rawType一定是一个Class<?>实例。举个例子,List<Person>通过getRawType()获取到的Type实例实际上是Class<?>实例,和List.class等价。
  • Type getOwnerType():获取原始类型所属的类型,从ParameterizedTypeImpl的源码看来,就是调用了原始类型rawType的getDeclaringClass()方法,而像rawType为List<T>Map<T>这些类型的getOwnerType()实际上就是调用List.class.getDeclaringClass(),Map.class.getDeclaringClass(),返回值都是null。

举个关于ParameterizedType的简单使用例子:

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
public class Main13 {

public static void main(String[] args) throws Exception {
Class<Sub> subClass = Sub.class;
Type genericSuperclass = subClass.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//获取父类泛型类型数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type type : actualTypeArguments) {
System.out.println(type + " is ParameterizedType -> " + (type instanceof ParameterizedType));
}
}
Field field = subClass.getDeclaredField("clazz");
Type genericType = field.getGenericType();
System.out.println(genericType + " is ParameterizedType -> " + (genericType instanceof ParameterizedType));
}

public static class Person {

}

public static abstract class Supper<T, E> {

}

public static class Sub extends Supper<String, List<Person>> {

}
}

输出结果:

1
2
3
class java.lang.String is ParameterizedType -> false
java.util.List<org.throwable.inherited.Main13$Person> is ParameterizedType -> true
java.lang.Class<?> is ParameterizedType -> true

TypeVariable

TypeVariable,type variable,也就是类型变量,它是各种类型变量的公共父接口,它主要用来表示带有上界的泛型参数的信息,它和ParameterizedType不同的地方是,ParameterizedType表示的参数的最外层一定是已知具体类型的(如List<String>),而TypeVariable面向的是K、V、E等这些泛型参数字面量的表示。常见的TypeVariable的表示形式是<T extends KnownType-1 & KnownType-2>。TypeVariable接口源码如下:

1
2
3
4
5
6
7
8
9
10
public interface TypeVariable<D extends GenericDeclaration> extends Type {
//获得泛型的上限,若未明确声明上边界则默认为Object
Type[] getBounds();
//获取声明该类型变量实体(即获得类、方法或构造器名)
D getGenericDeclaration();
//获得名称,即K、V、E之类名称
String getName();
//获得注解类型的上限,若未明确声明上边界则默认为长度为0的数组
AnnotatedType[] getAnnotatedBounds()
}
  • Type[] getBounds():获得该类型变量的上限(上边界),若无显式定义(extends),默认为Object,类型变量的上限可能不止一个,因为可以用&符号限定多个(这其中有且只能有一个为类或抽象类,且必须放在extends后的第一个,即若有多个上边界,则第一个&之后的必为接口)。
  • D getGenericDeclaration:获得声明(定义)这个类型变量的类型及名称,会使用泛型的参数字面量表示,如public void club.throwable.Main.query(java.util.List<club.throwable.Person>)
  • String getName():获取泛型参数的字面量名称,即K、V、E之类名称。
  • AnnotatedType[] getAnnotatedBounds():Jdk1.8新增的方法,用于获得注解类型的上限,若未明确声明上边界则默认为长度为0的数组。

举个关于TypeVariable的简单使用例子:

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
public class Main14 {

public static void main(String[] args) throws Exception {
Class<Supper> subClass = Supper.class;
TypeVariable<Class<Supper>>[] typeParameters = subClass.getTypeParameters();
for (TypeVariable<Class<Supper>> typeVariable : typeParameters) {
System.out.println("getBounds --> " + Arrays.toString(typeVariable.getBounds()));
System.out.println("getGenericDeclaration --> " + typeVariable.getGenericDeclaration());
System.out.println("getName --> " + typeVariable.getName());
AnnotatedType[] annotatedBounds = typeVariable.getAnnotatedBounds();
StringBuilder stringBuilder = new StringBuilder("getAnnotatedBounds --> ");
for (AnnotatedType annotatedType : annotatedBounds) {
java.lang.annotation.Annotation[] annotations = annotatedType.getAnnotations();
for (java.lang.annotation.Annotation annotation : annotations) {
stringBuilder.append(annotation).append(",");
}
}
System.out.println(stringBuilder.toString());
System.out.println("===================");
}
}

@Target(ElementType.TYPE)
public @interface Annotation {

}

interface InterFace {

}

public static class Person {

}

public static abstract class Supper<T extends Person & InterFace, E extends Annotation> {

}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
getBounds --> [class org.throwable.inherited.Main14$Person, interface org.throwable.inherited.Main14$InterFace]
getGenericDeclaration --> class org.throwable.inherited.Main14$Supper
getName --> T
getAnnotatedBounds -->
===================
getBounds --> [interface org.throwable.inherited.Main14$Annotation]
getGenericDeclaration --> class org.throwable.inherited.Main14$Supper
getName --> E
getAnnotatedBounds -->
===================

WildcardType

WildcardType用于表示通配符(?)类型的表达式的泛型参数,例如<? extends Number>等。根据WildcardType注释提示:现阶段通配符表达式仅仅接受一个上边界或者下边界,这个和定义类型变量时候可以指定多个上边界是不一样。但是为了保持扩展性,这里返回值类型写成了数组形式。实际上现在返回的数组的大小就是1。WildcardType接口源码如下:

1
2
3
4
5
6
public interface WildcardType extends Type {

Type[] getUpperBounds();

Type[] getLowerBounds();
}
  • Type[] getUpperBounds():获取泛型通配符的上限类型Type数组,实际上目前该数组只有一个元素,也就是说只能有一个上限类型。
  • Type[] getLowerBounds():获取泛型通配符的下限类型Type数组,实际上目前该数组只有一个元素,也就是说只能有一个下限类型。

举个关于WildcardType的简单使用例子:

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
public class Main16 {

public static void main(String[] args) {
Class<Main16> clazz = Main16.class;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if ("print".equals(method.getName())) {
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type type : genericParameterTypes) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualType : actualTypeArguments) {
if (actualType instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) actualType;
System.out.println("WildcardType --> " + wildcardType + " getUpperBounds--> "
+ Arrays.toString(wildcardType.getUpperBounds()) + " getLowerBounds--> " + Arrays.toString(wildcardType.getLowerBounds()));
} else {
System.out.println("Not WildcardType --> " + actualType);
}
}

}
}
}
}
}

interface Person {

}

public static void print(List<? extends Number> list, Set<? super Person> persons) {

}
}

输出结果:

1
2
WildcardType --> ? extends java.lang.Number getUpperBounds--> [class java.lang.Number] getLowerBounds--> []
WildcardType --> ? super org.throwable.inherited.Main16$Person getUpperBounds--> [class java.lang.Object] getLowerBounds--> [interface org.throwable.inherited.Main16$Person]

这里注意的是List<? extends Number> list这个参数整体来看是ParameterizedType类型,剥掉第一次List之后的? extends Number是WildcardType类型。

GenericArrayType

GenericArrayType,generic array type,也就是泛型数组,也就是元素类型为泛型类型的数组实现了该接口。它要求元素的类型是ParameterizedType或TypeVariable(实际中发现元素是GenericArrayType也是允许的)。举个例子:

1
2
3
4
5
List<String>[] listArray; //是GenericArrayType,元素是List<String>类型,也就是ParameterizedType类型
T[] tArray; //是GenericArrayType,元素是T类型,也就是TypeVariable类型

Person[] persons; //不是GenericArrayType
List<String> strings; //不是GenericArrayType

GenericArrayType接口的源码如下:

1
2
3
4
public interface GenericArrayType extends Type {

Type getGenericComponentType();
}
  • Type getGenericComponentType():获取泛型数组中元素的类型。注意无论从左向右有几个[]并列,这个方法仅仅脱去最右边的[]之后剩下的内容就作为这个方法的返回值。

举个关于GenericArrayType的简单使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Main15<T> {


public static void main(String[] args) throws Exception {
Method[] methods = Main15.class.getMethods();
for (Method method : methods) {
if ("method".equals(method.getName())) {
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type type : genericParameterTypes) {
if (type instanceof GenericArrayType) {
System.out.println("GenericArrayType --> " + type + " getGenericComponentType --> "
+ ((GenericArrayType) type).getGenericComponentType());
} else {
System.out.println("Not GenericArrayType --> " + type);
}
}
}
}
}

public static <T> void method(String[] strings, List<String> ls, List<String>[] lsa, T[] ts, List<T>[] tla, T[][] tts) {

}
}

输出结果:

1
2
3
4
5
6
Not GenericArrayType --> class [Ljava.lang.String;
Not GenericArrayType --> java.util.List<java.lang.String>
GenericArrayType --> java.util.List<java.lang.String>[] getGenericComponentType --> java.util.List<java.lang.String>
GenericArrayType --> T[] getGenericComponentType --> T
GenericArrayType --> java.util.List<T>[] getGenericComponentType --> java.util.List<T>
GenericArrayType --> T[][] getGenericComponentType --> T[]

这里分析一下:

  • String[] strings:数组是Class类型。
  • List<String> ls:列表是ParameterizedType类型。
  • List<String>[] lsa:数组是GenericArrayType类型,调用getGenericComponentType后返回的类型是java.util.List<java.lang.String>,也就是数组元素是ParameterizedType类型。
  • T[] ts:s数组是GenericArrayType类型,调用getGenericComponentType后返回的类型是T,也就是数组元素是TypeVariable类型。
  • List<T>[] tla:数组是GenericArrayType类型,调用getGenericComponentType后返回的类型是java.util.List<T>,也就是数组元素是ParameterizedType类型。
  • T[][] tts:数组是GenericArrayType类型,调用getGenericComponentType后返回的类型T[],也就是数组元素是GenericArrayType类型。

泛型的约束

使用Java泛型的时候需要考虑一些限制,这些限制大多数是由泛型类型擦除引起的。

  • 1、不能用基本类型实例化类型参数,也就是8种基本类型不能作为泛型参数,例如Pair<int>是非法的,会导致编译错误,而Pair<Integer>是合法的。
  • 2、运行时的类型查询只能适用于原始类型(非参数化类型)。
1
2
3
4
5
6
7
//下面的两种做法是错误的
if(a instanceof Pair<String>) //Error

if(a instanceof Pair<T>) //Error

// 正确做法
if(a instanceof Pair) //Right
  • 3、不能创建参数化类型的数组,例如Pair<String>[] arr = new Pair<String>[10]是非法的。
  • 4、不能实例化类型变量或者类型变量数组,例如T t = new T()或者T[] arr = new T[10]都是非法的。
  • 5、Varargs警告,这是因为第4点原因导致的,一般会发生在泛型类型变量作为可变参数的情况,例如public static <T> addAll(Collection<T> con,T ... ts),第二个参数实际上就是泛型类型变量数组,但是这种情况是合法的,不过会受到编译器的警告,可以通过@SuppressWarnings("unchecked")注解或者@SafeVarargs注解标注该方法以消除警告。
  • 6、不能在静态域或者方法中引用类型变量,例如private static T singleInstance;这样是非法的。
  • 7、不能抛出或者抛出或者捕获泛型类型变量,但是如果在异常规范中使用泛型类型变量则是允许的,举两个例子仔细品味一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 反例
public static <T extends Throwable> void doWork(Class<T> t) {
try{

}catch(T t){ //Error

}
}

// 正例
public static <T extends Throwable> void doWork(T t) throws T{
try{

}catch(Throwable e){
throw e;
}
}
  • 8、通过使用@SuppressWarnings("unchecked")注解可以消除Java类型系统的部分基本限制,一般使用在强制转换原始类型为泛型类型(只是在编译层面告知编译器)的情况,如:
1
2
3
4
5
// 不加此注解会收到编译器的警告
@SuppressWarnings("unchecked")
public static <T extends Throwable> void throwAs(Throwable e){
throw (T) e;
}

其实还有泛型的继承规则和通配符规则(可以看下前面介绍的Type的子类型)等等,这里不详细展开。

再议泛型数组的问题

在Java泛型约束中,无法实例化参数化类型数组,例如Pair<Integer>[] table = new Pair<Integer>[10];是非法的。根本原因在于泛型类型的擦除和数组会记录元素类型的特性。举个例子,假设可以实例化参数化类型数组:

1
Pair<String>[] table = new Pair<String>[10];

上面的参数化类型数组在泛型擦除之后,数组实例table的类型为Pair[],数组元素类型为Pair,可以强转为Object[]类型数组:

1
Object[] objArray = table;

基于泛型擦除,数组objArray可以任意赋值Pair<AnyType>的泛型化实例,例如:

1
2
3
4
objArray[0] = new Pair<Integer>();
objArray[1] = new Pair<Long>();
objArray[2] = new Pair<String>();
....

这样子能够通过数组存储元素的检查,后续操作数组元素随时会出现ClassCastException。基于以上的原因,Java从编译层面直接拒绝创建参数化类型数组

另外,类型变量数组的实例化也是非法的,如T[] tt = new T[10];,这是因为类型变量仅仅是编译期的字面量,其实和Java的类型体系是不相关的。

但是要注意一点:参数化类型数组和类型变量数组可以作为方法入参变量或者类的成员变量。例如下面的做法是合法的:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Pair<T> {

private Pair<T>[] attr;
private T[] ts;

public static <T> void method(Pair<T> pair) {

}

public static <T> void method(T[] ts) {

}
}

最后一点,可以查看前一篇文章,其实可以使用反射创建泛型数组

无限定通配符

泛型中支持无限定通配符<?>,使用无限定通配符类型的实例有以下限制:

  • 所有的Getter方法只能返回Object类型的值。
  • 所有的Setter方法只能赋值null,其他类型的值的设置都是非法的。

无限定通配符类型可以看做原始类型的一个影子类型,它屏蔽了除了null之外的设值操作,所有获取值的方法只能返回Object类型结果,这种特性使得通过无限定通配符类型进行一些简单的操作变得十分方便,例如:

1
2
3
public static boolean hasNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
}

如果反射用得比较熟的话,java.lang.Class也有类似的用法:

1
2
Class<?> clazz = ...;
Object instance = class.newInstance();

桥方法(Bridge Method)

先说明一下什么是桥方法,看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 父类
public interface Supper<T> {

void method(T t);
}

// 其中一个子类
public class Sub implements Supper<Integer> {

@Override
public void method(Integer value) {
System.out.println(value);
}
}

父类Supper<T>在泛型擦除后原始类型是:

1
2
3
4
public interface Supper{

void method(Object t);
}

子类Sub虽然实现了父类Supper,但是它只实现了void method(Integer value)而没有实现父类中的void method(Object t),这个时候,编译期编译器会为子类Sub创建此方法,也就是子类Sub会变成这样:

1
2
3
4
5
6
7
8
9
10
11
public class Sub implements Supper<Integer> {

@Override
public void method(Integer value) {
System.out.println(value);
}

public void method(Object value) {
this.method((Integer) value);
}
}

如果你直接这样编写一个子类Sub是会编译报错,而上面这里编译器生成的void method(Object value)方法就是桥方法。可以用反射验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws Exception {
Method[] declaredMethods = Sub.class.getDeclaredMethods();
List<Method> methods = new ArrayList<>();
for (Method method : declaredMethods) {
if (method.getName().equals("method")) {
methods.add(method);
}
}
for (Method method : methods) {
System.out.println(String.format("name=%s,paramTypes=%s,isBridge=%s", method.getName(),
Arrays.toString(method.getParameterTypes()), method.isBridge()));
}
}

//输出结果
name=method,paramTypes=[class java.lang.Integer],isBridge=false
name=method,paramTypes=[class java.lang.Object],isBridge=true

桥方法的定义比较模糊,因此这里只考虑它出现的情况,不做盲目的定义。不单只是子类实现带有泛型参数的父类会产生桥方法,还有一种比较常见的情况是在方法覆盖的时候指定一个更加"严格的"返回值类型的时候,也会产生桥方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Employee implements Cloneable{

public Employee clone() throws CloneNotSupportedException{
//...
}
}

// 这里实际上,Employee覆盖了Object的clone()方法,因此实际上编译后Employee如下
public Employee implements Cloneable{

public Employee clone() throws CloneNotSupportedException{
//...
}

// 这个是桥方法
public Object clone() throws CloneNotSupportedException{
//...
}
}

这是因为:

  • 编译的时候Java的方法签名是方法名称加上方法参数类型列表,也就是方法名和参数类型列表确定一个方法的签名(这样就可以很好理解方法重载,还有Java中的参数都是形参,所以参数名称没有实质意义,只有参数类型才是有意义的)。
  • Java虚拟机定义一个方法的签名是由方法名称、方法返回值类型和方法参数类型列表组成,所以JVM认为返回值类型不同,而方法名称和参数类型列表一致的方法是不相同的方法。

仔细看,其实两种情况都是由于继承才导致桥方法出现。

JDK中操作泛型的API

这里列举一下JDK中笔者所知的操作泛型的相关API(可以会有遗漏),这些API主要和反射操作相关:

java.lang.Class中的相关方法:

方法 功能
Type[] getGenericInterfaces() 返回类实例的接口的泛型类型
Type getGenericSuperclass() 返回类实例的父类的泛型类型

java.lang.reflect.Constructor中的相关方法:

方法 功能
Type[] getGenericExceptionTypes() 返回构造器的异常的泛型类型
Type[] getGenericParameterTypes() 返回构造器的方法参数的泛型类型

java.lang.reflect.Method中的相关方法:

方法 功能
Type[] getGenericExceptionTypes() 返回方法的异常的泛型类型
Type[] getGenericParameterTypes() 返回方法参数的泛型类型
Type getGenericReturnType() 返回方法返回值的泛型类型

java.lang.reflect.Field中的相关方法:

方法 功能
Type getGenericType() 返回属性的泛型类型

如果在使用上面的方法得到的返回值和期望的返回值不相同,请加深对泛型类型擦除的认识。

小结

参考资料:

个人认为,泛型其实是JDK迭代过程中妥协和兼容历史的产物,它是一种没有实现的泛型,当然,提供编译期类型安全这一点可以让开发者避免类型转换出现人为错误,也就是说:Java中的泛型使得程序或者代码的可读性和安全性提高,这是它的最大优势。

(本文完 e-20181204-c-3d r-20181205)

深入分析Java反射(二)-数组和枚举

前提

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

本文主要介绍反射中可能用到的两个比较特殊的类型,数组和枚举,分别对应java.lang.reflect.Arrayjava.lang.Enum,后者其实并不是反射类库包中的类,但是反射的基础类库里面有使用枚举类型的方法。

数组类型

数组是一种包含固定数量的相同类型组件(Component)的引用类型对象,也就是说数组的长度是不可变,它的每个元素都是相同类型的。创建数组实例需要定义数组的长度和组件的类型。数组是由Java虚拟机实现(这一点很重要,这就是为什么JDK类库中没有数组对应的类型的原因,array也不是Java中的保留关键字,操作数组的底层方法都是native方法),数组类型只有继承自java.lang.Object的方法,数组的length方法实际上并不属于数组类型的一部分,数组的length方法其实最终调用的是java.lang.reflect.Array#getLength(),注意到这个方法是native方法。java.lang.reflect.Array是基于反射操作数组的核心类。

使用非反射方式创建数组实例的过程如下:

1
2
3
4
5
fully_qualified_class_name[] variable_name  = {val1,val2,val3,...};

fully_qualified_class_name[] variable_name = new fully_qualified_class_name[${fix_length}];

例如:int[] arr = new int[10];

使用反射方式就是使用java.lang.reflect.Array中的相关方法:

1
2
3
4
5
6
7
8
9
Class<?> c = Class.forName(cName);
Object o = Array.newInstance(c, n);
for (int i = 0; i < n; i++) {
String v = cVals[i];
Constructor ctor = c.getConstructor(String.class);
Object val = ctor.newInstance(v);
Array.set(o, i, val);
}
Object[] oo = (Object[]) o;

下面列举一下java.lang.reflect.Array中的方法:

方法 功能
static Object newInstance(Class<?> componentType, int length) 指定组件类型和数组固定长度创建一维数组
static Object newInstance(Class<?> componentType, int… dimensions) 指定组件类型和多个固定长度创建多维数组,维度的最大值为255
static native int getLength(Object array) 获取数组长度
static native Object get(Object array, int index) 通过下标访问数组元素
static native void set(Object array, int index, Object value) 通过下标设置数组元素

这里省略了一部分对于IntBoolean等原始类型的Setter和Getter方法。

java.lang.Class和数组相关的方法:

方法 功能
native boolean isArray() 判断类型是否数组类型
Class<?> getComponentType() 如果是数组类型则返回其组件类型,否则返回null

这里举个例子加深下印象:

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
public class ArrayCreationMain {

/**
* 这个是我们创建的最终目标数组
*/
private static String R = "java.math.BigInteger[] bi = {123,234,345}";
private static final String[] S = new String[]{"123", "234", "345"};

public static void main(String[] args) throws Exception {
Class<BigInteger> componentType = BigInteger.class;
Object arrayObject = Array.newInstance(componentType, 3);
for (int i = 0; i < S.length; i++) {
String each = S[i];
Constructor<BigInteger> constructor = componentType.getConstructor(String.class);
BigInteger value = constructor.newInstance(each);
Array.set(arrayObject, i, value);
}
Object[] result = (Object[]) arrayObject;
System.out.println(String.format("%s[] = %s", componentType, Arrays.toString(result)));
int length = Array.getLength(arrayObject);
System.out.println("Length = " + length);
for (int i = 0; i < length; i++) {
System.out.println(String.format("index = %d,value = %s", i, Array.get(arrayObject, i)));
}
Class<?> arrayObjectClass = arrayObject.getClass();
System.out.println("Is array type:" + arrayObjectClass.isArray());
System.out.println("Component type:" + arrayObjectClass.getComponentType());
}
}

运行后输出:

1
2
3
4
5
6
7
class java.math.BigInteger[] = [123, 234, 345]
Length = 3
index = 0,value = 123
index = 1,value = 234
index = 2,value = 345
Is array type:true
Component type:class java.math.BigInteger

需要注意的是,java.lang.reflect.Array中的Setter和Getter方法如果越界操作数组元素,会抛出ArrayIndexOutOfBoundsException,通过Setter设置和数组初始化时候的组件类型不一致的元素会抛出IllegalArgumentException

细议数组类型

前面说到了数组类型的一些基础特性,这里补充一些比较高级的使用方法。

创建特定元素类型的数组:

因为Java泛型擦除的问题,实际上我们使用Array#newInstance方法只能得到一个Object类型的结果实例,其实这个结果实例的类型就是ComponentType[],这里只是返回了它的父类(Object)类型实例,因此我们可以直接强转,例如:

1
String[] strArray = (String[]) Array.newInstance(String.class, 3);

获取数组类型:

在非反射方式下,我们可以通过数组实例.class通过class字面量直接获取数组类型,例如:

1
Class stringArrayClass = String[].class;

反射条件下,可以通过Class.forName()获取数组类型,但是调用此方法的时候有个限制,类名必须使用JVM可以识别的签名形式,就是[L${ComponentType};,注意Class.forName()无法获取原始类型(如int、boolean)的类型,例如:

1
2
3
4
5
6
// 不能漏了左边的[L和右边的;
Class stringArrayClass = Class.forName("[Ljava.lang.String;");

// 下面这样做会抛出ClassNotFoundException
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");

获取数组元素(组件)类型:

目前获取数组组件类型只能通过数组类型实例去调用Class#getComponentType()

枚举类型

枚举是一种语言结构(Language Construct),用于定义可以使用一组固定的名值对表示的类型安全的枚举(原文是:An enum is a language construct that is used to define type-safe enumerations which can be used when a fixed set of named values is desired)。所有枚举都继承自java.lang.Enum。枚举可以包含一个或者多个枚举常量,这些枚举常量都是该枚举的实例。枚举的声明其实和一个普通的Class的声明相似,因为它可以包含字段、方法和构造函数之类的成员。

因为枚举就是普通的Java类,因此反射相关类库中并没有添加一个java.lang.reflect.Enum类型,反射中的API和枚举相关的有:

  • boolean java.lang.Class#isEnum():判断类型是否枚举类型。
  • T[] java.lang.Class#getEnumConstants():获取类型中所有的枚举常量。
  • boolean java.lang.reflect.Field#isEnumConstant():判断属性是否枚举类型。

如果实例中的成员属性为枚举,那么枚举的反射操作实际上就是java.lang.reflect.Field的相关操作。

举个例子:

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
public class EnumerationMain {

enum Color {
RED, BLACK, BLUE
}

public static class ColorHolder {

private Color color = Color.BLACK;

}

public static void main(String[] args) throws Exception {
Class<Color> colorClass = Color.class;
System.out.println("Color class is enum:" + colorClass.isEnum());
System.out.println("Color values:" + Arrays.toString(colorClass.getEnumConstants()));
ColorHolder colorHolder = new ColorHolder();
Class<ColorHolder> holderClass = ColorHolder.class;
Field field = holderClass.getDeclaredField("color");
field.setAccessible(true);
System.out.println("Old color:" + field.get(colorHolder));
field.set(colorHolder, Color.RED);
System.out.println("New color:" + field.get(colorHolder));
}
}

运行后输出:

1
2
3
4
Color class is enum:true
Color values:[RED, BLACK, BLUE]
Old color:BLACK
New color:RED

之前写过一篇文章《JDK中枚举的底层实现》,从枚举类的字节码翻译出类的代码逻辑,这里翻出来那个例子(手机操作系统枚举)说一下:

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
public enum PhoneOsEnum {

/**
* 安卓
*/
ANDROID(1, "android"),

/**
* ios
*/
IOS(2, "ios");


private final Integer type;
private final String typeName;

PhoneOsEnum(Integer type, String typeName) {
this.type = type;
this.typeName = typeName;
}

public Integer getType() {
return type;
}

public String getTypeName() {
return typeName;
}
}

这个是我们使用Java的关于枚举的语法创建出来的枚举类型,是编译前我们看到的Java类文件,实际上,编译完成之后,枚举类型会变成一个普通的Java类,它有以下特点:

  • 1、枚举类型会变成一个普通Java类,这个Java类会继承java.lang.Enum,并且把自身类型作为泛型参数类型,构造函数中必定包含name(字符串类型String)、ordinal(整型int)参数,因为父类java.lang.Enum的构造要求传入这两个参数。
  • 2、所有的枚举成员属性都变成static final修饰的在第1步中提到的Java类的实例,属性的名称和原来枚举的名字一致,实例在静态代码块中创建。
  • 3、新增了一个static final修饰的第1步中提到的Java类的数组实例,名称为$VALUES,此数组在静态代码块中创建,基于此数组还新增了一个静态方法values(),此方法就是直接返回数组的克隆。

也就是上面提到的PhoneOsEnum在编译完成之后会变成:

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
public final class PhoneOsEnumeration extends Enum<PhoneOsEnumeration> {

public PhoneOsEnumeration(String name, int ordinal, Integer type, String typeName) {
super(name, ordinal);
this.type = type;
this.typeName = typeName;
}

public Integer getType() {
return type;
}

public String getTypeName() {
return typeName;
}

public static PhoneOsEnumeration[] values() {
return $VALUES.clone();
}

public static PhoneOsEnumeration valueOf(String name) {
return Enum.valueOf(PhoneOsEnumeration.class, name);
}

private final Integer type;
private final String typeName;
public static final PhoneOsEnumeration ANDROID;
public static final PhoneOsEnumeration IOS;
private static final PhoneOsEnumeration[] $VALUES;

static {
ANDROID = new PhoneOsEnumeration("ANDROID", 0, 1, "android");
IOS = new PhoneOsEnumeration("IOS", 1, 2, "ios");
$VALUES = new PhoneOsEnumeration[]{ANDROID, IOS};
}
}

实际上,如果你直接编写一个Java类去继承java.lang.Enum会编译报错,也就是Java希望把枚举的行为和特性交由自身控制而不是开发者去控制,从编译层面控制枚举的类型安全。如果细心一点会发现,枚举中valueOf(String name)也是由java.lang.Class提供的,追溯到最里层是T[] java.lang.Class#getEnumConstants()方法,其实有可能在构造$VALUES属性的时候也是用到这个方法,这一点就没有深究下去,编译层面的东西可能会牵涉很多方面的知识,还没有到达那种水平。

小结

数组和枚举在Java中的使用频率也是比较高的,特别是算法或者框架中,本文尝试从反射角度介绍这两个类型的使用方式,掌握它们对数组或者枚举的使用有很大的帮助。

(本文完 e-2018122-c-1-d r-2018124-c-1-d)

深入分析Java反射(一)-核心类库和方法

前提

Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。

本文主要介绍反射的基本概念以及核心类ClassConstructorMethodFieldParameter的常用方法。

本文极长,请准备一个使自己舒服的姿势阅读。

什么是反射

反射(Reflection)是一种可以在运行时检查和动态调用类、构造、方法、属性等等的编程语言的能力,甚至可以不需要在编译期感知类的名称、方法的名称等等。Oracle关于Java反射的官方教程中指出反射是由应用程序使用,用于检查或修改在Java虚拟机中运行的应用程序的运行时行为,这是一个相对高级的功能,需要由掌握Java语言基础知识的开发者使用

反射的优点有很多,前面提到可以检查或修改应用程序的运行时行为、抑制修饰符限制直接访问私有属性等等,这里主要列举一下它的缺点:

  • 性能开销:由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能低于非反射操作,应避免在性能敏感应用程序中频繁调用反射操作代码片段。
  • 安全限制:反射需要运行时权限,不能在安全管理器(security manager)下进行反射操作。
  • 代码可移植性:反射代码打破了抽象,反射的类库有可能随着平台(JDK)升级发生改变,反射代码中允许执行非反射代码的逻辑例如允许访问私有字段,这些问题都有可能影响到代码的可移植性。

JDK中对和反射相关的类库集中在java.lang.reflect包和java.lang包中,java.lang.reflect包和java.lang包是开发者可以直接使用的,部分java.lang.reflect包中接口的实现类存放在sun.reflect包中,一般情况下sun包下的类库有可能跟随平台升级发生改变,一般尽量少用,否则有可能因为JDK升级导致原来的代码无法正常运行。还有部分反射相关的类库存放在jdk.internal.reflect包中,这个包是JDK内部使用的包,一般也不建议滥用其中的类库。可以理解为java.lang.reflect包和java.lang包中的类库就是面向开发者的类库。

图解反射核心类的体系

java.lang.reflect包反射核心类有核心类ClassConstructorMethodFieldParameter,它们的基础体系如下:

java.lang.Class类继承体系:

j-r-1.png

java.lang.reflect.Constructor类继承体系:

j-r-2.png

java.lang.reflect.Method类继承体系:

j-r-3.png

java.lang.reflect.Field类继承体系:

j-r-4.png

java.lang.reflect.Parameter类继承体系:

j-r-5.png

由它们的类继承图可以看出:

  • Class、Constructor、Method、Field、Parameter共有的父接口是AnnotatedElement。
  • Constructor、Method、Field共有的父类是AnnotatedElement、AccessibleObject和Member。
  • Constructor、Method共有的父类是AnnotatedElement、AccessibleObject、Member、GenericDeclaration和Executable。

下面会先简单分析AnnotatedElementAccessibleObjectMemberGenericDeclarationExecutable几个类提供的功能,然后重点分析ClassConstructorMethodFieldParameter的常用方法。

这里先说一个规律,在Class中,getXXX()方法和getDeclearedXXX()方法有所区别。注解类型Annotation的操作方法例外,因为基于注解的修饰符必定是public的:

  • getDeclaredMethod(s):返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。对于获取Method对象,Method[] methods = clazz.getDeclaredMethods();返回的是clazz本类所有修饰符(public、default、private、protected)的方法数组,但是不包含继承而来的方法。
  • getMethod(s):返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。对于获取Method对象,Method[] methods = clazz.getMethods();表示返回clazz的父类、父类接口、本类、本类接口中的全部修饰符为public的方法数组。
  • getDeclaredField(s)和getField(s)、getDeclaredConstructor(s)和getConstructor(s)同上。
  • getDeclaredAnnotation(s):返回直接存在于此元素上的所有注解,此方法将忽略继承的注解,准确来说就是忽略@Inherited注解的作用。
  • getAnnotation(s):返回此元素上存在的所有注解,包括继承的所有注解。

如果想获取一个类的所有修饰符的方法,包括所有父类中的方法,那么建议递归调用getDeclaredMethods()(所谓递归调用就是一直追溯目标类的父类递归调用getDeclaredMethods()方法直到父类为Object类型,这个思路可以参考Spring框架中的相关工具类)。获取一个类的所有Field、Constructor也可以类似操作,可以参考或者直接使用Spring中的工具类ReflectionUtils的相关方法。@Inherited元注解是一个标记注解,@Inherited阐述了某个被标注的Annotation类型是可以被继承的,详细的在分析AnnotatedElement的时候再展开。

Type接口

java.lang.reflect.Type接口是Java中所有类型的共同父类,这些类型包括原始类型、泛型类型、数组类型、类型变量和基本类型,接口定义如下:

1
2
3
4
5
6
public interface Type {

default String getTypeName() {
return toString();
}
}

AnnotatedElement接口

AnnotatedElement是一个接口,它定义的方法主要和注解操作相关,例如用于判断注解的存在性和获取注解等等。

方法 功能
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断指定的注解类型在当前的实例上是否存在
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 获取当前实例上指定注解类型的注解实例,不存在时返回null
Annotation[] getAnnotations() 获取当前实例上所有注解实例,包括继承获得的注解,不存在则返回长度为0的数组
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) 获取当前实例上指定注解类型的注解实例,不包括继承获得的注解,不存在则返回长度为0的数组
<T extends Annotation> T[] getDeclaredAnnotations(Class<T> annotationClass) 获取当前实例上所有的注解实例,不包括继承获得的注解,不存在则返回长度为0的数组
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) 在不使用@Repeatable的时候,功能和getDeclaredAnnotations方法一致,如果使用了@Repeatable,则合并解析@Repeatable后的结果
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) 如果指定annotationClass注解类型可继承(使用了@Inherited),那么递归调用getDeclaredAnnotationsByType

举个简单例子:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class Main {

public static void main(String[] args) {
Class<?> clazz = Sub.class;
System.out.println("-----getAnnotations-----");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.toString());
}
System.out.println("-----getDeclaredAnnotation-->SupperAnnotation-----");
SupperAnnotation declaredSupperAnnotation = clazz.getDeclaredAnnotation(SupperAnnotation.class);
System.out.println(declaredSupperAnnotation);
System.out.println("-----getAnnotation-->SupperAnnotation-----");
SupperAnnotation supperAnnotation = clazz.getAnnotation(SupperAnnotation.class);
System.out.println(supperAnnotation);
System.out.println("-----getDeclaredAnnotation-->SubAnnotation-----");
SubAnnotation declaredSubAnnotation = clazz.getDeclaredAnnotation(SubAnnotation.class);
System.out.println(declaredSubAnnotation);
System.out.println("-----getDeclaredAnnotationsByType-->SubAnnotation-----");
SubAnnotation[] declaredSubAnnotationsByType = clazz.getDeclaredAnnotationsByType(SubAnnotation.class);
for (SubAnnotation subAnnotation : declaredSubAnnotationsByType) {
System.out.println(subAnnotation);
}
System.out.println("-----getDeclaredAnnotationsByType-->SupperAnnotation-----");
SupperAnnotation[] declaredSupperAnnotationsByType = clazz.getDeclaredAnnotationsByType(SupperAnnotation.class);
for (SupperAnnotation supperAnnotation1 : declaredSupperAnnotationsByType) {
System.out.println(supperAnnotation1);
}
System.out.println("-----getAnnotationsByType-->SupperAnnotation-----");
SupperAnnotation[] supperAnnotationsByType = clazz.getAnnotationsByType(SupperAnnotation.class);
for (SupperAnnotation supperAnnotation2 : supperAnnotationsByType) {
System.out.println(supperAnnotation2);
}
}


@SupperAnnotation
private static class Supper {

}

@SubAnnotation
private static class Sub extends Supper {

}

@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Target(ElementType.TYPE)
private @interface SupperAnnotation {

String value() default "SupperAnnotation";
}

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
private @interface SubAnnotation {

String value() default "SubAnnotation";
}
}

运行后输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-----getAnnotations-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotation-->SupperAnnotation-----
null
-----getAnnotation-->SupperAnnotation-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)
-----getDeclaredAnnotation-->SubAnnotation-----
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotationsByType-->SubAnnotation-----
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotationsByType-->SupperAnnotation-----
-----getAnnotationsByType-->SupperAnnotation-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)

可以尝试注释掉@Inherited再运行一次,对比一下结果。如果注释掉@Inherited,从Sub这个类永远无法获取到它的父类Supper中的@SupperAnnotation。Class、Constructor、Method、Field、Parameter都实现了AnnotatedElement接口,所以它们都具备操作注解的功能。

Member接口

Member接口注解提供成员属性的一些描述,主要提供的方法如下:

方法 功能
Class<?> getDeclaringClass() 获取声明的Class对象,也就是获取当前Member实例的来源Class对象
String getName() 获取实例的名称,对于Constructor返回全类名,对于Method返回方法名,对于Field返回属性名
int getModifiers() 获取实例的修饰符
boolean isSynthetic() 是否合成的

这些方法里面除了isSynthetic()都比较好理解。synthetic总的来说,是由编译器引入的字段、方法、类或其他结构,主要用于JVM内部使用,为了遵循某些规范而作的一些小技巧从而绕过这些规范,有点作弊的感觉,只不过是由编译器光明正大为之,一般开发者是没有权限的(但事实上有时候还是能被利用到的)。下面这个例子参考自synthetic Java合成类型:

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
public class Main {

private static class Inner {
}
static void checkSynthetic (String name) {
try {
System.out.println (name + " : " + Class.forName (name).isSynthetic ());
} catch (ClassNotFoundException exc) {
exc.printStackTrace (System.out);
}
}
public static void main(String[] args) throws Exception
{
new Inner ();
checkSynthetic ("com.fcc.test.Main");
checkSynthetic ("com.fcc.test.Main$Inner");
checkSynthetic ("com.fcc.test.Main$1");
}
}
//打印结果:
com.fcc.test.Main : false
com.fcc.test.Main$Inner : false
com.fcc.test.Main$1 : true
//编译结果,生成三个class文件: Main.class/Main$Inner/Main$1.class
// $FF: synthetic class
class Main$1 {
}

Inner这个内部类是私有的,私有内部类。拥有内部类的类编译后内外部类两者没有关系,那么私有内部类编译后默认是没有对外构造器的(如果以上代码中在Inner手动给一个public的构造器,Main$1是不会出现的),但是我们又知道,外部类是可以引用内部类的,那么编译后,又是两个毫无关系的类,一个类没对外构造器,但另一个类确实是有对这个类的实例对象权限(这里就是重点,内部类哪怕没有public构造器,外部类都有实例化内部类对象的权限)的,这种情况下编译器就会生成一个合成类,也就是Main$1,一个什么也没有的空类(是的,什么也没有,连构造器都没有)。但到这里,仍然不明白其实现原理是怎么样的,原先以为合成类是那个内部类的副本,外部类访问内部类,在编译器认为只是和合成类交互,只是合成类只有外部类有权限访问,但是事实上,不管内部类怎么变化,合成类只是一个空的类,有点类似标记作用(真正作用却是不得而知)。

AccessibleObject类

AccessibleObject是一个普通Java类,实现了AnnotatedElement接口,但是对应AnnotatedElement的非默认方法的实现都是直接抛异常,也就是AnnotatedElement的接口方法必须由AccessibleObject的子类去实现,个人认为AccessibleObject应该设计为抽象类。AccessibleObject在JDK1.1的时候已经存在,在JDK9的时候被改进过,添加了一些新的方法,下面列举一下常用的方法:

方法 功能
void setAccessible(boolean flag) 设置实例是否可以访问,如果设置为true,可以抑制修饰符,直接进行访问
boolean isAccessible() 返回实例是否可以访问,实际上这个值并不准确,它只有在setAccessible被调用的时候才会更新
boolean trySetAccessible() 功能类似于setAccessible(boolean flag),返回值决定是否抑制修饰符成功
static void setAccessible(AccessibleObject[] array, boolean flag) setAccessible(boolean flag)的批量操作方法

一般而言,我们需要通过getModifiers()方法判断修饰符是否public,如果是非public,则需要调用setAccessible(true)进行修饰符抑制,否则会因为无权限访问会抛出异常。

GenericDeclaration接口

GenericDeclaration接口继承自AnnotatedElement,它的源码如下:

1
2
3
4
public interface GenericDeclaration extends AnnotatedElement {

public TypeVariable<?>[] getTypeParameters();
}

新增了一个方法getTypeParameters()用于返回类型变量TypeVariable数组,这里的TypeVariable是类型变量,它的定义如下:

1
2
3
4
5
6
7
8
9
10
public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
//获得泛型的类型(Type)上限数组,若未明确声明上边界则默认为Object
Type[] getBounds();
//获取声明该类型变量实体(即获得类、方法或构造器名)
D getGenericDeclaration();
//获得泛型参数的字面量名称,即K、V、E之类名称
String getName();
//获得泛型的注解类型(AnnotatedType)上限数组,若未明确声明上则为长度为0的空数组
AnnotatedType[] getAnnotatedBounds();
}

后面的文章介绍泛型的时候再展开。

Executable类

Executable是一个抽象类,它继承自AccessibleObject,实现了MemberGenericDeclaration接口。Executable的实现类是MethodConstructor,它的主要功能是从MethodConstructor抽取出两者可以共用的一些方法例如注解的操作,参数的操作等等,这里不详细展开。

Modifier

Modifier主要提供一系列的静态方法,用于判断基于int类型的修饰符参数的具体类型,这个修饰符参数来源于Class、Constructor、Method、Field、Parameter的getModifiers()方法。下面介绍一下Modifier的主要方法:

方法 功能
static boolean isAbstract(int mod) 整数modifier参数是否包括abstract修饰符
static boolean isFinal(int mod) 整数modifier参数是否包括final修饰符
static boolean isInterface(int mod) 整数modifier参数是否包括interface修饰符
static boolean isNative(int mod) 整数modifier参数是否包括native修饰符
static boolean isPrivate(int mod) 整数modifier参数是否包括private修饰符
static boolean isProtected(int mod) 整数modifier参数是否包括protected修饰符
static boolean isPublic(int mod) 整数modifier参数是否包括public修饰符
static boolean isStatic(int mod) 整数modifier参数是否包括static修饰符
static boolean isStrict(int mod) 整数modifier参数是否包括strictfp修饰符
static boolean isSynchronized(int mod) 整数modifier参数是否包括synchronized修饰符
static boolean isTransient(int mod) 整数modifier参数是否包括transient修饰符
static boolean isVolatile(int mod) 整数modifier参数是否包括volatile修饰符
static boolean toString(int mod) 返回描述指定修饰符中的访问修饰符标志的字符串

Class类

Class实现了SerializableGenericDeclarationTypeAnnotatedElement接口,它提供了类型判断、类型实例化、获取方法列表、获取字段列表、获取父类泛型类型等方法。下面主要介绍一下它的主要方法:

方法 功能
Class<?> forName(String className) 传入全类名创建Class实例
T newInstance() 通过当前的Class实例进行实例化对象,返回的就是新建的对象
int getModifiers() native方法,返回当前Class的修饰符
String getName() 返回类名称,虚拟机中类名表示
String getCanonicalName() 返回类名称,便于理解的类名表示
String getSimpleName() 返回类名称,源代码中给出的底层类的简单名称
Package getPackage() 返回类的包属性
String getPackageName() 返回类的包路径名称
String toGenericString() 返回描述此Class的字符串,其中包括类型参数的字面量
TypeVariable<Class<T>>[] getTypeParameters() 获取类定义泛型的类型变量
Class<?>[] getClasses() 获取所有的修饰符为public的成员Class,包括父类
Class<?>[] getDeclaredClasses() 获取本类所有修饰符的成员Class,不包括父类
Constructor<?>[] getConstructors() 获取所有的修饰符为public的构造器,包括父类
Constructor<T> getConstructor(Class<?>... parameterTypes) 获取参数类型匹配的修饰符为public的构造器,包括父类
Constructor<?>[] getDeclaredConstructors() 获取本类所有修饰符的构造器,不包括父类
Constructor<T>[] getDeclaredConstructor(Class<?>... parameterTypes) 获取本类参数类型匹配的所有修饰符的构造器,不包括父类
Method[] getMethods() 获取本类所有的修饰符为public的方法列表,包括父类
Method[] getDeclaredMethods() 获取本类所有修饰符的方法列表,不包括父类
Method getMethod(String name, Class<?>... parameterTypes) 通过指定方法名和参数类型获取本类修饰符为public的方法,包括父类
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 通过指定方法名和参数类型获取本类不限修饰符的方法,不包括父类
Field[] getFields() 获取本类所有的修饰符为public的属性列表,包括父类
Field[] getDeclaredFields() 获取本类所有修饰符的属性列表,不包括父类
Field getField(String name) 通过指定属性名名获取本类修饰符为public的属性,包括父类
Field getDeclaredField(String name) 通过指定属性名获取本类不限修饰符的属性,不包括父类
Class<?>[] getInterfaces() 获取类实现的所有接口的Class数组
Type[] getGenericInterfaces() 获取类实现的所有泛型参数接口的Type数组
Class<? super T> getSuperclass() 获取当前类的父类的Class,如果当前类是Object、接口、基本数据类型(primitive)或者void,则返回null
Type getGenericSuperclass() 获取当前类的泛型参数父类的Type,如果当前类是Object、接口、基本数据类型(primitive)或者void,则返回null
native boolean isInstance(Object obj) 判断传入的object是否当前类的实例
native boolean isAssignableFrom(Class<?> cls) 判断传入的Class对象是否和当前类相同,或者是否当前类的超类或超接口
native boolean isInterface() 判断当前类是否接口
native boolean isArray() 判断当前类是否数组
native boolean isPrimitive() 判断当前类是否基本数据类型
boolean isAnnotation() 判断当前类是否注解类型
boolean isSynthetic() 判断当前类是否复合
native Class<?> getComponentType() 如果当前类是数组,返回数组元素的类型
Class<?> getEnclosingClass() 返回一个类,当前类(一般是成员类)在这个类(封闭类,相对于内部类的外部类或者说外面一层)中定义
Constructor<?> getEnclosingConstructor() 返回构造器,当前类是在这个构造函数中定义
Method getEnclosingMethod() 返回方法,当前类是在这个方法中定义
Module getModule() 返回模块,JDK9新增方法

getName()getCanonicalName()getSimpleName()都是用于获取类的名称,但是有所区别,下面举个列子说明一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {

public static void main(String[] args) {
Supper<String, List<Integer>> supper = new Supper<>();
Class<?> clazz = supper.getClass();
System.out.println("name->" + clazz.getName());
System.out.println("canonicalName->" + clazz.getCanonicalName());
System.out.println("simpleName->" + clazz.getSimpleName());
System.out.println("======================================");
String[][] strings = new String[1][1];
System.out.println("name->" + strings.getClass().getName());
System.out.println("canonicalName->" + strings.getClass().getCanonicalName());
System.out.println("simpleName->" + strings.getClass().getSimpleName());
}

private static class Supper<K, V> {
private K key;
private V value;
//省略setter和getter方法
}
}

运行后输出结果:

1
2
3
4
5
6
7
name->club.throwable.reflect.Main$Supper
canonicalName->club.throwable.reflect.Main.Supper
simpleName->Supper
======================================
name->[[Ljava.lang.String;
canonicalName->java.lang.String[][]
simpleName->String[][]

简单理解为:

  • getName():用于获取类在Java虚拟机中的类名表示。
  • getCanonicalName():用于获取全类名,包括包路径,包路径以点号分隔。
  • getSimpleName():用于获取类名,不包括包路径。

下面再举一个例子通过类名进行实例化对象和操作,从例子可以看到,实例化对象可以不依赖new关键字,这就是反射的强大之处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main3 {

public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("club.throwable.reflect.Main3$Supper");
Supper supper = (Supper) clazz.newInstance();
System.out.println(supper.sayHello("throwable"));
}

public static class Supper {

public String sayHello(String name) {
return String.format("%s say hello!", name);
}
}
}

这里需要注意一点,Class.forName方法只能使用在修饰符为public的类上,如果使用在其他修饰符类上会抛出异常(IllegalAccessException),那么,如果上面的Supper类的修饰符修改为private,怎么样才能正常实例化它?这个问题将会在下面分析Constructor的时候得到解决。另外,这里的Class.forName方法不是获取Class实例的唯一方式,总结有以下三种方式:

  • 1、使用类的字面量"类名.class"。类字面常量使得创建Class对象的引用时不会自动地初始化该对象,而是按照之前提到的加载,链接,初始化三个步骤,这三个步骤是个懒加载的过程,不使用的时候就不加载。
  • 2、使用Class.forName(全类名);方法。
  • 3、使用实例的getClass()方法。getClass()是所有的对象都能够使用的方法,因为getClass()方法是Object类的方法,所有的类都继承了Object,因此所有类的对象也都具有getClass()方法。

一般来说,使用"类名.class",这样做即简单安全又比较高效。因为在编译时就会受到检查,因此不需要置于try语句块中,并且它根除了对forName()方法的调用(forName()方法是一个耗时比较多的方法),所以相对比较高效。

最后,分析一下这几个比较难懂的方法getEnclosingClass()getEnclosingConstructor()getEnclosingMethod()

  • getEnclosingClass():返回一个类,当前类(一般是成员类)在这个类(一般叫封闭类,相对于内部类的外部类或者说外面一层)中定义。
  • getEnclosingConstructor():返回构造器,当前类是在这个构造函数中定义。
  • getEnclosingClass():返回方法,当前类是在这个方法中定义。

我们在新建一个类的时候,这个类可以使另一个类中定义的成员类、构造方法中定义的内部类、方法中定义的内部类。可以通过当前的类反向获取定义当前的类的类、构造或者方法,这三种情况对应上面三个方法。举个例子:

getEnclosingClass()方法使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main5 {

public static void main(String[] args) throws Exception{
Class<Outter.Inner> clazz = Outter.Inner.class;
Class<?> enclosingClass = clazz.getEnclosingClass();
System.out.println(enclosingClass.getName());
}
// Inner类是Outter类的成员类
public static class Outter {

public static class Inner {

}
}
}

输出结果:

1
org.throwable.inherited.Main5$Outter

在这里,Inner就是当前定义的类,它是Outter的静态成员类,或者说Outter是Inner的封闭类,通过Inner的Class的getEnclosingClass()方法获取到的就是Outter的Class实例。

getEnclosingConstructor()方法使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main6 {

public static void main(String[] args) throws Exception {
Outter outter = new Outter();
}

public static class Outter {

//Outter的无参数构造器
public Outter() {
//构造中定义的内部类
class Inner {

}

Class<Inner> innerClass = Inner.class;
Class<?> enclosingClass = innerClass.getEnclosingClass();
System.out.println(enclosingClass.getName());
Constructor<?> enclosingConstructor = innerClass.getEnclosingConstructor();
System.out.println(enclosingConstructor.getName());
}
}
}

输出结果:

1
2
org.throwable.inherited.Main6$Outter
org.throwable.inherited.Main6$Outter

在这里,Inner是Outter的无参数构造里面定义的构造内部类,它也只能在Outter的无参数构造里面使用,通过Inner的Class的getEnclosingConstructor()方法获取到的就是Outter的无参数构造。

getEnclosingMethod()方法使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main7 {

public static void main(String[] args) throws Exception {
Outter outter = new Outter();
outter.print();
}

public static class Outter {

public void print(){
//方法print中定义的内部类
class Inner {

}

Class<Inner> innerClass = Inner.class;
Class<?> enclosingClass = innerClass.getEnclosingClass();
System.out.println(enclosingClass.getName());
Method enclosingMethod = innerClass.getEnclosingMethod();
System.out.println(enclosingMethod.getName());
}
}
}

输出结果:

1
2
org.throwable.inherited.Main7$Outter
print

在这里,Inner是Outter的print方法里面定义的方法内部类,它也只能在Outter的print方法里面使用,通过Inner的Class的getEnclosingMethod()方法获取到的就是Outter的print方法。这种方式可能不常用,但是可以在某版本的spring-jdbc的JdbcTemplate的源码中看到类似的类定义逻辑。

前面介绍过getXXX()方法和getDeclearedXXX()方法有所区别,这里做个对比表格:

Class中获取Field列表的方法:

Class中的API 获取所有的Field 包括继承的Field 包括私有的Field
getDeclaredField() N N Y
getField() N Y N
getDeclaredFields() Y N Y
getFields() Y Y N

Class中获取Method列表的方法:

Class中的API 获取所有的Method 包括继承的Method 包括私有的Method
getDeclaredMethod() N N Y
getMethod() N Y N
getDeclaredMethods() Y N Y
getMethods() Y Y N

Class中获取Constructor列表的方法:

Class中的API 获取所有的Constructor 包括私有的Constructor
getDeclaredConstructor() N Y
getConstructor() N N
getDeclaredConstructors() Y Y
getConstructors() Y N

Constructor类

Constructor用于描述一个类的构造函数。它除了能获取到构造的注解信息、参数的注解信息、参数的信息之外,还有一个很重要的作用是可以抑制修饰符进行实例化,而Class的实例化方法newInstance只能实例化修饰符为public的类。Constructor的主要方法如下:

方法 功能
Class<T> getDeclaringClass() 获取当前构造的定义类
String getName() 获取当前构造的名称
int getModifiers() 获取当前构造的修饰符
String toGenericString() 返回描述此构造的字符串,其中包括类型参数的字面量
TypeVariable<Constructor<T>>[] getTypeParameters() 获取类定义泛型参数的类型变量
Class<?>[] getExceptionTypes() 获取当前构造异常类型数组,如果不存在则返回一个长度为0的数组
Type[] getGenericExceptionTypes() 获取当前构造异常类型数组的泛型类型,如果不存在则返回一个长度为0的数组
Type[] getGenericParameterTypes() 获取当前构造参数的泛型类型,如果不存在则返回一个长度为0的数组
Annotation[][] getParameterAnnotations() 获取当前构造参数的注解数组,这里是二维数组的原因是一个参数可以使用多个注解
int getParameterCount() 获取当前构造参数的数量
Class<?>[] getParameterTypes() 获取当前构造参数的Class数组
boolean isSynthetic() 当前构造是否复合的
boolean isVarArgs() 当前构造是否使用不定参数
T newInstance(Object…initargs) 使用此构造对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例
Parameter[] getParameters() 返回此构造对象的参数Parameter数组,如果没有则返回一个长度为0的数组
void setAccessible(boolean flag) 抑制构造访问修饰符的权限判断

下面我们举个例子说明使用构造实例化对象可以抑制修饰符访问权限控制的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main8 {

public static void main(String[] args) throws Exception{
Class<Supper> supperClass = Supper.class;
Constructor<Supper> constructor = supperClass.getDeclaredConstructor();
constructor.setAccessible(Boolean.TRUE);
Supper supper = constructor.newInstance();
supper.sayHello("throwable");
}

private static class Supper {

public void sayHello(String name) {
System.out.println(String.format("%s say hello!", name));
}
}
}

输出结果:

1
throwable say hello!

这就是为什么一些IOC容器的实现框架中实例化类的时候优先依赖于无参数构造的原因,如果使用Class#newInstance方法,上面的代码调用逻辑会抛异常。

Method类

Method用于描述一个类的方法。它除了能获取方法的注解信息,还能获取方法参数、返回值的注解信息和其他信息。Method常用的方法如下:

方法 功能
Class<?> getDeclaringClass() 获取方法对应的Class
Object getDefaultValue() 获取方法上的注解成员的默认值
Class<?>[] getExceptionTypes() 获取方法上的异常类型数组,如果没有则返回一个长度为0的数组
Type[] getGenericExceptionTypes() 获取方法上的异常泛型类型Type数组,如果没有则返回一个长度为0的数组
Parameter[] getParameters() 返回方法的参数Parameter数组,如果没有则返回一个长度为0的数组
int getParameterCount() 返回方法的参数的数量
Class<?>[] getParameterTypes() 返回方法的参数的类型Class数组,如果没有则返回一个长度为0的数组
Annotation[][] getParameterAnnotations() 返回方法的注解Annotation数组,这里使用二维数组的原因是一个参数可以使用多个注解
TypeVariable<Method>[] getTypeParameters() 返回方法的泛型参数的类型变量
Type[] getGenericParameterTypes() 返回方法参数的泛型类型Type数组
Class<?> getReturnType() 返回方法的返回值的类型Class
Type getGenericReturnType() 返回方法的返回值的泛型类型Type
AnnotatedType getAnnotatedReturnType() 获取方法返回值的注解类型实例AnnotatedType
boolean isBridge() 是否桥方法
boolean isDefault() 是否接口的默认方法
boolean isSynthetic() 是否复合的
boolean isVarArgs() 是否使用了不定参数
String toGenericString() 返回方法带有泛型字面量的描述字符串
String getName() 返回方法的名称
int getModifiers() 返回方法的修饰符
Object invoke(Object obj, Object… args) 对带有指定参数的指定对象调用由此方法对象表示的底层方法
void setAccessible(boolean flag) 抑制方法访问修饰符的权限判断

关注其中的invoke(Object obj, Object... args)方法,第一个是要调用这个方法的对象,剩下的方法的参数,返回值就是该方法执行的返回值。如果方法的修饰符不是public,在调用invoke方法前需要调用setAccessible(boolean flag)抑制方法访问修饰符的权限判断,否则会抛出异常。举个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main10 {

public static void main(String[] args) throws Exception{
Class<Supper> supperClass = Supper.class;
Supper supper = supperClass.newInstance();
Method sayHello = supperClass.getDeclaredMethod("sayHello", String.class);
sayHello.setAccessible(Boolean.TRUE);
sayHello.invoke(supper,"throwable");
}

public static class Supper{

private void sayHello(String name){
System.out.println(String.format("%s say hello!", name));
}
}
}

输出结果:

1
throwable say hello!

Field类

Field类用来描述一个类里面的属性或者叫成员变量,通过Field可以获取属性的注解信息、泛型信息,获取和设置属性的值等等。Field的主要方法如下:

方法 功能
String getName() 返回该属性的名称
int getModifiers() 返回该属性的修饰符
Class<?> getType() 返回该属性的类型Class
Class<?> getParameterizedType() 返回该属性的泛型类型Type
boolean isSynthetic() 该属性是否复合的
boolean isEnumConstant() 该属性是否枚举类型的元素
Object get(Object obj) 通过对象实例获取该属性的值
void set(Object obj,Object value) 通过对象实例设置该属性的值
void setAccessible(boolean flag) 抑制属性访问修饰符的权限判断

这里忽略了注解以及Field实现了FieldAccessor接口中的getBooleansetBoolean等方法。下面举个例子说明一下Field的用法:

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
public class Main12 {

public static void main(String[] args) throws Exception {
Class<Supper> supperClass = Supper.class;
Supper supper = supperClass.newInstance();
Method sayHello = supperClass.getDeclaredMethod("sayHello");
sayHello.setAccessible(Boolean.TRUE);
Field name = supperClass.getDeclaredField("name");
name.setAccessible(Boolean.TRUE);
name.set(supper,"throwable");
System.out.println("Field get-->" + name.get(supper));
sayHello.invoke(supper);
name.set(supper, "throwable-10086");
System.out.println("Field get-->" + name.get(supper));
sayHello.invoke(supper);
}

public static class Supper {

private String name;

private void sayHello() {
System.out.println(String.format("%s say hello!", name));
}
}
}

输出结果:

1
2
3
4
Field get-->throwable
throwable say hello!
Field get-->throwable-10086
throwable-10086 say hello!

Parameter类

Parameter用于描述Method或者Constructor的参数,主要是用于获取参数的名称。因为在Java中没有形式参数的概念,也就是参数都是没有名称的。Jdk1.8新增了Parameter用来填补这个问题,使用javac编译器的时候加上-parameters参数的话,会在生成的.class文件中额外存储参数的元信息,这样会导致.class文件的大小增加。当你输入javac -help的时候,你会看到-parameters这个选项。获取Parameter的方法是Method或者Constructor的父类Executable的getParamaters方法。一般而言,Parameter是用于获取参数名称的后备方案,因为Jdk1.8之前没有这个类,并且即使使用了Jdk1.8如果javac编译器的时候没有加上-parameters参数的话,通过Parameter获取到的参数名称将会是"arg0"、“arg1”…"argn"类似的没有意义的参数名称。一般框架中使用其他方法解析方法或者构造器的参数名称,参考Spring的源码,具体是LocalVariableTableParameterNameDiscoverer,是使用ASM去解析和读取类文件字节码,提取参数名称。Parameter的主要方法如下:

方法 功能
String getName() 返回该参数的名称
int getModifiers() 返回该参数的修饰符
Class<?> getType() 返回该参数的类型Class
Class<?> getParameterizedType() 返回该参数的泛型类型Type
boolean isNamePresent() 该参数的名称是否保存在class文件中,需要编译时加参数-parameters
boolean isImplicit() 该参数是否隐式声明
boolean isSynthetic() 该参数是否复合的
boolean isVarArgs() 该参数是否不定参数

这里举个例子,编译时候添加参数-parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main11 {

public static void main(String[] args) throws Exception {
Class<Supper> supperClass = Supper.class;
Method sayHello = supperClass.getDeclaredMethod("sayHello", String.class);
sayHello.setAccessible(Boolean.TRUE);
Parameter[] parameters = sayHello.getParameters();
for (Parameter parameter : parameters) {
System.out.println("isNamePresent->" + parameter.isNamePresent());
System.out.println("isImplicit->" + parameter.isImplicit());
System.out.println("getName->" + parameter.getName());
System.out.println("=====================");
}

}

public static class Supper {

private void sayHello(String name) {
System.out.println(String.format("%s say hello!", name));
}
}
}

输出结果:

1
2
3
4
isNamePresent->true
isImplicit->false
getName->name
=====================

如果不设置编译参数-parameters,会输出下面的结果:

1
2
3
4
isNamePresent->false
isImplicit->false
getName->arg0
=====================

小结

这篇文章开篇对反射的基本进行介绍,后面花大量篇幅列举了相关类库的API和API使用,掌握这些类库,才能轻松地进行反射编程。

(本文完 e-2018122)