MapStruct文档(十)——映射扩展

目录

9.1、*装饰器映射

9.2、映射前置/后置方法

9.3、循环嵌套对象调用前后置方法


9.1、*装饰器映射

在进行映射时可以通过装饰器模式,为目标对象设置一些,不能由源对象直接生成或者源对象没有的属性。

spring注入策略的装饰器映射

使用@DecoratedWith注解制定装饰器。装饰器中使用@Qualifier("delegate")指定注入的bean。


@Mapper
@DecoratedWith(TestDecotator.class)
public interface TestMapper {

    TestBO toTestBO(TestPO testPO);


}
 
public abstract class TestDecorator implements TestMapper {

    @Autowired
    @Qualifier("delegate")
    private TestMapper testMapper;

    @Override
    public TestBO toTestBO(TestPO testPO) {
        TestBO testBO = testMapper.toTestBO(testPO);
        testBO.setName(testPO.getNameString() + testPO.getCreateTimeString());
        return testBO;
    }
}
 
@Component
@Primary
public class TestMapperImpl extends TestDecorator implements TestMapper {
}
 
@Component
@Qualifier("delegate")
public class TestMapperImpl_ implements TestMapper {

    @Override
    public TestBO toTestBO(TestPO testPO) {
        if ( testPO == null ) {
            return null;
        }

        TestBO testBO = new TestBO();

        if ( testPO.getPriceString() != null ) {
            testBO.setPrice( testPO.getPriceString().toString() );
        }
        testBO.setName( testPO.getNameString() );
        testBO.setId( testPO.getId() );

        return testBO;
    }
}
 
TestPO testPO = new TestPO();
testPO.setId(20L);
testPO.setNameString("test");
testPO.setPriceString(new BigDecimal("12.3"));
testPO.setCreateTimeString(new Date(System.currentTimeMillis()));
System.out.println(testMapper.toTestBO(testPO));

结果

生成的TestMapperImpl@Primary注解,使用注入是被选为第一候选,TestMapperImpl_@Qualifier("delegate")注解,指明了bean名称,所以在TestDecorator指明名称注入。


9.2、映射前置/后置方法

可以在抽象mapper类Mapper#uses引入的类或被@Context注解的上下文对象类中设置映射前后的回调方法。

@BeforeMapping注解的方法是前置方法,@AfterMapping注解的方法是后置方法。

如果前后置方法具有参数,返回的类型能被赋予映射方法的返回类型并且参数都可以用源/目标参数获取,才会调用前后置方法。

前后置方法中用@MappingTarget注解的参数获得的是目标实例,@TargetType获取的是目标类型,@Context可以获取上下文对象,其他的参数被赋予源参数。

然后前后置方法不是void的,返回的值不为null的情况下将作为映射方法的返回值。

同样,若前后置方法能匹配到多个,将会都调用,可以通过给前置方法添加@Named和映射方法上添加@BeanMapping#qualifiedByName制定要调用的前后置方法;一旦使用了@BeanMapping#qualifiedByName就必须指定自己选用调用的所有前后置方法的@Named名。

不带@MappingTarget注解参数的@BeforeMapping会在源参数进行null检查并且构造新的目标bean之前调用;

@MappingTarget注解参数的@BeforeMapping会在构造新的目标bean之后调用;

@AfterMapping会在return前最后调用。

在使用建造者时,@BeforeMapping@AfterMapping中要想获取目标对象,@MappingTarget注解的就必须是建造者。


@Mapper
@Named("baseMapper")
public class BaseMapper {

    @BeforeMapping
    @Named("before")
    public void before(BasePO basePO, @MappingTarget BaseBO baseBO) {
        System.out.println(basePO);
        System.out.println(baseBO);
    }

    @AfterMapping
    public <T> T after(@TargetType Class<T> clazz, @Context ThreadLocalContext threadLocalContext){
        System.out.println(threadLocalContext);
        T t = null;
        try {
           t = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return t;
    }

}
 
@Data
public class ThreadLocalContext {

    private ThreadLocal<Object> threadLocal;

    public ThreadLocalContext() {
        threadLocal = new ThreadLocal<>();
    }

    @BeforeMapping
    @Named("before2")
    public static void before2(BasePO basePO, @MappingTarget BaseBO baseBO) {
        System.out.println(basePO);
        System.out.println(baseBO);
    }
}
 
@Mapper(uses = BaseMapper.class)
public interface TestMapper {

    @BeanMapping(qualifiedByName = { "baseMapper"})
    TestSixBO toTestBO(TestFourPO testPO, @Context ThreadLocalContext threadLocalContext);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper baseMapper;

