结论
手动设置属性与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
本博客文章绝大多数为原创,少量为转载,代码经过测试验证,如果有疑问直接留言或者私信我。
创作文章不容易,转载文章必须注明文章出处;如果这篇文章对您有帮助,点击右侧打赏,支持一下吧。
创作文章不容易,转载文章必须注明文章出处;如果这篇文章对您有帮助,点击右侧打赏,支持一下吧。