Description

As per the issue #24267 the copyProperties method in the BeanUtils class has been overloaded in order to make it able to copy any kind of Java Bean: Mutable, Immutable, Mixed.

Fixes #24267

How Has This Been Tested?

  • [X] A Mutable Java Bean is successfully transformed
  • [X] An Immutable Java Beans is successfully transformed
  • [X] A Java Beans is successfully transformed with a given BeanTransformer
  • [X] The introduced changes have no impact on the existing code

Comment From: quaff

Please avoid introducing new dependency.

Comment From: fborriello

Hi @quaff, I know that introduce a new dependency on a project wouldn't be the best thing to do for a lot of reasons, but, in this case, I think in this case we should evaluate pros and cons, as, with the change introduced Spring would offer the following new features for free:

  • support copy of immutable beans.
  • support copy of mutable beans.
  • support copy of hybrid beans (some fields private and some not).
  • support copy of Java beans without getter and setter methods.
  • support copy with Java primitive type.
  • support copy with Java Collection type. e.g. List<BeanA> => List<BeanB>
  • support copy with nested map fields. e.g. Map<String, Map<String, String>>
  • support copy with array containing primitive types. e.g. String[] => String[]
  • support copy with an array type. e.g. BeanA[] => BeanB[]
  • support copy with property name mapping. e.g. int id => int userId
  • support copy with recursion copy.
  • support validation through annotations.
  • support copy of beans with different field's name.
  • support lambda function field transformation.
  • support copy of java bean built through Builder.
  • easy usage, declarative way to define the property mapping (in case of different names) or simply adding the Lombok annotations.
  • allows setting the default value for all objects not existing in the source object.
  • allows skipping transformation for a given set of fields.
  • supports the retrieval of the value from getters if a field does not exist in the source object.
  • supports the automatic conversion of primitive types.

moreover, that library is under the Apache license and it's constantly maintained and improved. What do you think?

another approach we could follow is to add the external dependency with the provided scope leaving the "spring hosting" project to add the BULL dependency explicitly if they want to use the above features, what do you think? Please, let me know, Thanks

Comment From: sbrannen

If we were to introduce this support, it would have to be an optional dependency instead of an implementation dependency.

In addition, the class-level Javadoc for BeanUtils states the following.

Mainly for use within the framework, but to some degree also useful for application classes.

In light of that and the fact that the proposed utility methods do not provide much benefit to the standard Spring developer, I don't think we should introduce a dependency on the BULL framework. This would align with our general approach to utility methods that are primarily for use within the Spring Framework itself. For example, the class-level Javadoc for StringUtils states the following.

Mainly for internal use within the framework; consider Apache's Commons Lang for a more comprehensive suite of String utilities.

@fborriello, since you are a developer of BULL, have you considered introducing such convenience methods directly in the BULL framework?

Comment From: fborriello

since you are a developer of BULL, have you considered introducing such convenience methods directly in the BULL framework? Good suggestion, for sure I will.

In light of that and the fact that the proposed utility methods do not provide much benefit to the standard Spring developer, I don't think we should introduce a dependency on the BULL framework. This would align with our general approach to utility methods that are primarily for use within the Spring Framework itself. For example, the class-level Javadoc for StringUtils states the following.

I was thinking to a different solution, what about moving those new methods on a separated class, called: BeanTransformer?

something defined as following:

package org.springframework.beans;

import org.springframework.util.Assert;

/**
 * Methods for populating Mutable, Immutable and Hybrid JavaBeans properties via reflection.
 * @author borriello.fabio
 */
public class BeanTransformer {
    /**
     * Transforms the given source bean into the given target bean class copying all its properties.
     * <p>Note: The source and target classes have to match or even be derived.
     * <p>This is just a convenience method. For more complex transfer needs,
     * consider using a {@link com.hotels.beans.transformer.BeanTransformer} and its settings.
     * @see <a href="https://hotelsdotcom.github.io/bull/apidocs">
     * https://hotelsdotcom.github.io/bull/apidocs</a>
     * @param source the source bean
     * @param targetClass the target bean class
     * @param <K> the target object type
     * @return a transformed copy of the source object into the destination object
     * @throws BeansException if the transformation failed
     * @see BeanWrapper
     */
    public <K> K transform(final Object source, final Class<K> targetClass) throws BeansException {
        return new com.hotels.beans.BeanUtils().getTransformer()
                .setDefaultValueForMissingField(true)
                .transform(source, targetClass);
    }

    /**
     * Transforms the given source bean into the given target bean class copying all its properties through the given
     * {@link com.hotels.beans.transformer.BeanTransformer} class.
     * <p>Note: The source and target classes have to match or even be derived.
     * <p>This is just a convenience method. For more complex transfer needs,
     * consider using a {@link com.hotels.beans.transformer.BeanTransformer} and its settings.
     * @see <a href="https://hotelsdotcom.github.io/bull/apidocs">
     * https://hotelsdotcom.github.io/bull/apidocs</a>
     * @param source the source bean
     * @param targetClass the target bean class
     * @param beanTransformer the bean transformer to use for the transformation. {@link com.hotels.beans.transformer.BeanTransformer}
     * @param <K> the target object type
     * @return a transformed copy of the source object into the destination object
     * @throws BeansException if the transformation failed
     * @see BeanWrapper
     */
    public <K> K transform(final Object source, final Class<K> targetClass, final com.hotels.beans.transformer.BeanTransformer beanTransformer)
            throws BeansException {
        Assert.notNull(beanTransformer, "BeanTransformer must not be null");
        return beanTransformer.transform(source, targetClass);
    }

    /**
     * Returns a bean transformer able to transform properties of any type of java bean.
     * <p>Note: The {@link com.hotels.beans.transformer.BeanTransformer} can be configured in order to apply any type of transformation on a java bean.
     * @see <a href="https://hotelsdotcom.github.io/bull/apidocs/com/hotels/beans/transformer/BeanTransformer.html">
     * https://hotelsdotcom.github.io/bull/apidocs/com/hotels/beans/transformer/BeanTransformer.html</a>
     * @return BeanTransformer instance
     * @see com.hotels.beans.transformer.BeanTransformer
     */
    public static com.hotels.beans.transformer.BeanTransformer getBeanTransformer() {
        return new com.hotels.beans.BeanUtils().getTransformer();
    }
}

what do you think? I have already implemented and pushed it. I've also changed the dependency from implementation to optional

Comment From: sbrannen

Team Decision: As stated in https://github.com/spring-projects/spring-framework/pull/24268#issuecomment-569286157, we feel that the core Spring Framework should not introduce a dependency on the BULL framework for this functionality. As an alternative, please consider introducing such convenience methods directly in the BULL framework so that all users of the BULL framework may benefit from it.

In addition, we will add information to the class-level Javadoc of BeanUtils similar to that found in StringUtils.

I am therefore closing this PR.

Comment From: sbrannen

See c1d0060a6f4f03bc7d761f568e1b60a0f59ed59b for the revised BeanUtils Javadoc.