• 发现Mapstruct的一个bug


    前言


    在一次需求的开发过程中,发现一个对象插入数据库时某个字段总是为空。

    版本:lombok:1.18.24、mapstruct:1.5.2.Final

    简化后的代码如下:

    @Autowired
        private PersonService personService;
        
        public void test1(){
            Person person = personService.findById(1L);
            PersonDto personDto = PersonMapper.INSTANCE.personToPersonDto(person);
            personService.insert(personDto);
        }
    

    这么简单的逻辑按理说不会出幺蛾子啊,我先排查了数据库里person id=1的记录发现值是有的啊,然后又排查了我的insert方法,也是没问题的。


    经过一段时间的排查,才发现是

    PersonDto personDto = PersonMapper.INSTANCE.personToPersonDto(person);
    

    这行代码的问题。证据截图如下:

    image.png

    前面的时候addTeacherNum还有值,转化后怎么又没值了呢?

    大家看到这里肯定猜测是不是我属性名不对,或者属性类型不对。我甚至还删除了之后用复制的方式来保证没有手敲敲错的情况。

    image.png

    完全是一模一样的属性啊。

    image.png

    我们知道mapstruct是编译时通过我们的PersonMapper接口来实现实现类,实现类里是setter、getter方法来实现的。于是我打开了PersonMapper的实现类准备一探究竟:

    public class PersonMapperImpl implements PersonMapper {
        public PersonMapperImpl() {
        }
    
        public PersonDto personToPersonDto(Person person) {
            if (person == null) {
                return null;
            } else {
                PersonDtoBuilder personDto = PersonDto.builder();
                personDto.name(person.getName());
                return personDto.build();
            }
        }
    }
    

    竟然没有对我这个属性addTeacherNum进行赋值。这让我百思不得其解。只能去看看源码,试图找出原因。

    如何调试Maven插件


    前面我们提到mapstruct是在代码编译的时候就开始生成代码了,于是我们需要对maven编译期进行调试。方法如下:

    1. maven debug命令
    mvnDebug clean compile
    
    1. idea远程debug

    image.png

    新建一个remote,然后修改端口为8000,然后在执行maven命令的同时,启动这个remote即可。

    源码解析


    断点应该打在哪里呢?

    我们查看mapstruct的结构,一般先从配置的文件入手
    image.png

    找到了这个MappingProcessor类,我们可以看到这里面有个process方法,里面又调用了如下的这个方法:

    private void processMapperTypeElement(ProcessorContext context, TypeElement mapperTypeElement) {
        Object model = null;
    
        for ( ModelElementProcessor<?, ?> processor : getProcessors() ) {
            try {
                model = process( context, processor, mapperTypeElement, model );
            }
            catch ( AnnotationProcessingException e ) {
               //省略
            }
        }
    }
    

    这段代码其实就是调用getProcessors()方法拿到多个processor,然后遍历调用。而这个getProcessors()就是从配置文件里通过SPI的方式加载对象。

    image.png

    这里面我们重点关注这个Processor:MapperCreationProcessor。它的process方法如下:

    @Override
    public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
        this.elementUtils = context.getElementUtils();
        this.typeUtils = context.getTypeUtils();
        this.messager =
            new MapperAnnotatedFormattingMessenger( context.getMessager(), mapperTypeElement, context.getTypeUtils() );
        this.options = context.getOptions();
        this.versionInformation = context.getVersionInformation();
        this.typeFactory = context.getTypeFactory();
        this.accessorNaming = context.getAccessorNaming();
    
        MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() );
        List<MapperReference> mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions );
    
        MappingBuilderContext ctx = new MappingBuilderContext(
            typeFactory,
            elementUtils,
            typeUtils,
            messager,
            accessorNaming,
            context.getEnumMappingStrategy(),
            context.getEnumTransformationStrategies(),
            options,
            new MappingResolverImpl(
                messager,
                elementUtils,
                typeUtils,
                typeFactory,
                new ArrayList<>( sourceModel ),
                mapperReferences,
                options.isVerbose()
            ),
            mapperTypeElement,
            //sourceModel is passed only to fetch the after/before mapping methods in lifecycleCallbackFactory;
            //Consider removing those methods directly into MappingBuilderContext.
            Collections.unmodifiableList( sourceModel ),
            mapperReferences
        );
        this.mappingContext = ctx;
        return getMapper( mapperTypeElement, mapperOptions, sourceModel );
    }
    

    getMapper里面有一段这个方法引起我的注意:

    List<MappingMethod> mappingMethods = getMappingMethods( mapperOptions, methods );
    

    猜测这段就是获取要写入的set、get方法。
    于是一路跟踪:

    发现mapstruct里面把方法分为了下面四类,而我的addTeacherNum属性通过lombok生成的方法methodType被分到了ADDER里面。image.png

    而在生成我的Mapper实现类的时候它会只过滤setter方法。

    List<Accessor> candidates = new ArrayList<>( getSetters() );
    

    至此真相大白。

  • 相关阅读:
    获取ocx运行路径的另一种方法
    使用D3D渲染YUV视频数据
    C++(MFC)中WebBrowser去除3D边框的方法(实现IDocHostUIHandler接口)
    ActiveX控件的安全初始化和脚本操作 和 数字签名SIGN
    解决Eclipse中的卡死现象
    Http请求头和响应头
    HTTP请求头与响应头
    centos7 Mariadb5.5升级到Mariadb10.2
    window下利用navicat访问Linux下的mariadb数据库
    在Linux上安装及配置MariaDB
  • 原文地址:https://www.cnblogs.com/javammc/p/16469763.html
Copyright © 2020-2023  润新知