您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Mapstruct源码解析与使用
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Mapstruct源码解析与使用
自猿其说Tech
2022-05-09
IP归属:未知
28880浏览
计算机编程
MapStruct 是一个属性映射插件,用于为Java Bean生成类型安全且高性能的映射。它基于编译阶段生成get/set代码,此实现过程中没有反射,不会造成额外的性能损失。只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了繁琐的映射实现。 ### 1 mapstruct与其他映射框架对比 实体类映射框架大致有两种:一种是运行期通过java反射机制动态映射;另一种是编译期动态生成getter/setter,在运行期直接调用框架编译好的class类实现实体映射。 #### t1.1 Setter - 原生的set赋值随便速度比较快 - 缺点:字段多的时候手动Set赋值比较麻烦 #### 1.2 BeanUtils - apache的BeanUtils.copyProperties效率低 - spring的BeanUtils.copyProperties()效率还可以 - 缺点:字段的类型和名称都必须一样,扩展性差 #### 1.3 MapStruct - 简单泛型智能转换; - 效率高:无需手动 set/get 或 implements Serializable 以达到深拷贝; - MapStruct属于在编译期,生成调用get/set方法进行赋值的代码,生成对应的Java文件。在编译期间消耗少许的时间,换取运行时的高性能 - 性能更高:使用简单的 Java 方法调用代替反射; - 编译时类型安全:只能映射相同名称或带映射标记的属性; - 编译时产生错误报告:如果映射不完整(存在未被映射的目标属性)或映射不正确(找不到合适的映射方法或类型转换)则会在编译时抛出异常。 #### 1.4 Other - Bean Mapping:属性拷贝,性能一般 - Bean Mapping ASM:基于ASM字节码框架实现,与普通的 Bean Mapping 相比,性能有所提升,可以使用 - BeanCopier:基于CGlib字节码操作生成get、set方法。整体性能很不错,使用也不复杂,可以使用 - Orika:基于字节码生成映射对象。测试性能不是太突出,如果使用的话需要把 MapperFactory 的构建优化成 Bean 对象 - Dozer:属性映射框架,递归的方式复制对象。性能有点差,不建议使用 - ModelMapper:基于ASM字节码实现。转换对象数量较少时性能不错,如果同时大批量转换对象,性能有所下降 - JMapper:速度可以,不过结合 SpringBoot 感觉有的一点点麻烦 总结:属性映射是代码开发中常用的部分,各种实现方式殊途同归,实现的结果是一致的。作为开发工作中的一部分,本身作为一种实现方式或工具存在,任何业务场景下使用任何方式都是允许的,并无场景限制。但是不同的实现方式在效率以及易用性上是有区别的,通常会选择效率高且易用的方式来使用 ### 2 MapStruct使用 #### 2.1 检索映射器 ##### 2.1.1 Mapper工厂(非依赖注入) 当不使用 DI 框架时,可以通过org.mapstruct.factory.Mappers类检索 Mapper 实例。只需调用该getMapper()方法,传递映射器的接口类型即可返回 ###### 1)接口 ```java @Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); CarDto carToCarDto(Car car); } ``` ###### 2)抽象类 ```java @Mapper public abstract class CarMapper { public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); CarDto carToCarDto(Car car); } ``` 这种模式使客户端可以很容易地使用映射器对象,而无需重复实例化新实例 ##### 2.1.2 依赖注入 以指定生成映射器类的组件模型应基于通过@Mapper#componentModel或使用配置选项中所述的处理器选项。 ###### 1)CDI注入 ```java @Mapper(componentModel = "cdi") public interface CarMapper { CarDto carToCarDto(Car car); } @Inject private CarMapper mapper; ``` ###### 2)spring注入 ```java @Mapper(componentModel = "spring") public interface CarMapper { CarDto carToCarDto(Car car); } @Autowired private CarMapper mapper; ``` ![](//img1.jcloudcs.com/developer.jdcloud.com/dc0f883d-8327-4f8b-968b-f448db71a47e20220509162131.png) #### 2.2 映射规则 ##### 2.2.1 @Mapper 1)对于同类型且名字相同的自动映射 2)自动类型转换 - 8种基本类型和他们的包装类型之间 - 8种基本类型(包括他们的包装类型)和String类型之间 - 日期类型和String之间 注意:从较大的数据类型转换为较小的数据类型(例如 from longto int)可能会导致值或精度损失。Mapper和MapperConfig注释具有typeConversionPolicy控制警告/错误的方法。由于向后兼容的原因,默认值为“ReportingPolicy.IGNORE” ##### 2.2.2 @Mappings和@Mapping 1)指定属性之间的映射转换 - 数字格式化 numberFormat - 日期格式化 dateFormat ```java @Mappings({ @Mapping(source = "totalPrice", target = "price", numberFormat = "#.00"), @Mapping(source = "fullTrayTime", target = "fullTrayTime", dateFormat = "yyyy-MM-dd HH:mm:ss") }) ``` 2)source和target没有的属性不会报错,因为会判空(参考 实现类Impl) 3)ignore ```java @Mappings({ @Mapping(source = "id", target = "carId"), @Mapping(source = "name", target = "carName"), @Mapping(target = "tenantCode", ignore = true), @Mapping(target = "configLevel", ignore = true), @Mapping(target = "userGroupCode", ignore = true) }) CarDto toCarDto(CarVo carVo); ``` ignore标识的字段属性在实体映射中会被忽略,当原对象某些字段不需要转换覆盖时,在相应字段上使用ignore即可实现该功能。上面示例中方法toCarDto将CarVo转化为CarDto,carVo中属性id和name会转化到carDto中的carId和carName,被ignore修饰的tenantCode、configLevel、userGroupCode则不会被映射 4)属性是引用对象 ```java 对象UserDto中有个对象属性private CarDto carDto;对象UserVo中有个对象属性private CarVo carVo; @Mappings({ .... @Mapping(source = "carVo", target = "carDto"), UserDto toUserDto(UserVo userVo); }) @Mapping(source = "id", target = "carId"), @Mapping(source = "name", target = "carName"), CarDto toCarDto(CarVo carVo); ``` 5)批量映射 ```java List<CarDto> toCarDtoList(List<CarVo> carVoList); ``` 6)Map映射(版本在1.5.0及以上才可以) 当map中的key值与对象属性名一致,会自动映射 ##### 2.2.3 @AfterMapping和@MappingTarget 在属性映射的最后一步进行自定义属性映射处理 调用时我们无需主动调用@AfterMapping注解的方法,当@Mapping完成属性映射时,会自动调用对应的@AfterMapping方法。这里使用@AfterMapping时,常用更新现有实例的方式实现(见2.3.4),也可以写自定义方法实现进行抓换,只要@AfterMapping修饰的接口方法/实现方法与@Mapping修饰的原映射方法是同样的A对象转化成B对象即可 ```java @Mappings({ .... }) CarDto toCarDto(CarVo carVo); @AfterMapping public void toCarDtoAfter(CarVo carVo,@MappingTarget CarDto carDto){ // MappingTarget:表示传来的对象是已经赋过值的 /** * 业务逻辑 * if(carVo.getUser()==null){ * carDto.setAbc("1234"); * }else{ * carDto.setAbc("4321"); *} * */ } ``` ##### 2.2.4 @BeanMapping 配置忽略mapstruct的默认映射行为,只映射哪些配置了@Mapping的属性。实现结果与使用ignore一样,当实体中有二十个属性,只有五六个属性需要映射,此时使用代码@BeanMapping更加简洁 ```java // vehicleVo中属性配置了@Mapping的属性id和name会被映射,其他属性不会被映射 @BeanMapping(ignoreByDefault = true) @Mapping(source = "id", target = "carId"), @Mapping(source = "name", target = "carName"), VehicleDto toVehicleDto(VehicleVo vehicleVo); ``` ##### 2.3.5 @InheritConfiguration和@InheritInverseConfiguration @InheritConfiguration继承之前方法的配置,不需要再重复配置一份@mapping 如果A 的所有类型(源类型和结果类型)都可以分配给B的相应类型,则一种方法A可以从另一种方法B继承配置。 ps:下面只有domainTodto一个方法将Person映射为PersonDTO,所以@InheritConfiguration中的(name = "domainTodto") 可以忽略。如果该convert中还有另一个方法PersonDTO domainTodto2(Person person);,继承配置时必须指定name ```java @Mappings({ @Mapping(source = "birthday", target = "birth"), @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"), @Mapping(source = "user.age", target = "age"), @Mapping(target = "email", ignore = true) }) PersonDTO domainTodto(Person person); @InheritConfiguration(name = "domainTodto") void update(Person person, @MappingTarget PersonDTO personDTO); ``` @InheritInverseConfiguration反向继承配置,只会继承@mapping ```java @Mapper public interface CarMapper { @Mapping(source = "numberOfSeats", target = "seatCount") CarDto carToDto(Car car); @InheritInverseConfiguration @Mapping(target = "numberOfSeats", ignore = true) Car carDtoToCar(CarDto carDto); } ``` ### 2.3 映射方法 #### 2.3.1 自定义映射方法 1)默认方法调用 ```java @Mapper public interface CarMapper { @Mapping(...) ... CarDto carToCarDto(Car car); default PersonDto personToPersonDto(Person person) { //hand-written mapping logic } } ``` 2)抽象类 ```java @Mapper public abstract class CarMapper { @Mapping(...) ... public abstract CarDto carToCarDto(Car car); public PersonDto personToPersonDto(Person person) { //hand-written mapping logic } } ``` 两种方式最终实现的结果是一致的。 personToPersonDto为我们自定义的映射方法,当Car映射为CarDto时,Car中的对象属性Person会通过自定义映射方法personToPersonDto映射到CarDto中的对象属性PersonDto ##### 2.3.2 多个源参数的映射方法 ```java @Mapper public interface AddressMapper { @Mapping(source = "person.description", target = "description") @Mapping(source = "address.houseNo", target = "houseNumber") DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address); } ``` @Mapping如果多个源对象定义具有相同名称的属性,则必须使用注释指定从中检索属性的源参数 ##### 2.3.3 直接引用源参数的映射方法 ```java @Mapper public interface AddressMapper { @Mapping(source = "person.description", target = "description") @Mapping(source = "hn", target = "houseNumber") DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn); } ``` 参数hn,一个非 bean 类型(在这种情况下java.lang.Integer)被映射到houseNumber ##### 2.3.4 更新现有实例 ```java @Mapper public interface CarMapper { void updateCarFromDto(CarDto carDto, @MappingTarget Car car); } ``` 将CarDto中的属性更新到Car中,编译后生成的代码为car.setXX(carDto.getXX()); ##### 2.3.5 调用其他映射器 ```java public class DateMapper { public String asString(Date date) { return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) .format( date ) : null; } public Date asDate(String date) { try { return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) .parse( date ) : null; } catch ( ParseException e ) { throw new RuntimeException( e ); } } } @Mapper(uses=DateMapper.class) public class CarMapper { CarDto carToCarDto(Car car); } ``` 调用自定义映射器将实例转化时符合条件的字段值按照我们自定义规则进行转换。上面案例中DateMapper定义了时间类型和String类型的转换,当某字段符合Date转换成String或者String转换成Date时,会按照我们定义的yyyy-MM-dd格式进行转换 ##### 2.3.6 表达式 将实例转换中指定字段值按照我们定义表达式转换,分为默认和声明式两种方式。默认写法expression为全路径,包含包名+类名+方法名(参数);声明式是默认写法的简化版,将类注入到转化convert中,无需写全路径即可调用方法进行转换 ###### 1)默认 ```java @Mapper public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mapping(target = "timeAndFormat", expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )") Target sourceToTarget(Source s); } ``` ###### 2)声明式 ```java imports org.sample.TimeAndFormat; @Mapper( imports = TimeAndFormat.class ) public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mapping(target = "timeAndFormat", expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )") Target sourceToTarget(Source s); } ``` ##### 2.3.7 NULL参数的映射结果 ```java public enum NullValuePropertyMappingStrategy { /** * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}. */ SET_TO_NULL, /** * If a source bean property equals {@code null} the target bean property will be set to its default value. * <p> * This means: * <ol> * <li>For {@code List} MapStruct generates an {@code ArrayList}</li> * <li>For {@code Map} a {@code HashMap}</li> * <li>For arrays an empty array</li> * <li>For {@code String} {@code ""}</li> * <li>for primitive / boxed types a representation of {@code 0} or {@code false}</li> * <li>For all other objects an new instance is created, requiring an empty constructor.</li> * </ol> * <p> * Make sure that a {@link Mapping#defaultValue()} is defined if no empty constructor is available on * the default value. */ SET_TO_DEFAULT, /** * If a source bean property equals {@code null} the target bean property will be ignored and retain its * existing value. */ IGNORE; } ``` 当映射方法的source参数等于null时,MapStruct提供对要创建的对象的控制。默认情况下,将返回null。 但是,通过@BeanMapping,@IterableMapping,@MapMapping或@Mapper或@MappingConfig上的全局指定nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,可以更改映射结果以返回空的默认值。这意味着: - Bean映射:将返回一个“empty”目标bean,除常量和表达式外,它们将在存在时填充。 - 基本数据类型:将返回基本数据类型的默认值,例如boolean类型的默认值时false,int类型的默认值时0。 - 集合/数组(Iterables/Arrays):将返回一个空(empty)的iterable。 - Maps:将返回一个空(empty)的Map。 该策略以等级方式运作。在映射方法级别上设置nullValueMappingStrategy将覆盖@Mapper#nullValueMappingStrategy,而@Mapper#nullValueMappingStrategy将覆盖@MappingConfig#nullValueMappingStrategy。 代码示例: ```java @Mapper(uses = DateMapper.class, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) public interface TrayContainerDtoConverter { ... } ``` #### 2.4 性能比较 通过MapStruct 和 BeanUtils 将相同对象转换100W次,看看整体的耗时数据。测试类PerformanceTest的代码如下所示: ``` public class PerformanceTest { public static void main(String[] args) { for(int i=0; i<5; i++) { Long startTime = System.currentTimeMillis(); for(int count = 0; count<1000000; count++) { StudentDto studentDto = new StudentDto(); studentDto.setId(count); studentDto.setName("Java实体映射工具MapStruct详解"); studentDto.setClassName("清华大学一年级"); studentDto.setCreateTime(new Date()); Student student = new Student(); BeanUtils.copyProperties(studentDto, student); } System.out.println("BeanUtils 100w次实体映射耗时:" + (System.currentTimeMillis() - startTime)); startTime = System.currentTimeMillis(); for(int count = 0; count<1000000; count++) { StudentDto studentDto = new StudentDto(); studentDto.setId(count); studentDto.setName("Java实体映射工具MapStruct详解"); studentDto.setClassName("清华大学一年级"); studentDto.setCreateTime(new Date()); Student student = StudentMapper.INSTANCE.getModelFromDto(studentDto); } System.out.println("MapStruct 100w次实体映射耗时:" + (System.currentTimeMillis()-startTime)); System.out.println(); } } } 输出结果如下所示: BeanUtils 100w次实体映射耗时:548 MapStruct 100w次实体映射耗时:33 BeanUtils 100w次实体映射耗时:231 MapStruct 100w次实体映射耗时:35 BeanUtils 100w次实体映射耗时:227 MapStruct 100w次实体映射耗时:27 BeanUtils 100w次实体映射耗时:219 MapStruct 100w次实体映射耗时:29 BeanUtils 100w次实体映射耗时:218 MapStruct 100w次实体映射耗时:28 ``` ### 3 源码解析 #### 3.1 本地调试 因为这个注解处理器是在解析->编译的过程完成,跟普通的jar包调试不太一样,maven框架为我们提供了调试入口,需要借助maven才能实现debug。所以只需要在编译过程打开debug才可调试。 在项目的pom文件所在目录执行mvnDebug compile ![](//img1.jcloudcs.com/developer.jdcloud.com/616a9fe4-47c7-4334-ba28-e735dfa3df0e20220509163907.png) 接着用idea打开项目,添加一个remote,端口为8000 ![](//img1.jcloudcs.com/developer.jdcloud.com/9afacc9f-6a43-40ee-8e0d-a878af6c3d1f20220509163922.png) 打上断点,debug 运行remote即可调试。 注意: 1)本地提示没有mvndebug指令 安装maven,版本最好在3.6以下 Maven:https://maven.apache.org/download.cgi 2)启动报错Could not resolve dependencies for project XXX;Could not transfer artifact XXX;Blocked mirror for repositories XXX - 原因:基本出现在mvn package的时候,导入了公司内部包/非https地址的外部包。因为最新版本的maven block掉了所有HTTP协议的repositories,仅支持https;而公司内部的一些repositories是没有用https导致的 - 解决办法:1、直接设置IDEA的mvn版本为默认版本,IDEA 2021最新版本内置的maven是3.6.3,可以支持http;2、手动安装旧版本(3.6以下)的maven,使用旧版本导入即可(别忘了修改本地环境变量) 3)debug不进入断点,返回成功 - 原因:mapstruct是编译期执行,如果代码没有改动不会重新编译,所以不会触发断点 - 解决办法:手动修改后重新调试 #### 3.2 源码解析 ##### 3.2.1 原理分析 mapstruct原理是在编译时通过注解处理器解析,扫描@Mapper注解和被注解的接口,按照指定属性参数完成被注解的接口的实现类生成。实现类的方法将完成对象属性间的拷贝 Mapstruct使用“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。 举例来说,现在有一个实现了"JSR 269 API"的程序A,那么使用javac编译源码的时候具体流程如下: - javac对源代码进行分析,生成一棵抽象语法树(AST) ; - 运行过程中调用实现了"JSR 269 API"的A程序 ; - 此时A程序就可以完成它自己的逻辑,包括修改第一步骤得到的抽象语法树(AST) ; - javac使用修改后的抽象语法树(AST)生成字节码文件. mapstruct本质上就是这样的一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/6aab0c86-534d-477e-bd72-244de052401b20220509164049.png) 主要使用了processor来对解析生成过程进行处理,我们可以看到processor的相关定义 ![](//img1.jcloudcs.com/developer.jdcloud.com/af58b0de-7dab-4ed3-9b34-49acdcd3791620220509164103.png) 其中每个process节点都继承ModelElementProcessor基类 ![](//img1.jcloudcs.com/developer.jdcloud.com/53773acc-0d38-41d9-a7f7-a10a74455fe520220509164119.png) ##### 3.2.2 核心MappingProcessor mapstruct注解助理类org.mapstruct.ap.MappingProcessor MappingProcessor继承了javax.annotation.processing.AbstractProcessor类,AbstractProcessor的方法。 重点关注:public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) ![](//img1.jcloudcs.com/developer.jdcloud.com/57f68c39-93b9-4079-a1b4-4d368cb9a3ae20220509164149.png) 获取被@Mapper注解的接口信息具体是在MappingProcessor.getMappers(...)方法中这行代码体现 ![](//img1.jcloudcs.com/developer.jdcloud.com/fb33623c-2a43-4128-b264-a875aff4f2f520220509164206.png) ##### 3.2.3 链式处理Processor ![](//img1.jcloudcs.com/developer.jdcloud.com/7e1bdab7-56e7-46d8-ab95-5bbc1748e00620220509164559.png) 1)MethodRetrievalProcessor ![](//img1.jcloudcs.com/developer.jdcloud.com/712fe1f1-fc8e-4c3b-a67d-81434465ca5920220509164621.png) uses ```java // org.mapstruct.ap.internal.processor.MethodRetrievalProcessor.retrieveMethods(...) // 这短短代码对@Mapper注解的uses属性进行了处理 if (usedMapper.equals(mapperToImplement)) { // 本例中uses只设置了一个值 mapperConfig.uses()的值即com.mingo.exp.mapstruct.EntityObjFactory的mapper描述对象 var6 = mapperConfig.uses().iterator(); while(var6.hasNext()) { DeclaredType mapper = (DeclaredType)var6.next(); // 这里递归处理了com.mingo.exp.mapstruct.EntityObjFactory的mapper描述对象 // 会把EntityObjFactory类里的声明的方法加入到methods里:setApplicationContext、createEntity方法 methods.addAll(this.retrieveMethods(this.asTypeElement(mapper), mapperToImplement, mapperConfig, prototypeMethods)); } } ``` 2)MapperCreationProcessor 该处理器目的就是确定生成实现类的注解、包、属性、方法等各种修饰符 ![](//img1.jcloudcs.com/developer.jdcloud.com/77b13aed-74a8-4fe4-909b-41b803f72f1c20220509164710.png) getMapper() ![](//img1.jcloudcs.com/developer.jdcloud.com/90afe3b2-6afd-4f84-b04b-c873c442342f20220509164730.png) 3)CdiComponentProcessor 处理CDI方式 4)Jsr330ComponentProcessor 处理Jsr330方式 注意:由于@Mapper注解的属性componentModel = "spring",所以Cdi(第三)和Jsr(第四)开头的Processor将会被跳过 5)SpringComponentProcessor extends AnnotationBasedComponentModelProcessor ```java public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) { this.typeFactory = context.getTypeFactory(); MapperConfiguration mapperConfiguration = MapperConfiguration.getInstanceOn(mapperTypeElement); String componentModel = mapperConfiguration.componentModel(context.getOptions()); InjectionStrategyPrism injectionStrategy = mapperConfiguration.getInjectionStrategy(); // componentModel不等于"spring"时会直接返回 if (!this.getComponentModelIdentifier().equalsIgnoreCase(componentModel)) { return mapper; } else { Iterator var7 = this.getTypeAnnotations(mapper).iterator(); // 用于实现类上的Spring注解,这里是定值@Component while(var7.hasNext()) { Annotation typeAnnotation = (Annotation)var7.next(); mapper.addAnnotation(typeAnnotation); } if (!this.requiresGenerationOfDecoratorClass()) { mapper.removeDecorator(); } else if (mapper.getDecorator() != null) { this.adjustDecorator(mapper, injectionStrategy); } // 用于成员属性的Spring注解,这里是@Autowired List<Annotation> annotations = this.getMapperReferenceAnnotations(); ListIterator iterator = mapper.getFields().listIterator(); // 用新生成且含Spring注解的Field原来的替换Field while(iterator.hasNext()) { Field reference = (Field)iterator.next(); if (reference instanceof MapperReference) { iterator.remove(); iterator.add(this.replacementMapperReference(reference, annotations, injectionStrategy)); } } // @Mapper注解默认是InjectionStrategyPrism.FIELD注入 if (injectionStrategy == InjectionStrategyPrism.CONSTRUCTOR) { this.buildConstructors(mapper); } return mapper; } } MapperRenderingProcessor 主要用于渲染生成实现类java文件 public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) { if (!context.isErroneous()) { this.writeToSourceFile(context.getFiler(), mapper, mapperTypeElement); return mapper; } else { return null; } } private void writeToSourceFile(Filer filer, Mapper model, TypeElement originatingElement) { ModelWriter modelWriter = new ModelWriter(); this.createSourceFile(model, modelWriter, filer, originatingElement); if (model.getDecorator() != null) { this.createSourceFile(model.getDecorator(), modelWriter, filer, originatingElement); } } ``` 7)MapperServiceProcessor componentModel等于"default"时有效,生成文件放入META-INF/services下。本例中用不到,不会做其他操作 最终生成的文件 ![](//img1.jcloudcs.com/developer.jdcloud.com/e6a29dca-b76d-497a-9fab-86a8a7c427a420220509164840.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/d3e61480-8cbf-4ff6-9a5f-a55938394eac20220509164903.png) ### 4 结论 在我们的项目中有太多的拷贝动作,Set、Get方法过长且难维护,造成代码整洁度不高。使用Mapstruct可以极大优化我们的拷贝动作,使用方便,代码简洁,还能节省反射带来的性能开销,一举多得。 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:李洪吉
原创文章,需联系作者,授权转载
上一篇:mybatis-plus特性学习
下一篇:手摸手带你初探Vue 3.0
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号