结论

手动设置属性与cglib性能接近;

cglib/手动设置cglib = 10 * ModelMapper = 100 *Apache BeanUtils

所以不到万不得已不要使用Apache的Bean Utils

工具类

package com.www.common.util;

import org.modelmapper.*;
import org.modelmapper.convention.MatchingStrategies;
import org.modelmapper.spi.DestinationSetter;
import org.modelmapper.spi.MappingContext;
import org.modelmapper.spi.SourceGetter;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.util.CollectionUtils;

import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

/***
 * 效率高于Apache BeanUtils, Spring BeanUtils的工具类
 */
public final class PropertyUtils {

    private static final ModelMapper modelMapper = new ModelMapper();

    private static final Set<PropertyMap> mapperCache = new CopyOnWriteArraySet<PropertyMap>();

    private PropertyUtils() {
    }

    static {
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
        //for skip a field purpose
        modelMapper.getConfiguration().setAmbiguityIgnored(true);
    }

    /***
     * 设置属性
     * @param trg
     * @param propertyName 属性名
     * @param value 属性值
     */
    public static void setProperty(final Object trg, final String propertyName, final Object value) {
        BeanWrapper trgWrap = PropertyAccessorFactory.forBeanPropertyAccess(trg);
        //to avoid NullValueInNestedPathException
        trgWrap.setAutoGrowNestedPaths(true);
        trgWrap.setPropertyValue(propertyName,value);
    }

    /***
     * 将对象<code>src</code>上面的<code>props</code>属性拷贝到对象<code>trg</code>上面
     * @param src
     * @param trg
     * @param props 待拷贝的字段名称
     */
    public static void copyProperties(Object src, Object trg, Iterable<String> props) {
        BeanWrapper srcWrap = PropertyAccessorFactory.forBeanPropertyAccess(src);
        BeanWrapper trgWrap = PropertyAccessorFactory.forBeanPropertyAccess(trg);
        //to avoid NullValueInNestedPathException
        trgWrap.setAutoGrowNestedPaths(true);
        props.forEach(p -> trgWrap.setPropertyValue(p, srcWrap.getPropertyValue(p)));
    }

    public static void copyProperties(Object src, Object trg, Iterable<String> props, Map<String, String> fieldMapping) {

        BeanWrapper srcWrap = PropertyAccessorFactory.forBeanPropertyAccess(src);
        BeanWrapper trgWrap = PropertyAccessorFactory.forBeanPropertyAccess(trg);
        //to avoid NullValueInNestedPathException
        trgWrap.setAutoGrowNestedPaths(true);

        if (CollectionUtils.isEmpty(fieldMapping)) {
            return;
        }

        props.forEach(fromFieldName -> {
            String toFieldName = fieldMapping.get(fromFieldName);
            if (Objects.nonNull(toFieldName)) {
                trgWrap.setPropertyValue(toFieldName, srcWrap.getPropertyValue(fromFieldName));
            } else {
                trgWrap.setPropertyValue(fromFieldName, srcWrap.getPropertyValue(fromFieldName));
            }
        });
    }

    public static void copyProperties(Map<?, ?> src,Object trg) {

        BeanWrapper trgWrap = PropertyAccessorFactory.forBeanPropertyAccess(trg);
        //to avoid NullValueInNestedP athException
        trgWrap.setAutoGrowNestedPaths(true);
        trgWrap.setPropertyValues(src);
    }

    /***
     * 从map实例上面讲属性拷贝到<code>trg</code>对象属性上面
     * @param trg
     * @param src
     * @param ignoreUnknownProperties 如果<code>trg</code>目标对象不存在map中某个属性,不报错,正常拷贝
     */
    public static void copyProperties( Map<?, ?> src, Object trg,boolean ignoreUnknownProperties) {
        BeanWrapper trgWrap = PropertyAccessorFactory.forBeanPropertyAccess(trg);
        //to avoid NullValueInNestedP athException
        trgWrap.setAutoGrowNestedPaths(true);
        trgWrap.setPropertyValues(new MutablePropertyValues(src), ignoreUnknownProperties);
    }

    /***
     * 将<code>src</code>上面的属性拷贝到<code>targetClass</code>类型的类实例上
     * @param src
     * @param targetClass
     * @param <T>
     * @return
     */
    public static <T, S> T mappingProperties(S src, Class<T> targetClass) {
        return modelMapper.map(src, targetClass);
    }

