14定制化Mappings

有时,我们需要在某些映射方法之前或之后添加一些自定义逻辑。MapStruct为此提供了两种方法:

  1. 基于装饰器模式的方法:它允许对特定映射方法进行类型安全的定制;
  2. 映射前和映射后生命周期方法:它允许对具有给定源或目标类型的映射方法进行通用的定制。


1、基于装饰器模式的方法

在某些情况下,可能需要定制一个生成的映射方法,例如:在目标对象中设置一个不能由生成的方法实现设置的附加属性。MapStruct使用装饰器支持这一需求。

要将装饰器应用到映射器类,请使用@DecoratedWith注释指定它。

下面,我们来看看具体的代码示例。

(1)实体类定义,这里为了重点描述装饰器,给出的实体非常简单。

@Data
public class Source {

    private int value;

}
@Data
public class Target {

    private int value;

}

(2)Mapper

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

    TestMapper INSTANCE = Mappers.getMapper(TestMapper.class);

    Target s2t(Source source);

    Source t2s(Target target);

}

(3)装饰器

public abstract class TestDecotator implements TestMapper {

    private final TestMapper delegate;

    public TestDecotator(TestMapper delegate) {
        this.delegate = delegate;
    }

    @Override
    public Target s2t(Source source) {
        Target target = delegate.s2t(source);
        target.setValue(target.getValue() + 10);
        return target;
    }
}

装饰器必须是被装饰的映射器类型的子类型。你可以让它成为一个抽象类,它只允许实现那些你想要定制的mapper接口的方法。对于所有未实现的方法,将直接调用原始映射器的对应方法。【具体可以看下文生成的代码】

(4)测试

public class Main {

    public static void main(String[] args) {
        Source source = new Source();
        source.setValue(5);

        Target target = TestMapper.INSTANCE.s2t(source);
        System.out.println(target);
    }
}

程序的输出结果如下:

Target(value=15)

(5)小结

我们看到,MapStruct生成了两个Mapper实现类:

这里贴一下Mapper实现类的代码:

TestMapperImpl_.class

public class TestMapperImpl_ implements TestMapper {

    @Override
    public Target s2t(Source source) {
        if ( source == null ) {
            return null;
        }

        Target target = new Target();

        target.setValue( source.getValue() );

        return target;
    }

    @Override
    public Source t2s(Target target) {
        if ( target == null ) {
            return null;
        }

        Source source = new Source();

        source.setValue( target.getValue() );

        return source;
    }
}

TestMapperImpl.class

public class TestMapperImpl extends TestDecotator {

    private final TestMapper delegate;

    public TestMapperImpl() {
        this( new TestMapperImpl_() );
    }

    private TestMapperImpl(TestMapperImpl_ delegate) {
        super( delegate );
        this.delegate = delegate;
    }

    @Override
    public Source t2s(Target target)  {
        return delegate.t2s( target );
    }
}

对于componentModel = "default"的Mapper,装饰器必须定义一个带有单个形参的构造函数,该形参接受修饰后的映射器的类型。

而对于使用依赖注入的Mapper,其使用方式有点不同。

当在带有组件模型spring的映射器上使用@DecoratedWith时,原始映射器的生成实现会使用spring注解@Qualifier("delegate")进行注释。要在装饰器中自动装配该bean,还要添加限定符注释:

public abstract class PersonMapperDecorator implements PersonMapper {

     @Autowired
     @Qualifier("delegate")
     private PersonMapper delegate;

     @Override
     public PersonDto personToPersonDto(Person person) {
         PersonDto dto = delegate.personToPersonDto( person );
         dto.setName( person.getFirstName() + " " + person.getLastName() );

         return dto;
     }
 }

生成的装饰器的实现类使用Spring的@Primary注解进行标注。应用程序中会自动注入装饰过的映射器,不需要做任何特殊的操作。

@Autowired
private PersonMapper personMapper; 


2、映射前和映射后生命周期方法

在定制映射器时,装饰器可能并不总是符合需求。例如:如果您不仅需要为一些选定的方法执行定制,而且需要为映射特定超级类型的所有方法执行定制。在这种情况下,您可以使用在映射开始之前或映射完成之后调用的回调方法。

回调方法可以在抽象映射器本身中实现,也可以在Mapper#uses中引用的类型中实现,或者在用@Context注解标注的参数中实现。

下面,我们来看看具体的代码示例。

(1)实体类定义,这里为了重点描述装饰器,给出的实体非常简单。

@Data
public class Source {

    private String value;

}
@Data
public class Target {

    private String value;

}

(2)Mapper

@Mapper
public abstract class SourceTargetMapper {

    public static final SourceTargetMapper INSTANCE = Mappers.getMapper(SourceTargetMapper.class);

    @BeforeMapping
    protected void beforeMapping(Source source) {
        System.out.println("转换前,打印源参数:" + source.toString());
    }

    @AfterMapping
    protected void afterMapping(Source source, @MappingTarget Target target) {
        System.out.println("转换后,打印源参数:" + source.toString());
        System.out.println("转换后,打印目标参数:" + target.toString());
    }

    abstract Target s2t(Source source);
}

转换前调用的方法使用@BeforeMapping标注;

转换后调用的方法使用@AfterMapping标注;

(3)测试

public class Main {

    public static void main(String[] args) {
        Source source = new Source();
        source.setValue("Hello");

        Target target = SourceTargetMapper.INSTANCE.s2t(source);
        System.out.println(target);
    }
}

程序的输出结果如下:

转换前,打印源参数:Source(value=Hello)
转换后,打印源参数:Source(value=Hello)
转换后,打印目标参数:Target(value=Hello)
Target(value=Hello)

(5)小结

如果@BeforeMapping / @AfterMapping方法有参数,只有当方法的返回类型(如果非void)可以赋值给映射方法的返回类型,并且所有参数都可以由映射方法的源或目标参数赋值时,才会生成方法调用:

对于非void方法,如果方法调用的返回值不为空,则作为映射方法的结果返回。

与映射方法一样,可以为映射之前/之后的方法指定类型参数。


可以应用到映射方法的所有映射前/映射后方法都将被使用。基于限定符的映射方法选择可以用于进一步控制可以选择哪些方法和不可以选择哪些方法。为此,限定符注释需要应用于before/after方法,并在BeanMapping#qualifiedBy或IterableMapping#qualifiedBy中引用。

TODO:没理解

方法调用的顺序主要由它们的变体决定:

  1. 不带@MappingTarget参数的@BeforeMapping方法在对源参数进行任何空检查和构造新的目标bean之前被调用。
  2. 带有@MappingTarget参数的@BeforeMapping方法在构造新的目标bean之后被调用。
  3. @AfterMapping方法在映射方法的最后一个返回语句之前被调用。

在这些组中,方法调用按定义的位置排序:

  1. 在@Context参数中声明的方法,按参数顺序排序。
  2. 在当前Mapper中实现的方法。
  3. Mapper#uses()中引用的类型的方法,按照注解中类型声明的顺序。
  4. 在子类型中声明的方法会在超类型中声明的方法之后被调用。

重要:不能保证在一个类型中声明的方法的顺序,因为它取决于编译器和处理环境实现。

展开阅读全文

页面更新:2024-02-23

标签:注解   注释   顺序   定义   声明   参数   目标   类型   代码   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top