天天看點

基于grpc從零開始搭建一個準生産分布式應用(6) - 05 - MapStruct特殊實作

開始前必讀:​​基于grpc從零開始搭建一個準生産分布式應用(0) - quickStart​​ 

一、Clone

@Mapper(mappingControl = DeepClone.class)//這裡需要注意用這個注解
public interface Cloner {
    Cloner MAPPER = Mappers.getMapper( Cloner.class );
    CustomerDto clone(CustomerDto customerDto);
}      

二、特殊注解

2.1、@qualifiedBy

這個注解的作用是當有重複的轉換類時,指定一個,但也可以用注解來實作,下面的例子就是從一個集合中的對象中特定的地方取值然後mapping到特定的屬性上,下面是一個集合應用。

public class Source {
    private List<Integer> myIntegers;
    private List<String> myStrings;
}
public class Target {
    private Integer myInteger;
    private String myString;
}
public static void main( String[] args ) {
    Source s = new Source();
    s.setMyIntegers( Arrays.asList( 5, 3, 7 ) );
    s.setMyStrings( Arrays.asList( "five", "three", "seven " ) );

    Target t = SourceTargetMapper.MAPPER.toTarget( s );
    System.out.println( t.getMyInteger() );
    System.out.println( t.getMyString() );
}
//5
//seven      
@Mapper( uses = IterableNonInterableUtil.class )
public interface SourceTargetMapper {
    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
    @Mapping( source = "myIntegers", target = "myInteger", qualifiedBy = FirstElement.class )
    @Mapping( source = "myStrings", target = "myString", qualifiedBy = LastElement.class )
    Target toTarget( Source s );
}

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface FirstElement {
}

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface LastElement {
}      
public class IterableNonInterableUtil {

    @FirstElement
    public <T> T first( List<T> in ) {
        if ( in != null && !in.isEmpty() ) {
            return in.get( 0 );
        }
        else {
            return null;
        }
    }

    @LastElement
    public <T> T last( List<T> in ) {
        if ( in != null && !in.isEmpty() ) {
            return in.get( in.size() - 1 );
        }
        else {
            return null;
        }
    }
}      

2.2、@ObjectFactory

@Mapper
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping( target = "descriptionArticle1", source = "brush.description" )
    @Mapping( target = "descriptionArticle2", source = "paste.description" )
    CombinedOfferingEntity toEntity(Toothbrush brush, ToothPaste paste, @Context ArticleRepository repo);

    @ObjectFactory
    default <T extends Entity> T lookup(Toothbrush brush, ToothPaste paste, @Context ArticleRepository repo,
        @TargetType Class<T> targetType ) {
        ComposedKey key = new ComposedKey(brush.getName(), paste.getName() );
        CombinedOfferingEntity entity = repo.lookup( key );
        if ( entity == null ) {
            entity = new CombinedOfferingEntity();
        }
        return (T) entity;
    }
}

//生成的代碼如下,上面代碼大概的邏輯是先執行工廠方法,然後先執行工廠方法,再執行餘下的映射
public CombinedOfferingEntity toEntity(Toothbrush brush, ToothPaste paste, ArticleRepository repo) {
    if ( brush == null && paste == null ) {
        return null;
    }

    CombinedOfferingEntity combinedOfferingEntity = lookup( brush, paste, repo, CombinedOfferingEntity.class );

    if ( brush != null ) {
        combinedOfferingEntity.setDescriptionArticle1( brush.getDescription() );
    }
    if ( paste != null ) {
        combinedOfferingEntity.setDescriptionArticle2( paste.getDescription() );
    }

    return combinedOfferingEntity;
}      

2.3、@Context循環依賴

public class Employee {
    private String name;
    private Employee reportsTo;
    private List<Employee> team;
}
public class EmployeeDto {
    private String employeeName;
    private EmployeeDto reportsTo;
    private List<EmployeeDto> team;
}
@Mapper
public interface EmployeeMapper {

    EmployeeMapper MAPPER = Mappers.getMapper( EmployeeMapper.class );

    @Mapping(source = "employeeName", target = "name")
    Employee toEmployee(EmployeeDto employeeDto, @Context CycleAvoidingMappingContext context);

