Java-反射

Java/反射

Java/反射1

反射

简单例子:

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
try {
Class cls = Class.forName("com.jasonwu.Test");
//获取构造方法
Constructor[] publicConstructors = cls.getConstructors();
//获取全部构造方法
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
//获取公开方法
Method[] methods = cls.getMethods();
//获取全部方法
Method[] declaredMethods = cls.getDeclaredMethods();
//获取公开属性
Field[] publicFields = cls.getFields();
//获取全部属性
Field[] declaredFields = cls.getDeclaredFields();
Object clsObject = cls.newInstance();
Method method = cls.getDeclaredMethod("getModule1Functionality");
Object object = method.invoke(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}

Java反射效率低的主要原因(结论):

  • Method#invoke方法会对参数做封装和解封操作
  • 需要检查方法可见性
  • 需要校验参数
  • 反射方法难以内联
  • JIT无法优化

Java反射原理–获取要反射的方法

反射的使用

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RefTest{
public static void main(String[] args){
try{
Class clazz = Class.forName("com.ab.java.RefTest");
Object refTest = Clazz.newInstance();
Method method = clazz.getDeclaredMethod("refMethod");
method.invoke(refTest);
} catch (Exception e){
e.printStackTrace();
}
}

public void refMethod(){
}
}

获取反射方法时有俩方法,getMethodgetDeclaredMethod

getMethod / getDeclaredMethod

先整体看下 getMethod/getDeclaredMethod 的实现

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 Class {
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法权限
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}

@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法权限
checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
}

俩方法有两个区别:

  1. checkMemberAccess传入的参数不一样
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Member{
/**
* Identifies the set of all public members of a class or interface,
* including inherited members.
*/
public static final int PUBLIC = 0;

/**
* Identifies the set of declared members of a class or interface.
* Inherited members are not included.
*/
public static final int DECLARED = 1;
}

PUBLIC会包含所有的public方法,包括父类的方法。DECLARED会包括所有自己定义的方法,publicprotectedprivate都在此,不包括父类的方法。

  1. 获取方法不同,getMethod调用 getMethod0getDeclaredMethod调用privateGetDeclaredMethods
1
2
3
4
//获取自身定义的方法,参数 publicOnly表示是否只获取公共方法
private Method[] privateGetDeclaredMethods(boolean publicOnly){
//...
}

getMethod0会递归查找父类方法,其中会调用到privateGetDeclaredMethods方法

getMethod

getMethod方法流程图

java反射getMethod方法流程图

checkMemberAccess

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Class{
private void checkMemberAccess(SecurityManager sm, int which, Class<?> caller, boolean checkProxyInterfaces){
/* Default policy allows access to all {@link Member#PUBLIC} members,
* as well as access to classes that have the same class loader as the caller.
* In all other cases, it requires RuntimePermission("accessDeclaredMembers")
* permission.
*/
final ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (which != Member.PUBLIC) {
final ClassLoader cl = getClassLoader0();
if (ccl != cl) {
sm.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
}
}
this.checkPackageAccess(sm, ccl, checkProxyInterfaces);
}
}

getMethod0

1
2
3
4
5
6
7
class Class{
private Method getMethod0(String name, Class<?>[] parameterType){
PublicMethods.MethodList res = getMethodsRecursive(name, parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,/* includeStatic */ true);
//res.getMostSpecific()返回具体的(Child)方法(Child继承自Parent)
return res == null ? null : res.getMostSpecific();
}
}

getMethodsRecursive

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
class Class{
private PublicMethods.MethodList getMethodsRecursive(String name, Class<?>[] parameterTypes, boolean includeStatic){
//1. 获取自己的public方法
Method[] methods = privateGetDeclareMethods(/* publicOnly */ true);
//2. 筛选符合条件的方法,构造MethodList对象
PublicMethods.MethodList res = PublicMethods.MethodList.filter(methods, name, parameterTypes, includeStatic);
//找到方法,直接返回
if(res != null){
return res;
}

//3. 没有找到方法,就获取其父类,递归调用 getMethodsRecursive 方法
Class<?> sc = getSuperclass();
if(sc != null){
res = sc.getMethodsRecursive(name, parameterTypes, includeStatic);
}

//4. 获取接口中对应的方法
for(Class<?> intf: getInterfaces(/* cloneArray */ false)){
res = PublicMethods.MethodList.merge(res, intf.getMethodsRecursive(name, parameterTypes, /* includeStatic */ false));
}

return res;
}
}

