PropertyDescriptor类的使用及其执行过程的分析
小橘子🍊

前言

PropertyDescriptor 是位于 java.beans 包下的一个类,其注释对其的解释为: PropertyDescriptor 描述了 Java Bean 的一个属性,我们可以通过一对 getter 和 setter 方法对其进行访问。

我对其产生兴趣是因为在看 Spring 源码的过程中,发现 Spring 会通过 org.springframework.beans.BeanWrapperImpl 对其管理的 bean 进行包装,然后调用它的 org.springframework.beans.PropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues) 方法对属性进行填充,其中参数 PropertyValues 就包含 PropertyDescriptor 属性。在对属性进行填充时,实际上是先获取 PropertyValues 对象的 WriteMethod,然后调用其 invoke 方法,最终通过反射的方式完成属性设置。

举个栗子

如果是要通过 Spring 对 bean 的管理来重现上边的过程比较的麻烦,所以这里手写一个小栗子来模拟这个过程。这里只有两个类,User 为用户类,我们这里就是要对它的属性进行访问,另外一个就是 App 类,所有的逻辑都在其中实现,代码很简单,我们主要是为了观察 setter 方法被执行的过程。

User 类

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
public class User {
private String id;

private String userName;

public User(String id, String userName) {
this.id = id;
this.userName = userName;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", userName='" + userName + '\'' +
'}';
}
}

App 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class App {
public static void main(String[] args) throws Exception {
User user = new User("12", "eva");

Class<? extends User> clazz = user.getClass();

PropertyDescriptor pd = new PropertyDescriptor("userName", User.class);

Method m = pd.getWriteMethod();

m.invoke(user,"tim");

System.out.println(user);
}
}

执行结果如下:

1
User{id='12', userName='tim'} 

其实代码逻辑很简单,我们仅仅关心 user 实例的 userName 字段被修改的过程,可以看到我们的 PropertyDescriptor 对象是通过其构造函数得到的,并且传入了字段信息为 “userName”,所以我们获取到的 PropertyDescriptor 就是 “userName” 这个字段所代表的 Java Bean 的属性, Method m = pd.getWriteMethod(); 就是获取的这个字段的 setter 方法。然后我们调用 invoke 方法,这里有两个参数:此方法所在的实例和此方法的参数。因为获取到的是一个 Method 对象,它有一个特殊的字段我们要注意 java.lang.reflect.Method#methodAccessor,这里我把它的源码贴出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package sun.reflect;

import java.lang.reflect.InvocationTargetException;

/** This interface provides the declaration for
java.lang.reflect.Method.invoke(). Each Method object is
configured with a (possibly dynamically-generated) class which
implements this interface.
*/

public interface MethodAccessor {
/** Matches specification in {@link java.lang.reflect.Method} */
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}

可以看到这个这个接口的唯一一个方法的签名和 Method 类的的 invoke 方法的签名是一模一样的,而且注释中也说了每一个 Method 对象都会包含一个实现了这个接口的对象。通过查看 Method 类的 invoke 方法,可以发现它最终也就是调用了 MethodAccessor 实现类的 invoke 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}

MethodAccessor 的实现类有两个 sun.reflect.DelegatingMethodAccessorImplsun.reflect.NativeMethodAccessorImpl,通过 idea 的调试功能,可以看到 Method 包含的是 DelegatingMethodAccessorImpl 这个实现类,我们继续查看它对 invoke 方法的实现,很简单,就是直接调用了它所包含的 MethodAccessor 实现类的 invoke 方法,注意这里 MethodAccessor 的实现类变成了 NativeMethodAccessorImpl,好了那就直接看看 NativeMethodAccessorImpl 的 invoke 方法实现吧:

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 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);

可以看到在 invoke 方法中,是最后调用了 invoke0 方法,而且在 java 中有个特点,就是如果一个方法是以数字 0 结尾,那么通常这个方法就是一个本地方法,这里也不例外,最后 invoke0 就是一个本地方法。本地方法对我们就是一个黑盒,但是我们可以通过其传入的参数猜测一下他的作用。

一共三个参数, 分别是 method obj 和 orgs,通过 idea 的 debug 直视化功能,我们可以很直观的看到三个参数的值分别为 public void User.setUserName(java.lang.String)User{id='12', userName='eva'}tim ,这不正好就是我们期望调用的方法、参数和对象吗?好了,提前在目标方法中打上断点,再直接下一步,可以看到程序停在了 User 类的 setUsername 方法上了,这样方法的调用过程就分析完毕。

总结

之前在看 Aop 的实现原理时,发现动态代理的过程也会执行上述的代码,但是当时并没有看懂各种类之间是什么关系,这次通过一个比较简单的例子重新走了一个这个过程,大致理清了下通过反射调用的过程。但是对于 Aop 的增强方法的链式调用过程还是很模糊,有时间了再去看看 Aop 实现的过程。