背景和问题
原来项目中 使用的Bean映射工具是dozer Mapper , 刚开始没啥问题,随着业务扩大流量并发上来了,每次迭代版本项目重启后都会有短时间的服务不稳定情况,产生大量告警,给业务带来很大的麻烦。
利用arthas 定位问题 发现每次重启之后第一次请求,接口中Bean转换占用时间较长,达到了几十毫秒,效率很低,开始考虑替换dozerBeanMapper
为什么选mapStruct
参考这篇文章
5种常见Bean映射工具的性能比对
MapStruct 是一个基于JSR 269的 Java 注释处理器。开发者只需要定义一个 Mapper 接口,该接口声明任何所需的映射方法。在编译期间 MapStruct 将生成此接口的实现类。
使用 MapStruct 可以在两个 Java Bean 之间实现自动映射的功能,只需要创建好接口。由于它是在编译时自动创建具体的实现,因此无需反射等开销,在性能上也会好于 Apache 的 BeanUtils、Dozer 等。
开始替换,记录下前后耗时对比
dozerMapper
OrderQuery orderQuery = mapper.map(request, OrderQuery.class);
利用Alibaba开源的Java诊断工具arthas , 可以监控获取方法每一行的执行效率
arthas的使用很简单:
- 运行jar包,启动arthas
- 选择你正在运行的Java程序
- 选择你要监控的代码块 (trace com.zm.order.server.seller.controller.SellerOrderController listOrderAddr)
mapStruct的使用:
- 添加依赖
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
- 添加配置类
package com.zm.order.web.config;
import com.zm.order.server.common.mapper.OrderAddrStructMapper;
import com.zm.order.server.common.mapper.OrderOperateStructMapper;
import com.zm.order.server.common.mapper.OrderStructMapper;
import com.zm.order.server.common.mapper.PerformanceStructMapper;
import com.zm.order.server.seller.mapper.OrderBillStructMapper;
import com.zm.order.server.seller.mapper.OrderPerformanceStructMapper;
import org.mapstruct.factory.Mappers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.zm.order.server.seller.mapper.SellerStructMapper;
/**
* 转换类配置
*/
@Configuration
public class StructMapperConfig {
@Bean
public SellerStructMapper sellerStructMapper() {
return Mappers.getMapper(SellerStructMapper.class);
}
@Bean
public OrderAddrStructMapper orderAddrStructMapper() {
return Mappers.getMapper(OrderAddrStructMapper.class);
}
}
- 定义具体的转换接口和方法
package com.zm.order.server.common.mapper;
import com.zm.order.biz.dto.view.OrderAddrDTO;
import com.zm.order.server.common.response.OrderAddrVO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* @author: zhuxiaofeng
* @date: 2021/7/27
*/
@Mapper
public interface OrderAddrStructMapper {
/**
* OrderAddrDTO 转换 OrderAddrVO
* @date 2021/7/26 16:33
* @param orderAddrDtoList
* @return List
**/
List<OrderAddrVO> mapToOrderAddrVoList(List<OrderAddrDTO> orderAddrDtoList);
}
使用的时候直接调用接口中的方法就行了, mapStruct 会像『代码生成器』一样,自动帮我们生成该接口的实现类,并重写接口中的方法,用最简单的get set方法去帮我们一个一个属性去转换,所以它能做到性能最佳。
值得注意的是 mapStruct 除了能直接转换不同的对象,还能转换不同的List, 实现类中会自动加一个对象转换的方法,然后重写的方法去遍历调用对象转换,刚发现这个的时候就一个想法,卧槽 牛逼!
看一眼自动生成的实现类
package com.zm.order.server.common.mapper;
import com.zm.order.biz.dto.view.OrderAddrDTO;
import com.zm.order.server.common.response.OrderAddrVO;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-07-28T11:17:05+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_131 (Oracle Corporation)"
)
public class OrderAddrStructMapperImpl implements OrderAddrStructMapper {
@Override
public List<OrderAddrVO> mapToOrderAddrVoList(List<OrderAddrDTO> orderAddrDtoList) {
if ( orderAddrDtoList == null ) {
return null;
}
List<OrderAddrVO> list = new ArrayList<OrderAddrVO>( orderAddrDtoList.size() );
for ( OrderAddrDTO orderAddrDTO : orderAddrDtoList ) {
list.add( orderAddrDTOToOrderAddrVO( orderAddrDTO ) );
}
return list;
}
protected OrderAddrVO orderAddrDTOToOrderAddrVO(OrderAddrDTO orderAddrDTO) {
if ( orderAddrDTO == null ) {
return null;
}
OrderAddrVO orderAddrVO = new OrderAddrVO();
orderAddrVO.setOrderNo( orderAddrDTO.getOrderNo() );
orderAddrVO.setAcceptName( orderAddrDTO.getAcceptName() );
orderAddrVO.setSex( orderAddrDTO.getSex() );
orderAddrVO.setZip( orderAddrDTO.getZip() );
orderAddrVO.setProvince( orderAddrDTO.getProvince() );
orderAddrVO.setCity( orderAddrDTO.getCity() );
orderAddrVO.setArea( orderAddrDTO.getArea() );
orderAddrVO.setStreet( orderAddrDTO.getStreet() );
orderAddrVO.setLng( orderAddrDTO.getLng() );
orderAddrVO.setLat( orderAddrDTO.getLat() );
orderAddrVO.setMobile( orderAddrDTO.getMobile() );
orderAddrVO.setDoorNumber( orderAddrDTO.getDoorNumber() );
orderAddrVO.setProvinceName( orderAddrDTO.getProvinceName() );
orderAddrVO.setCityName( orderAddrDTO.getCityName() );
orderAddrVO.setAreaName( orderAddrDTO.getAreaName() );
orderAddrVO.setLabel( orderAddrDTO.getLabel() );
return orderAddrVO;
}
}
- 开始检测效果吧
dozerMapper,RT情况
项目重新部署后第一次请求
项目重新部署后第二次请求
1、[40.105291ms] com.github.dozermapper.core.Mapper:map() #225
2、[0.338133ms] com.github.dozermapper.core.Mapper:map() #225
3、[0.320335ms] com.github.dozermapper.core.Mapper:map() #225
改用MapStruct后,RT情况
第一次:
第二次:
多测试几次发现 mapStruct 的转化时间一直很稳定 ,相比dozer :
每次项目重启后第一次请求处理转换的时间提升了几十倍 甚至上百倍,RT差距非常明显。
over
最近终于不用写业务代码了,开心~~~