    @Override
    public TestSixBO toTestBO(TestFourPO testPO, ThreadLocalContext threadLocalContext) {
        if ( testPO == null ) {
            return null;
        }

        TestSixBO testSixBO = new TestSixBO();

        baseMapper.before( testPO, testSixBO );

        testSixBO.setId( testPO.getId() );
        testSixBO.setName( testPO.getName() );
        if ( testPO.getCreateTime() != null ) {
            testSixBO.setCreateTime( new SimpleDateFormat().format( testPO.getCreateTime() ) );
        }

        TestSixBO target = baseMapper.after( TestSixBO.class, threadLocalContext );
        if ( target != null ) {
            return target;
        }

        return testSixBO;
    }
}

9.3、循环嵌套对象调用前后置方法

若有类似于权限菜单的List嵌套结构的对象,mapstruct也可以自动映射。


@Data
public class TestBOS {

    private TestBOS testS;

    private List<TestBOS> list;
}
 
@Data
public class TestPOS {

    private TestPOS testS;

    private List<TestPOS> list;
}
 
@Mapper
public class BaseMapper {

    @BeforeMapping
    public <T> T before(Object source, @TargetType Class<T> targetType) {
        T t;
        try {
            t = targetType.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            return null;
        }
        return t;
    }

}


@Mapper(uses = BaseMapper.class)
public interface TestMapper {

    TestBOS toBean(TestPOS testPOS);

}
 
@Component
public class TestMapperImpl implements TestMapper {

    @Autowired
    private BaseMapper baseMapper;

    @Override
    public TestBOS toBean(TestPOS testPOS) {
        TestBOS target = baseMapper.before( testPOS, TestBOS.class );
        if ( target != null ) {
            return target;
        }

        if ( testPOS == null ) {
            return null;
        }

        TestBOS testBOS = new TestBOS();

        testBOS.setTestS( toBean( testPOS.getTestS() ) );
        testBOS.setList( testPOSListToTestBOSList( testPOS.getList() ) );

        return testBOS;
    }

    protected List<TestBOS> testPOSListToTestBOSList(List<TestPOS> list) {
        List<TestBOS> target = baseMapper.before( list, List.class );
        if ( target != null ) {
            return target;
        }

        if ( list == null ) {
            return null;
        }

        List<TestBOS> list1 = new ArrayList<TestBOS>( list.size() );
        for ( TestPOS testPOS : list ) {
            list1.add( toBean( testPOS ) );
        }

        return list1;
    }
}

看自动生成的testPOSListToTestBOSList的映射方法中也调用了前置方法,因为前置方法对于任意的Object源类型,泛型的目标类型都会调用,方法体中list1.add( toBean( testPOS ) )继续调用toBean映射方法,toBean映射方法中循环调用testBOS.setTestS( toBean( testPOS.getTestS() ) ),且继续调用testBOS.setList( testPOSListToTest

### 如何配置和使用 VSCode 调试工作台 #### 配置调试环境 对于特定项目如 SRS 源码,在进入 `struct` 目录之后,可以通过如下命令来准备可调试版本的程序: ```bash ./configure --static=on --extra-flags="-c -g" make -j4 ``` 这组指令确保编译过程中加入了 `-g` 参数以便生成用于调试的信息[^1]。 #### 创建 launch.json 文件 VSCode 使用名为 `.vscode/launch.json` 的文件来进行调试配置。此 JSON 文件定义了一系列启动配置项,允许开发者指定要使用的调试器类型、参数以及其他选项。针对不同类型的编程语言和技术栈,存在多种预设模板可供选择。 对于 C/C++ 类型的应用来说,典型的 `launch.json` 可能看起来像这样: ```json { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/srs", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "C/C++: g++.exe build active file", "miDebuggerPath": "/usr/bin/gdb", "internalConsoleOptions": "openOnSessionStart" } ] } ``` 上述配置适用于 Linux 或 macOS 平台上基于 GDB 的本地应用程序调试;其中 `"program"` 字段应指向实际构建后的二进制文件路径。 #### 设置断点并开始调试会话 一旦完成了必要的准备工作——即安装了合适的扩展插件(例如 C/C++ 扩展)、设置了正确的编译标志以及创建好了 `launch.json` ——就可以利用左侧边栏中的调试图标或者顶部菜单里的相应入口进入到调试界面。在这里可以选择之前定义好的某个配置方案作为当前会话的基础,并点击绿色箭头按钮正式启动调试过程。 在源代码编辑窗口内单击行号区域即可快速设立临时性的断点标记,当执行流触及这些位置时便会自动暂停下来等待进一步指示。此时可以在右侧弹出的小窗格里查看变量状态变化情况、评估表达式的即时结果或是逐步推进至下一行语句继续跟踪逻辑走向。 #### 开启 Source Map 支持 (如果适用) 对于 TypeScript 和 JavaScript 项目而言,启用 source maps 是分重要的一步操作,因为它们使得开发人员能够在原始未压缩形式下的源文件中进行有效的错误排查活动而不必面对经过转换处理过的最终产物所带来的困扰。为此目的而设计的相关属性应当被加入到项目的 tsconfig.json 中去: ```json { "compilerOptions": { "sourceMap": true, "outDir": "./dist" } } ``` 以上设置告知编译工具为每一个 .ts 文件都额外产出对应的 .js.map 映射文档,从而让浏览器端或 Node.js 环境能够正确识别并加载这些资源以辅助后续可能出现的各种诊断需求[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值