这两天碰到了一个令人头秃的问题,花费了我一整天的时间最终跟踪到了问题根源:
@Test
public void testOrderPage(){
Page page = new Page();
Pagination pagination = new Pagination(2,5);
try {
// 这里copy时,pagination中的current(2)和size(5)并没有copy到page对象
PropertyUtils.copyProperties(page, pagination);
} catch (Exception e) {
e.printStackTrace();
}
}
public class Page {
// ...
protected long current = 1;
// ...
public long getCurrent() {
return this.current;
}
// 根源就是在这里的set方法返回值不是void
public Page setCurrent(long current) {
this.current = current;
return this;
}
// ...
}
public class Pagination {
// ...
public Pagination(long current, long size) {
if (current > 1) {
this.current = current;
}
this.size = size;
}
// ...
private long size = 10;
private long current = 1;
// ...
public long getSize() {
return size;
}
// ...
public void setSize(long size) {
this.size = size;
}
// ...
public long getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
}
一路跟踪下去
org\apache\commons\beanutils\PropertyUtils.java
public static void copyProperties(final Object dest, final Object orig){
PropertyUtilsBean.getInstance().copyProperties(dest, orig);
}
org\apache\commons\beanutils\PropertyUtilsBean.java
public void copyProperties(final Object dest, final Object orig){
if (...) {
// ...
} else /* if (orig is a standard JavaBean) */ {
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
// 跟踪到这里的isWriteable,是判断目标对象的name属性是否可写
if (isReadable(orig, name) && isWriteable(dest, name)) {
// ...
}
}
}
}
public boolean isWriteable(Object bean, String name) {
// ...
// 为null的原因在这里 ↓
final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
if (desc != null) {
// 这里应该拿到目标对象属性的set方法,但是为null,见上面 ↑
Method writeMethod = getWriteMethod(bean.getClass(), desc);
if (writeMethod == null) {
// ...
}
return (writeMethod != null);
} else {
return (false);
}
}
public PropertyDescriptor getPropertyDescriptor(Object bean, String name){
// ...
// 通过BeanIntrospectionData 对象的 getDescriptor 获取属性描述对象
final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
PropertyDescriptor result = data.getDescriptor(name);
// ...
return result;
}
private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
// ...
// 这里还用了个缓存 descriptorsCache,通过 beanClass 获取 BeanIntrospectionData 对象
BeanIntrospectionData data = descriptorsCache.get(beanClass);
if (data == null) {
data = fetchIntrospectionData(beanClass);
descriptorsCache.put(beanClass, data);
}
return data;
}
private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
for (final BeanIntrospector bi : introspectors) {
try {
// 这里就是处理获取 PropertyDescriptors 的地方,然后追踪到 org.apache.commons.beanutils.DefaultBeanIntrospector#introspect
bi.introspect(ictx);
} catch (final IntrospectionException iex) {
log.error("Exception during introspection", iex);
}
}
// 这里会把处理后的 PropertyDescriptors 设置到 BeanIntrospectionData 里并返回
return new BeanIntrospectionData(ictx.getPropertyDescriptors());
}
org\apache\commons\beanutils\DefaultBeanIntrospector.java
public void introspect(final IntrospectionContext icontext) {
// 通过 jdk 的 Introspector.getBeanInfo -> beanInfo.getPropertyDescriptors() 获取 PropertyDescriptor[]
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
} catch (final IntrospectionException e) { //... }
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
// ...
icontext.addPropertyDescriptors(descriptors);
}
java\beans\Introspector.java
public static BeanInfo getBeanInfo(Class<?> beanClass){
// ...
// 这里获取的 BeanInfo
beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
// ...
return beanInfo;
}
private BeanInfo getBeanInfo() throws IntrospectionException {
// ...
// 这里获取的 PropertyDescriptor[]
PropertyDescriptor pds[] = getTargetPropertyInfo();
return new GenericBeanInfo(bd, esds, defaultEvent, pds,
defaultProperty, mds, explicitBeanInfo);
}
private PropertyDescriptor[] getTargetPropertyInfo() {
// ...
// 跟踪到这里发现在 PropertyInfo 中就没有获取到属性的set方法,最终锁定 ClassInfo.getProperties() 方法
for (Map.Entry<String,PropertyInfo> entry : ClassInfo.get(this.beanClass).getProperties().entrySet()) {
addPropertyDescriptor(null != entry.getValue().getIndexed()
? new IndexedPropertyDescriptor(entry, this.propertyChangeSource)
: new PropertyDescriptor(entry, this.propertyChangeSource));
}
// ...
}
com\sun\beans\introspect\ClassInfo.java
public Map<String,PropertyInfo> getProperties() {
if (this.properties == null) {
synchronized (this.mutex) {
if (this.properties == null) {
this.properties = PropertyInfo.get(this.type);
}
}
}
return this.properties;
}
com\sun\beans\introspect\PropertyInfo.java
public static Map<String,PropertyInfo> get(Class<?> type) {
List<Method> methods = ClassInfo.get(type).getMethods();
if (methods.isEmpty()) {
return Collections.emptyMap();
}
Map<String,PropertyInfo> map = new TreeMap<>();
for (Method method : methods) {
if (!Modifier.isStatic(method.getModifiers())) {
Class<?> returnType = method.getReturnType();
String name = method.getName();
switch (method.getParameterCount()) {
case 0:
if (returnType.equals(boolean.class) && isPrefix(name, "is")) {
PropertyInfo info = getInfo(map, name.substring(2), false);
info.read = new MethodInfo(method, boolean.class);
} else if (!returnType.equals(void.class) && isPrefix(name, "get")) {
PropertyInfo info = getInfo(map, name.substring(3), false);
info.readList = add(info.readList, method, method.getGenericReturnType());
}
break;
case 1:
// 最终就是在这里跳过了 set 方法,因为 returnType 不是 void
if (returnType.equals(void.class) && isPrefix(name, "set")) {
PropertyInfo info = getInfo(map, name.substring(3), false);
info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[0]);
} else if (!returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "get")) {
PropertyInfo info = getInfo(map, name.substring(3), true);
info.readList = add(info.readList, method, method.getGenericReturnType());
}
break;
case 2:
if (returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "set")) {
PropertyInfo info = getInfo(map, name.substring(3), true);
info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[1]);
}
break;
}
}
}
Iterator<PropertyInfo> iterator = map.values().iterator();
while (iterator.hasNext()) {
if (!iterator.next().initialize()) {
iterator.remove();
}
}
return !map.isEmpty()
? Collections.unmodifiableMap(map)
: Collections.emptyMap();
}