BeanUtils不适用于链设置器
例如
class tester { @Test public void testBeanUtils() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { Stranger stranger = new Stranger(); BeanUtils.setProperty(stranger,"name","wener"); BeanUtils.setProperty(stranger,"xname","xwener"); BeanUtils.setProperty(stranger,"yname","ywener"); System.out.println(stranger); } @Data// lombok annotation generate all setter and getter public static class Stranger { @Accessors(chain = true)// generate chained setter String name; String xname; String yname; public Stranger setYname(String yname)// no lombok, still not work { this.yname = yname; return this; } } }
我的输出:
TestValues.Stranger(name=null, xname=xwener, yname=null)
这有什么问题? 连锁二传手是一件好事。 有什么建议?
编辑
再次回到这个问题。这次我无法删除Accessors chain
。 现在,我使用commons-lang3
来实现。
// force access = true is required Field field = FieldUtils.getField(bean.getClass(), attrName, true); field.set(bean,value);
对于那些有同样问题的人。
这很简单: BeanUtils
相当奇怪,它使用的是Introspector
:
尽管BeanUtils.setProperty
声明了一些exception,但它似乎默默地忽略了要设置的属性的不存在。 最终的罪魁祸首是Introspector
,它只需要设置者的空虚。
我称之为设计破坏,但是YMMV。 它是一个古老的类,在那些黑暗时代还没有发明流畅的界面。 使用Accessors(chain=false)
禁用链接。
更重要的是: 使用来源 。 得到它并得到一个调试器(它已经在您的IDE中)自己找到它(仍然可以随意询问它是否不起作用,只是尝试一下)。
您可以使用FluentPropertyBeanIntrospector实现:
“BeanIntrospector接口的一个实现,它可以检测在流畅的API场景中使用的属性的写入方法。”
PropertyUtils.addBeanIntrospector(new FluentPropertyBeanIntrospector()); BeanUtils.setProperty( this.o, "property", "value" );
在我的项目中,我们使用链式访问器,因此设置chain=false
不是一种选择。 我最后编写了自己的introspector,类似于@mthielcke推荐的那个,并且可能以相同的方式注册。
内部检查
import org.apache.commons.beanutils.BeanIntrospector; import org.apache.commons.beanutils.IntrospectionContext; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; /** * Allows {@link org.apache.commons.beanutils.BeanUtils#copyProperties(Object, Object)} to copy properties across beans whose * properties have been made fluent through Lombok * {@link lombok.experimental.Accessors}, {@link lombok.Setter} and {@link lombok.Getter} annotations. * * @author izilotti */ @Slf4j public class LombokPropertyBeanIntrospector implements BeanIntrospector { /** * Performs introspection. This method scans the current class's methods for property write and read methods which have been * created by the Lombok annotations. * * @param context The introspection context. */ @Override public void introspect(final IntrospectionContext context) { getLombokMethods(context).forEach((propertyName, methods) -> { if (methods[0] != null && methods[1] != null) { final PropertyDescriptor pd = context.getPropertyDescriptor(propertyName); try { if (pd == null) { PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, methods[1], methods[0]); context.addPropertyDescriptor(descriptor); } } catch (final IntrospectionException e) { log.error("Error creating PropertyDescriptor for {}. Ignoring this property.", propertyName, e); } } }); } private Map getLombokMethods(IntrospectionContext context) { Map lombokPropertyMethods = new HashMap<>(); // property name, write, read Stream.of(context.getTargetClass().getMethods()) .filter(this::isNotJavaBeanMethod) .forEach(method -> { if (method.getReturnType().isAssignableFrom(context.getTargetClass()) && method.getParameterCount() == 1) { log.debug("Found mutator {} with parameter {}", method.getName(), method.getParameters()[0].getName()); final String propertyName = propertyName(method); addWriteMethod(lombokPropertyMethods, propertyName, method); } else if (!method.getReturnType().equals(Void.TYPE) && method.getParameterCount() == 0) { log.debug("Found accessor {} with no parameter", method.getName()); final String propertyName = propertyName(method); addReadMethod(lombokPropertyMethods, propertyName, method); } }); return lombokPropertyMethods; } private void addReadMethod(Map lombokPropertyMethods, String propertyName, Method readMethod) { if (!lombokPropertyMethods.containsKey(propertyName)) { Method[] writeAndRead = new Method[2]; lombokPropertyMethods.put(propertyName, writeAndRead); } lombokPropertyMethods.get(propertyName)[1] = readMethod; } private void addWriteMethod(Map lombokPropertyMethods, String propertyName, Method writeMethod) { if (!lombokPropertyMethods.containsKey(propertyName)) { Method[] writeAndRead = new Method[2]; lombokPropertyMethods.put(propertyName, writeAndRead); } lombokPropertyMethods.get(propertyName)[0] = writeMethod; } private String propertyName(final Method method) { final String methodName = method.getName(); return (methodName.length() > 1) ? Introspector.decapitalize(methodName) : methodName.toLowerCase(Locale.ENGLISH); } private boolean isNotJavaBeanMethod(Method method) { return !isGetter(method) || isSetter(method); } private boolean isGetter(Method method) { if (Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0) { if (method.getName().matches("^get[AZ].*") && !method.getReturnType().equals(Void.TYPE)) { return true; } return method.getName().matches("^is[AZ].*") && method.getReturnType().equals(Boolean.TYPE); } return false; } private boolean isSetter(Method method) { return Modifier.isPublic(method.getModifiers()) && method.getReturnType().equals(Void.TYPE) && method.getParameterTypes().length == 1 && method.getName().matches("^set[AZ].*"); } }
注册
PropertyUtils.addBeanIntrospector(new LombokPropertyBeanIntrospector()); BeanUtils.copyProperties(dest, origin);