    public static <T,S> T mappingProperties(S src, Class<T> targetClass, Iterable<String> excludeProperties)
    {
        final TypeMap<?, T> builder = modelMapper.typeMap(src.getClass(), targetClass);
        //for(final Iterator<DestinationSetter<T, S>> it = destinationSetters.iterator();it.hasNext();) {
            //builder.addMappings(mp -> mp.skip(it.next()));
        for(final Iterator<String> it = excludeProperties.iterator(); it.hasNext();){
            final String property = it.next();
            builder.addMappings(mp->mp.when(new Condition<Object, Object>() {
                @Override
                public boolean applies(final MappingContext<Object, Object> context) {
                    if(property.equals(context.getDestination()))
                    {
                        return true;
                    }
                    return false;
                }
            }));
        }
        return null;
    }

    //TODO:https://stackoverflow.com/questions/49074784/modelmapper-skip-a-field
    //TODO:https://www.baeldung.com/java-modelmapper
    public static <T, S> T mappingProperties(S src, Class<T> targetClass, Iterable<DestinationSetter<T, S>>  destinationSetters,String i) {

        final TypeMap<?, T> builder = modelMapper.typeMap(src.getClass(), targetClass);
        for(final Iterator<DestinationSetter<T, S>> it = destinationSetters.iterator();it.hasNext();) {
            builder.addMappings(mp -> mp.skip(it.next()));
        }
        PropertyMap<S, T> skipModifiedFieldsMap = new PropertyMap<S, T>() {
            @Override
            protected void configure() {
//               source("d");
//               skip().setModifiedBy(null);
//               skip().setModifiedDate(null);
            }
        };
        //modelMapper.addMappings(skipModifiedFieldsMap).map(src,targetClass);
        return modelMapper.map(src, targetClass);
    }

    public static <T, S, V> T mappingProperties(S src, Class<T> targetClass,DestinationSetter<T,V> setter) {
        final TypeMap<?, T> builder = modelMapper.typeMap(src.getClass(), targetClass);

        final TypeMap<?, T> typeMapper = modelMapper.typeMap(src.getClass(), targetClass);
        //? super T, ? extends R
        SourceGetter<? extends T> getter = null;
        //typeMapper.<V>addMapping(getter,setter);

        PropertyMap<S, T> skipModifiedFieldsMap = new PropertyMap<S, T>() {
            @Override
            protected void configure() {
//               source("d");
//               skip().setModifiedBy(null);
//               skip().setModifiedDate(null);
            }
        };
        //modelMapper.addMappings(skipModifiedFieldsMap).map(src,targetClass);
        return modelMapper.map(src, targetClass);
    }
    /***
     * 忽略指定字段,PropertyMap用法<pre>
     *       PropertyMap<ItemDto, Item> skipModifiedFieldsMap = new PropertyMap<ItemDto, Item>() {
     *       protected void configure() {
     *          skip().setModifiedBy(null);
     *          skip().setModifiedDate(null);
     *      }
     *    };
     * </pre>
     * @param src
     * @param targetClass
     * @param propertyMappingRules
     * @param <T>
     * @param <S>
     * @return
     */
    public static <T, S> T mappingProperties(Object src, Class<T> targetClass, PropertyMap<S, T> propertyMappingRules) {
        //如果存在映射则不需要再次添加防止报错
        if(!mapperCache.contains(propertyMappingRules)) {
            modelMapper.addMappings(propertyMappingRules);
            mapperCache.add(propertyMappingRules);
        }
        return mappingProperties(src, targetClass);
    }
}

 

测试

package com.www;

import com.www.common.util.PropertyUtils;
import com.www.entity.UserBasicInfo;
import com.www.model.UserModel;

import org.apache.commons.beanutils.BeanUtils;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.springframework.cglib.beans.BeanCopier;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author: passedbylove
 * @date: Created by  2022/2/9 11:30
 * @version: 1.0.0
 */
@BenchmarkMode(Mode.AverageTime) // 测试方法平均执行时间
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 输出结果的时间粒度为微秒
@State(Scope.Thread)
public class BeanCopyPerformanceBenchmark {

