Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BeanUtils not works for chain setter

e.g.

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

My output:

TestValues.Stranger(name=null, xname=xwener, yname=null)

What's wrong with this? chain setter is a good thing. Any suggests ?

EDIT

Back to this problem again.This time I can not remove the Accessors chain. Now, I use the commons-lang3 to achieve.

// force access = true is required
Field field = FieldUtils.getField(bean.getClass(), attrName, true);
field.set(bean,value);

For those who got the same problem.

like image 744
wener Avatar asked Mar 30 '14 12:03

wener


3 Answers

You can use the FluentPropertyBeanIntrospector implementation:

"An implementation of the BeanIntrospector interface which can detect write methods for properties used in fluent API scenario."

https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/FluentPropertyBeanIntrospector.html

PropertyUtils.addBeanIntrospector(new FluentPropertyBeanIntrospector());
BeanUtils.setProperty( this.o, "property", "value" );
like image 96
mthielcke Avatar answered Oct 22 '22 09:10

mthielcke


That's simple: BeanUtils are rather strange and so is Introspector it uses:

Although BeanUtils.setProperty declares some exceptions, it seems to silently ignore the non-existence of the property to be set. The ultimate culprit is the Introspector which simply requires the voidness of setter.

I'd call it broken by design, but YMMV. It's an old class and fluent interfaces weren't invented yet in those dark times. Use Accessors(chain=false) to disable chaining.


More important: Use the source. Get it and get a debugger (it's already in your IDE) to find it out yourself (still feel free to ask if it doesn't work, just try a bit harder).

like image 21
maaartinus Avatar answered Oct 22 '22 08:10

maaartinus


In my project we use chained accessors across the board, so setting chain=false was not an option. I ended up writing my own introspector, which is similar to the one recommended by @mthielcke, and may be registered in the same way.

Introspector

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 <b>fluent</b> through <a href="https://projectlombok.org/">Lombok</a>
 * {@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 <a href="https://projectlombok.org/">Lombok</a> 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<String, Method[]> getLombokMethods(IntrospectionContext context) {
        Map<String, Method[]> 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<String, Method[]> 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<String, Method[]> 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[A-Z].*") && !method.getReturnType().equals(Void.TYPE)) {
                return true;
            }
            return method.getName().matches("^is[A-Z].*") && 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[A-Z].*");
    }

}

Registration

PropertyUtils.addBeanIntrospector(new LombokPropertyBeanIntrospector());
BeanUtils.copyProperties(dest, origin);
like image 2
izilotti Avatar answered Oct 22 '22 10:10

izilotti