开发时碰到的问题,自己记录方便日后观看
一、工具
本次测试工具:
- set/get方法
- spring BeanUtil
- hutool BeanUtil
- fastjson序列化
二、测试
为了测试更加细致,本次测试分为三个方向进行测试,会使用几种组合进行测试。
- 对象复杂度
- 对象大小
- 对象数量
(一)场景一
这里有个坑详见:场景一:第一次测试(坑位)
- 复杂对象
- 小对象
- 数据量:10个对象
(二)场景二
- 复杂对象
- 小对象
- 数据量:10000个对象
数据量提升后,hutool工具效率明显比spring慢。在多次测试后,当数据量达到三位数时hutool工具效率低于spring,相反三位数前hutool>spring。json先不看后续会有其它结论
(三)场景三
- 简单对象
- 小对象
- 数据量:10000个对象
对象变为简单对象后,性能得到提高。也就是说spring/hutool/json都会受到对象复杂度影响
(四)场景四
- 简单对象
- 大对象
- 数据量:10000个对象
对象变为大对象后,spring/hutool不受影响,json则变慢。这块是因为copy对象用的是反射,而json是序列化,序列化的解析方式都会受到内容大小的影响。序列化的优化,简单记录后续学习调研使用:Protobuf、MessagePack
三、结论
- set/get方式无论何时都是最快的,原因:
- 这种方式只是将上个对象的地址赋值给拷贝对象,不涉及额外操作
- 这种方式注意下,因为是地址赋值,所以拷贝后的对象此内容变更会影响拷贝前的对象。也就是我们常说的浅拷贝
- hutool bean工具/spring bean工具/fastJson序列化工具都属于深拷贝
- hutool/spring拷贝方式和性能消耗点
- 反射:这就不多说了
- 类型转换和检查:简单掰了下源码,对是否有set/get方法、源对象和目标对象属性方法是否私有、源类型是否可以赋值目标类型进行校验(ClassUtils.isAssignable)
- json
- JSON序列化/反序列化
- hutool/spring拷贝方式和性能消耗点
- hutool工具在数据量达到三位数前是优于spring工具,达到三位数后随着数据量越大则变慢的很明显。
- 越复杂的对象处理效率越慢,10000个对象在100、200ms性能要求没那么极限的话也在可接受范围内
- 序列化拷贝方式受到对象大小的影响
四、坑位
(一)场景一:第一次测试(坑位)
此场景与本次性能测试无关,可跳过直接看后续,在此做个记录为以后判断问题使用。
- 复杂对象
- 小对象
- 小数据量:10个对象
这块在测试时遇到了一个坑,可以看下图
10个对象spring、hutool、json竟然需要花费100多毫秒?这工具岂不是无法使用了?
问题分析及排查:
- 我最初以为是我用的对象过于复杂导致的
- 重新创建简单对象再次测试
- 是快了一点点,但肯定不是根本原因
- 于是把数据量从10改到1,看结果
- 好家伙,也就是说1次的性能跟10次差不多。那问题其实就不是出自spring\hutool\json工具本身
- 网上查询资料,结论就是:虚拟机(JVM)会对代码进行优化,但是这通常需要一些时间来"热身"。在短小的测试中,这些优化可能还没有充分发挥作用。
- “热身”后在次测试,测试场景恢复为:复杂对象、小对象、对象数10
- 之前还100多毫秒,现在瞬间降下来了
五、参考代码
StopWatch记录时间
for循环测试数据
@Test public void beanObjTest() { ArrayList<Obj1> obj1s = new ArrayList<>(); StopWatch stopWatch = new StopWatch("耗时统计"); stopWatch.start("初始化数据"); for (int i = 0; i < 10000; i++) { Obj1 obj1 = new Obj1(); // obj1.setText(i+"abc"); obj1.setText(i+getStr()); obj1s.add(obj1); } stopWatch.stop(); stopWatch.start("set/get"); List<Obj1> obj1List = new ArrayList<>(); for (Obj1 obj1 : obj1s) { Obj1 obj1Set = new Obj1(); obj1Set.setText(obj1.getText()); obj1List.add(obj1Set); } stopWatch.stop(); stopWatch.start("spring BeanUtil"); List<Obj1> obj1Spring = new ArrayList<>(); for (Obj1 obj1 : obj1s) { Obj1 obj1Copy = new Obj1(); BeanUtils.copyProperties(obj1, obj1Copy); obj1Spring.add(obj1Copy); } stopWatch.stop(); stopWatch.start("hutool BeanUtil"); List<Obj1> obj1Hutool = new ArrayList<>(); for (Obj1 obj1 : obj1s) { Obj1 obj1Copy = BeanUtil.copyProperties(obj1, Obj1.class); obj1Hutool.add(obj1Copy); } stopWatch.stop(); stopWatch.start("json序列化"); List<Obj1> obj1Json = new ArrayList<>(); for (Obj1 obj1 : obj1s) { Obj1 obj1Copy = JSON.parseObject(JSON.toJSONString(obj1), Obj1.class); obj1Json.add(obj1Copy); } stopWatch.stop(); System.out.println(stopWatch.prettyPrint()); log.info("{}",stopWatch.prettyPrint()); }
六、引入MapStruct
用BeanUtils.copyProperties可以简化代码复杂度,但是基于上述要求极致性能又希望代码能够尽量优雅,可以考虑使用MapStruct。
MapStruct是基于编译时生成实现类更加安全、高效
(一)maven
<!--1.5.0.Beta1之前不支持map结构--> <mapstruct.version>1.5.1.Final</mapstruct.version> <!--mapstruct--> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> </dependency> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins>
(二)代码示例
使用公共方式进行转换:
BaseConverter:
import org.mapstruct.IterableMapping; import org.mapstruct.Named; import java.util.List; public interface BaseConverter<T, E> { /** * T转E * * @param t * @return */ @Named("toObjE") E toObjE(T t); /** * E转T * * @param e * @return */ @Named("toObjT") T toObjT(E e); /** * T-List转E-List * * @param tList * @return */ @IterableMapping(qualifiedByName = "toObjE") List<E> toListE(List<T> tList); /** * E-List转T-List * * @param eList * @return */ @IterableMapping(qualifiedByName = "toObjT") List <T> toListT(List<E> eList); }
TestConverter:
import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface TestConverter extends BaseConverter<Test, EsTest> { @Mapping(target = "pin", expression ="java(StringUtils.defaultString(esTest.getPin(),\"test\"))") Test convertTest(EsTest esTest); }
特殊逻辑映射可自使用@Mapping映射实现:
import org.mapstruct.Mapper; import org.mapstruct.Mapping; import java.util.Map; @Mapper(componentModel = "spring", uses = CustomDateMapper.class) public interface SelectionOrderConverter { @Mapping(source = "id", target = "recordId") @Mapping(source = "order_id", target = "orderId") @Mapping(source = "create_at", target = "createAt") SelectionOrderEsEntity covertEsEntity(Map<String, String> selectionOrder); @Mapping(source = "recordId", target = "id") SelectionOrderEntity esCovertEntity(SelectionOrderEsEntity selectionOrderEs); }
CustomDateMapper:
@Component public class CustomDateMapper { public Date stringToDate(String date) { if (StringUtils.isNotBlank(date)) { return DateUtil.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } else { return null; } } }
调用示例:
import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.math.BigDecimal; import java.util.Date; import java.util.HashMap; /** * @author ext.xuzhengyang5 * @date 2024/2/19 * @Description */ @Slf4j @SpringBootTest @RunWith(SpringRunner.class) public class ConverterTest { @Autowired TestConverter testConverter; @Test public void test() { Test test = new Test(); test.setId(1L); test.setEnv("dev"); test.setUpdateAt(new Date()); test.setRepayMoney(new BigDecimal("1.333")); test.setYn(1); EsTest esTest = testConverter.toObjE(test); log.info("esTest:{}", JSON.toJSONString(esTest)); } @Test public void expressionTest() { EsTest esTest = new EsTest(); Test test = testConverter.convertTest(esTest); log.info("test:{}", JSON.toJSONString(test)); } @Autowired SelectionOrderConverter selectionOrderConverter; @Test public void selectionOrderTest() { /*SelectionOrderEntity selectionOrderEntity = new SelectionOrderEntity(); selectionOrderEntity.setOrderId(12L); selectionOrderEntity.setId(3L);*/ HashMap<String, String> map = new HashMap<>(8); map.put("order_id", "12"); map.put("id", "3"); map.put("create_at", "2024-01-24 19:41:09"); map.put("env", "admin"); SelectionOrderEsEntity selectionOrderEsEntity = selectionOrderConverter.covertEsEntity(map); log.info("selectionOrderEsEntity:{}", JSON.toJSONString(selectionOrderEsEntity)); } }