再记一次lombok踩坑事件

it2024-08-08  38

由于lombok的存在,使得我们不必在对象中显示定义get和set方法。

当一个对象存在许多字段时,一个个设置字段的值会显得代码特别臃肿,若在对象头部加上@Accessors(chain = true)注解,可实现链式编程。设置字段时,可以直接xxx.setA(aValue).setB(bValue).setC(cValue),使得代码间接了不少,非常好用。

 

但是在使用apache的BeanUtils,根据字段名设置字段值时出现了严重的BUG

BeanUtils.setProperty(obj,fieldName,value);

在调试时发现,该方法实现为:

public static void setProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException { BeanUtilsBean.getInstance().setProperty(bean, name, value); }

再跟进去,发现里面的有一步实现为:

PropertyDescriptor descriptor; try { descriptor = this.getPropertyUtils().getPropertyDescriptor(target, name); if (descriptor == null) { return; } } catch (NoSuchMethodException var13) { return; }

而在descriptor中,记载了writeMethodName和readMethodName。断点发现name对应的字段拿到的writeMethodName为null,而readMethodName不为空。

通过调用this.getPropertyUtils().getPropertyDescriptors(target)方法,发现所有字段的writeMethodName都是null。不禁怀疑是@Accessors(chain = true)注解重写了set方法的返回值导致。

在去掉@Accessors(chain = true)注解后,重新调试,发现所有writeMethodName都恢复正常。

 

继续深究源代码,发现最终会调用org.apache.commons.beanutils.PropertyUtilsBean#getPropertyDescriptors(java.lang.Class)方法,而这个方法的内部实现是调用Introspector.getBeanInfo(beanClass);   这个Introspector属于JDK自带的类,它调用getBeanInfo()方法,再调用getTargetPropertyInfo()方法,然后我们发现了问题所在

 

Method methodList[] = getPublicDeclaredMethods(beanClass); // Now analyze each method. for (int i = 0; i < methodList.length; i++) { Method method = methodList[i]; if (method == null) { continue; } // skip static methods. int mods = method.getModifiers(); if (Modifier.isStatic(mods)) { continue; } String name = method.getName(); Class<?>[] argTypes = method.getParameterTypes(); Class<?> resultType = method.getReturnType(); int argCount = argTypes.length; PropertyDescriptor pd = null; if (name.length() <= 3 && !name.startsWith(IS_PREFIX)) { // Optimization. Don't bother with invalid propertyNames. continue; } try { if (argCount == 0) { if (name.startsWith(GET_PREFIX)) { // Simple getter pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null); } else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) { // Boolean getter pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null); } } else if (argCount == 1) { if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) { pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null); } else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) { // Simple setter pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method); if (throwsException(method, PropertyVetoException.class)) { pd.setConstrained(true); } } } else if (argCount == 2) { if (void.class.equals(resultType) && int.class.equals(argTypes[0]) && name.startsWith(SET_PREFIX)) { pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, null, method); if (throwsException(method, PropertyVetoException.class)) { pd.setConstrained(true); } } } } catch (IntrospectionException ex) { // This happens if a PropertyDescriptor or IndexedPropertyDescriptor // constructor fins that the method violates details of the deisgn // pattern, e.g. by having an empty name, or a getter returning // void , or whatever. pd = null; } if (pd != null) { // If this class or one of its base classes is a PropertyChange // source, then we assume that any properties we discover are "bound". if (propertyChangeSource) { pd.setBound(true); } addPropertyDescriptor(pd); } } 其中,对于单个参数的set方法,会走以下逻辑: else if (argCount == 1) { if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) { pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null); } else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) { // Simple setter pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method); if (throwsException(method, PropertyVetoException.class)) { pd.setConstrained(true); } } } 这段代码中可以看到,只有返回值是void,且方法名以set作为前缀的,才会被当做writeMethod。

因此,对于要使用beanutils类进行赋值的对象,不能用@Accessors(chain = true)注解。

最新回复(0)