    @InheritInverseConfiguration
    EmployeeDto fromEmployee(Employee employee, @Context CycleAvoidingMappingContext context);
}      
public class CycleAvoidingMappingContext {
    private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();

    @BeforeMapping
    public <T> T getMappedInstance(Object source, @TargetType Class<T> targetType) {
        return (T) knownInstances.get( source );
    }

    @BeforeMapping
    public void storeMappedInstance(Object source, @MappingTarget Object target) {
        knownInstances.put( source, target );
    }
}      

三、MapToObject互轉

public class Source {
    private Map<String, Object> map;
}
public class Target {
    private String ip;
    private String server;
}

@Mapper( uses = MappingUtil.class )
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping(source = "map", target = "ip", qualifiedBy = Ip.class )
    @Mapping(source = "map", target = "server", qualifiedBy = Server.class )
    Target toTarget(Source s);
}

public class MappingUtil {

    @Qualifier
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Ip {
    }

    @Qualifier
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public static @interface Server {
    }

    @Ip
    public String ip(Map<String, Object> in) {
        return (String) in.get("ip");
    }

    @Server
    public String server(Map<String, Object> in) {
        return (String) in.get("server");
    }
}

@Test
public void testMapperOnExistingIpAndServer() {

    Map<String, Object> map = new HashMap<>();
    map.put("ip", "127.0.0.1");
    map.put("server", "168.192.1.1");

    Source s = new Source(map);
    Target t = SourceTargetMapper.MAPPER.toTarget( s );
    System.out.println(JSONUtil.toJsonStr(t));
    //{"server":"168.192.1.1","ip":"127.0.0.1"}
}      
//下面的例子是從map中映射字段到object
public class Employee {

    private String id;
    private String name;
    private Department department;
}
public class Department {
    private String id;
    private String name;
}

@Mapper
public interface MapToBeanMapper {

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

    @Mapping(target = "department", ignore = true)
    Employee fromMap(Map<String, String> map);

    @AfterMapping
    default void finishEmployee(@MappingTarget Employee employee, Map<String, String> map) {
        employee.setDepartment(fromMapToDepartment(map));
    }

    @Mapping(target = "id", source = "did")
    @Mapping(target = "name", source = "dname")
    Department fromMapToDepartment(Map<String, String> map);
}

@Test
public void shouldMapMapToBean() {
    Map<String, String> map = new HashMap<>();
    map.put("id", "1234");
    map.put("name", "Tester");
    map.put("did", "4321"); //Department Id
    map.put("dname", "Test");// Depart name

    Employee employee = MapToBeanMapper.INSTANCE.fromMap(map);
}      

四、SPI實作

//主要是用于一些特定的規則處理
step-1:
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {

    @Override
    public boolean isGetterMethod(ExecutableElement method) {
        String methodName = method.getSimpleName().toString();
        return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID;
    }

    @Override
    public boolean isSetterMethod(ExecutableElement method) {
        String methodName = method.getSimpleName().toString();
        return methodName.startsWith( "with" ) && methodName.length() > 4;
    }

    @Override
    public String getPropertyName(ExecutableElement getterOrSetterMethod) {
        String methodName = getterOrSetterMethod.getSimpleName().toString();
        return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring(  4 ) : methodName );
    }
}      
step-2
在 resources/META-INF.services目錄下建立檔案
org.mapstruct.example.spi.CustomAccessorNamingStrategy
檔案内容
org.mapstruct.example.spi.CustomAccessorNamingStrategy      

五、lombok配置

這主要是配置,以指定comiple的順序
<!--處理lombok和mapstruft 加載順序的問題 防止在生成的實體沒有屬性-->
<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <annotationProcessorPaths>
                <path>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>${lombok.version}</version> //1.18.20 || 1.18.20
                </path>
                <path>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>${mapstruct.version}</version> //1.4.2.Final || 1.5.0.Beta2
                </path>

                <!-- additional annotation processor required as of Lombok 1.18.16 -->
                <path>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok-mapstruct-binding</artifactId>
                    <version>0.1.0</version>  //0.1.0 || 0.2.0
                </path>
            </annotationProcessorPaths>
        </configuration>
    </plugin>
</plugins>