    private UserModel model = new UserModel();
    private BeanCopier beanCopier = BeanCopier.create(UserModel.class, UserBasicInfo.class,false);
    LocalDateTime now = LocalDateTime.now();

    @Benchmark
    public UserBasicInfo manuallySetter()
    {
        UserBasicInfo vo = new UserBasicInfo();
        vo.setCreatedTime(now);
        return vo;
    }

    @Benchmark
    public UserBasicInfo beanUtils() throws InvocationTargetException, IllegalAccessException {
        UserBasicInfo vo = new UserBasicInfo();
        BeanUtils.copyProperties(this.model,vo);
        return vo;
    }

    @Benchmark
    public UserBasicInfo beanCopier()
    {
        UserBasicInfo vo = new UserBasicInfo();
        beanCopier.copy(this.model,vo,null);
        return vo;
    }

    @Benchmark
    public UserBasicInfo modelMapper()
    {
        UserBasicInfo vo = PropertyUtils.mappingProperties(this.model,UserBasicInfo.class);
        return vo;
    }

    @Benchmark
    public UserBasicInfo propertyAccessor()
    {
        Set<String> props = new HashSet<>();
        props.add("id");
        props.add("accountId");
        props.add("userType");
        props.add("name");
        props.add("hrNumber");
        props.add("groupId");
        props.add("phone");
        props.add("email");
        props.add("createdBy");
        props.add("createdTime");
        props.add("updatedBy");
        props.add("updatedTime");

        UserBasicInfo vo = new UserBasicInfo();
        PropertyUtils.copyProperties(this.model,vo,props);
        return vo;
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(BeanCopyPerformanceBenchmark.class.getName()+".*").measurementIterations(5).forks(1).build();
        new Runner(options).run();
    }
}
Benchmark                                      Mode  Cnt   Score    Error  Units
BeanCopyPerformanceBenchmark.beanCopier        avgt    5  ≈ 10⁻⁵           ms/op
BeanCopyPerformanceBenchmark.beanUtils         avgt    5   0.008 ±  0.001  ms/op
BeanCopyPerformanceBenchmark.manuallySetter    avgt    5  ≈ 10⁻⁵           ms/op
BeanCopyPerformanceBenchmark.modelMapper       avgt    5  ≈ 10⁻⁴           ms/op
BeanCopyPerformanceBenchmark.propertyAccessor  avgt    5   0.003 ±  0.001  ms/op

 

测试2

package com.www;

import com.www.common.util.PropertyUtils;
import com.www.entity.UserBasicInfo;
import com.www.model.UserModel;
import org.apache.commons.beanutils.BeanUtils;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.springframework.cglib.beans.BeanCopier;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

/**
 * @author: passedbylove
 * @date: Created by  2022/2/9 11:30
 * @version: 1.0.0
 */
@BenchmarkMode(Mode.AverageTime) // 测试方法平均执行时间
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 输出结果的时间粒度为微秒
@State(Scope.Thread)
public class CopyPropertyPerformanceBenchmark {
    LocalDateTime now = LocalDateTime.now();

    @Benchmark
    public void manuallySetter()
    {
        UserBasicInfo vo = new UserBasicInfo();
        vo.setCreatedTime(now);
    }

    @Benchmark
    public void beanUtils() throws InvocationTargetException, IllegalAccessException {
        UserBasicInfo vo = new UserBasicInfo();
        BeanUtils.setProperty(vo,"createdTime",now);
    }

    @Benchmark
    public UserBasicInfo propertyAccessor()
    {
        UserBasicInfo vo = new UserBasicInfo();
        PropertyUtils.setProperty(vo,"createdTime",now);
        return vo;
    }
    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(CopyPropertyPerformanceBenchmark.class.getName()+".*").measurementIterations(20).forks(1).build();
        new Runner(options).run();
    }
}
Benchmark                                          Mode  Cnt   Score    Error  Units
CopyPropertyPerformanceBenchmark.beanUtils         avgt   20  ≈ 10⁻³           ms/op
CopyPropertyPerformanceBenchmark.manuallySetter    avgt   20  ≈ 10⁻⁵           ms/op
CopyPropertyPerformanceBenchmark.propertyAccessor  avgt   20  ≈ 10⁻⁴           ms/op

 

posted on 2022-02-17 18:09  你不知道的浪漫  阅读(97)  评论(0编辑  收藏  举报