我们平时编写Java代码时,同一个类是不能有方法名和方法参数都相同的方法的,在JVM中一个方法签名是和返回值、方法名、方法参数三者相关的。也就是说,在JVM中可以存在方法名和方法参数都相同但是返回值不同的方法。

Java反射思维导图

sun.misc.Unsafe

例子:获取对象地址

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
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public static long addressOf(Object o){
Object[] array = new Object[]{0};
long objectAddress = -1;
try {
Class cls = Class.forName("sun.misc.Unsafe");
Field field = cls.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object unsafe = field.get(null);
Class unsafeCls = unsafe.getClass();
Method arrayBaseOffset = unsafeCls.getMethod("arrayBaseOffset", Object.class.getClass());
int baseOffset = (int) arrayBaseOffset.invoke(unsafe, Object[].class);
Method size = unsafeCls.getMethod("addressSize");
int addressSize = (int) size.invoke(unsafe);
switch (addressSize) {
case 4:
Method getInt = unsafeCls.getMethod("getInt", Object.class, long.class);
objectAddress = (long) getInt.invoke(unsafe, array, baseOffset);
break;
case 8:
Method getLong = unsafeCls.getMethod("getLong", Object.class, long.class);
objectAddress = (long) getLong.invoke(unsafe, array, baseOffset);
break;
default:
throw new Error("unsupported address size: " + addressSize);
}
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
return objectAddress;
}

深入理解sun.misc.Unsafe原理

Unsafe类中的核心方法

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
//重新分配内存
public native long reallocateMemory(long address, long bytes);

//分配内存
public native long allocateMemory(long bytes);

//释放内存
public native void freeMemory(long address);

//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);

//从一个内存块拷贝到另一个内存块
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);

//获取值,不管java的访问限制,其他有类似的getInt,getDouble,getLong,getChar等等
public native Object getObject(Object o, long offset);

//设置值,不管java的访问限制,其他有类似的putInt,putDouble,putLong,putChar等等
public native void putObject(Object o, long offset);

//从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定
public native long getAddress(long address);

//存储一个本地指针到一个给定的内存地址,如果地址不是allocateMemory方法的,结果将不确定
public native void putAddress(long address, long x);

//该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的
public native long staticFieldOffset(Field f);

//报告一个给定的字段的位置,不管这个字段是private,public还是保护类型,和staticFieldBase结合使用
public native long objectFieldOffset(Field f);

//获取一个给定字段的位置
public native Object staticFieldBase(Field f);

//确保给定class被初始化,这往往需要结合基类的静态域(field)
public native void ensureClassInitialized(Class c);

//可以获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class arrayClass);

//可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用, 可以定位数组中每个元素在内存中的位置
public native int arrayIndexScale(Class arrayClass);

//获取本机内存的页数,这个值永远都是2的幂次方
public native int pageSize();

//告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);

//定义一个类,但是不让它知道类加载器和系统字典
public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);

//锁定对象,必须是没有被锁的
public native void monitorEnter(Object o);

//解锁对象
public native void monitorExit(Object o);

//试图锁定对象,返回true或false是否锁定成功,如果锁定,必须用monitorExit解锁
public native boolean tryMonitorEnter(Object o);

//引发异常,没有通知
public native void throwException(Throwable ee);

//CAS,如果对象偏移量上的值=期待值,更新为x,返回true.否则false.类似的有compareAndSwapInt,compareAndSwapLong,compareAndSwapBoolean,compareAndSwapChar等等。
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

// 该方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。类似的方法有getIntVolatile,getBooleanVolatile等等
public native Object getObjectVolatile(Object o, long offset);

//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。
public native void park(boolean isAbsolute, long time);

//终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,也正是使用这两个方法
public native void unpark(Object thread);

//获取系统在不同时间系统的负载情况
public native int getLoadAverage(double[] loadavg, int nelems);

//创建一个类的实例,不需要调用它的构造函数、初使化代码、各种JVM安全检查以及其它的一些底层的东西。即使构造函数是私有,我们也可以通过这个方法创建它的实例,对于单例模式,简直是噩梦。
public native Object allocateInstance(Class cls) throws InstantiationException;