接入Spring,使用Spring的依赖注入,能使MapStruct变得更加强大。

创建需要映射的POJO

@Data  
@Builder  
public class X {  
    private String firstName;  
    private Integer type;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String typeName;  
}

创建Spring的service

@Service  
public class TypeService {  
  
    public String convert(Integer type){  
        String typeName = "未知";  
        if (type == null) {  
            return typeName;  
        }  
        switch (type) {  
            case 1:  
                typeName = "飞机";  
                break;  
            case 2:  
                typeName = "大炮";  
                break;  
            case 3:  
                typeName = "坦克";  
                break;  
            case 4:  
                typeName = "潜艇";  
                break;  
            default:  
                break;  
        }  
        return typeName;  
    }  
}

编写映射接口

@Mapper 注解中,增加 componentModel属性,指定为 spring
然后使用 @Autowired 注入TypeService。

@Mapper(builder = @Builder(disableBuilder = true), componentModel = "spring")  
public abstract class XYMapper {  
  
    @Autowired  
    protected TypeService typeService;  
  
    @Mapping(target = "typeName", expression = "java(typeService.convert(x.getType()))")  
    public abstract Y source2Destination(X x);  
  
}

生成的接口实现

可以看到,生成的实现中,添加了 @Component 注解,这样就归Spring进行生命周期管理。

@Component  
public class XYMapperImpl extends XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String firstName = null;  
  
        firstName = x.getFirstName();  
  
        String typeName = typeService.convert(x.getType());  
  
        Y y = new Y( firstName, typeName );  
  
        return y;  
    }  
}

测试验证

@RestController  
@AllArgsConstructor  
@Slf4j  
public class MessageController implements MessageClient {  
  
    private XYMapper xyMapper;  
  
    @Override  
    public SingleResponse<String> sendMessage(SendMsgCmd cmd) {  
    
        X x = X.builder().firstName("GG").type(1).build();  
        System.out.println(x);  
        Y y = xyMapper.source2Destination(x);  
        System.out.println(y);  

        return SingleResponse.of("eventId");  
    }  
  
}
X(firstName=GG, type=1)
Y(firstName=GG, typeName=飞机)

上面的情况都是比较常见、简单的情况,还有更复杂的情况。
比如当字段是null时,可以使用上面的方法处理,但是字段类型String值为""时,那么该字段不是null就无法适用了。
亦或者,当字段为0或者某些特定值时,需要进行特殊的默认值处理。

上面这些特殊的情况,都可以进行自定义处理。

使用 @BeforeMapping 和 @AfterMapping 进行处理

创建的POJO如下:

@Data  
@Builder  
public class X {  
    private String firstName;  
    private String lastName;  
    private String fullName;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String lastName;  
    private String fullName;  
}

映射接口如下:

@Mapper(builder = @Builder(disableBuilder = true))  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(source = "lastName", target = "lastName", defaultValue = "不晓得")  
    Y source2Destination(X x);  
  
    @AfterMapping  
    default void after(@MappingTarget Y y) {  
        if (StringUtils.isBlank(y.getLastName())) {  
            y.setLastName("我是真的不晓得");  
        }  
        if("X".equals(y.getFullName())){  
            y.setFullName("X先生");  
        }  
    }  
  
}

生成的接口实现如下:

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String lastName = null;  
        String firstName = null;  
        String fullName = null;  
  
        if ( x.getLastName() != null ) {  
            lastName = x.getLastName();  
        }  
        else {  
            lastName = "不晓得";  
        }  
        firstName = x.getFirstName();  
        fullName = x.getFullName();  
  
        Y y = new Y( firstName, lastName, fullName );  
  
        after( y );  
  
        return y;  
    }  
}

测试如下:

使用空字符串lastName和值为“X”的fullName进行测试:

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().firstName("gg").lastName("").fullName("X").build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(firstName=gg, lastName=, fullName=X)
Y(firstName=gg, lastName=我是真的不晓得, fullName=X先生)

使用自定义方法加 @QualifiedName 进行处理

创建POJO

@Data  
@Builder  
public class X {  
    private String firstName;  
    private Integer status;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String statusName;  
}

编写映射接口

映射接口中,创建一个默认方法 statusConvert 用于将Integer类型的status字段转换成String类型
的StatusName字段,并且给这个方法添加注解 @Nameed("statusConvert")
然后在 @Mapping 中,使用 qualifiedByName 属性指定上面给定的名称进行绑定。

@Mapper(builder = @Builder(disableBuilder = true))  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(source = "status", target = "statusName", qualifiedByName = "statusConvert")  
    Y source2Destination(X x);  
  
    @Named("statusConvert")  
    default String statusConvert(Integer status) {  
        if (status == null) {  
            return "未知";  
        } else if (status == 1) {  
            return "成功";  
        } else if (status == 2) {  
            return "失败";  
        }  
        return "未知";  
    }  
  
}

接口实现:

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String statusName = null;  
        String firstName = null;  
  
        statusName = statusConvert( x.getStatus() );  
        firstName = x.getFirstName();  
  
        Y y = new Y( firstName, statusName );  
  
        return y;  
    }  
}

测试验证

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().firstName("gg").status(1).build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(firstName=gg, status=1)
Y(firstName=gg, statusName=成功)

使用表达式处理

前面我们已经见过 expressiondefaultExpression 表达式了,表达式非常强大,既可以返回简单的字符串,也可以调用方法进行处理。

下面简单展示一个调用方法传递参数的例子。

创建POJO

@Data  
@Builder  
public class X {  
    private String firstName;  
    private Integer type;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String typeName;  
}

创建外部处理类

public class TypeUtils {  
  
    public static String convert(Integer type) {  
        String typeName = "未知";  
        if (type == null) {  
            return typeName;  
        }  
        switch (type) {  
            case 1:  
                typeName = "飞机";  
                break;  
            case 2:  
                typeName = "大炮";  
                break;  
            case 3:  
                typeName = "坦克";  
                break;  
            case 4:  
                typeName = "潜艇";  
                break;  
            default:  
                break;  
        }  
        return typeName;  
    }  
}

编写映射接口

@Mapper(builder = @Builder(disableBuilder = true))  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(target = "typeName", expression = "java(com.tongde.digitalfabric.incident.test.TypeUtils.convert(x.getType()))")  
    Y source2Destination(X x);  
  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String firstName = null;  
  
        firstName = x.getFirstName();  
  
        String typeName = com.tongde.digitalfabric.incident.test.TypeUtils.convert(x.getType());  
  
        Y y = new Y( firstName, typeName );  
  
        return y;  
    }  
}

验证测试

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().firstName("gg").type(2).build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(firstName=gg, type=2)
Y(firstName=gg, typeName=大炮)

当源对象字段值为null时给定默认值

通常的情况时这样,源对象中的字段如果不为null,那么直接给目标对象中的对应字段进行赋值。
但是如果源对象中的字段是null,那么给目标对象中的对应字段赋默认值。

默认值有两种处理,一个是用 defaultValue 给定值即可,另外一种是使用 defaultExpression 使用表达式进行处理。

创建需要映射的POJO

@Data  
@Builder  
public class X {  
    private String firstName;  
    private String lastName;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String lastName;  
}

编写映射接口

@Mapper(builder = @Builder(disableBuilder = true))  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(source = "lastName", target = "lastName", defaultValue = "不晓得")  
    Y source2Destination(X x);  
  
    @Mapping(source = "lastName", target = "lastName", defaultExpression = "java(\"不晓得呀呀呀\")")  
    Y source2Destination2(X x);  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String lastName = null;  
        String firstName = null;  
  
        if ( x.getLastName() != null ) {  
            lastName = x.getLastName();  
        }  
        else {  
            lastName = "不晓得";  
        }  
        firstName = x.getFirstName();  
  
        Y y = new Y( firstName, lastName );  
  
        return y;  
    }  
  
    @Override  
    public Y source2Destination2(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String lastName = null;  
        String firstName = null;  
  
        if ( x.getLastName() != null ) {  
            lastName = x.getLastName();  
        }  
        else {  
            lastName = "不晓得呀呀呀";  
        }  
        firstName = x.getFirstName();  
  
        Y y = new Y( firstName, lastName );  
  
        return y;  
    }  
}

测试验证

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().firstName("gg").build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
        Y y2 = XYMapper.INSTANCE.source2Destination2(x);  
        System.out.println(y2);  
    }  
  
}

直接给目标对象中新字段给定默认值

即目标对象中有新的字段,在源对象中没有对应的字段,直接给新字段进行赋值。
可以通过 constant 属性或者 expression 来进行处理。

创建需要映射的POJO

@Data  
@Builder  
public class X {  
    private String firstName;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String lastName;  
}

编写映射接口

@Mapper(builder = @Builder(disableBuilder = true))  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(target = "lastName", constant = "不晓得")  
    Y source2Destination(X x);  
    
    @Mapping(target = "lastName", expression = "java(\"真的不晓得\")")  
    Y source2Destination2(X x);
  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String firstName = null;  
  
        firstName = x.getFirstName();  
  
        String lastName = "不晓得";  
  
        Y y = new Y( firstName, lastName );  
  
        return y;  
    }  
  
    @Override  
    public Y source2Destination2(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String firstName = null;  
  
        firstName = x.getFirstName();  
  
        String lastName = "真的不晓得";  
  
        Y y = new Y( firstName, lastName );  
  
        return y;  
    }  
}

测试验证

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().firstName("gg").build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
        Y y2 = XYMapper.INSTANCE.source2Destination2(x);  
        System.out.println(y2);  
    }  
  
}
X(firstName=gg)
Y(firstName=gg, lastName=不晓得)
Y(firstName=gg, lastName=真的不晓得)

基础映射

创建需要映射的POJO

咱们使用两个基础对象做示范。

注意:代码中使用了lombok
@Data  
@Builder  
public class X {  
    private Long id;  
    private String name;  
    private String description;  
}
@Data  
@Builder  
public class Y {  
    private Long id;  
    private String name;  
    private String description;  
}

编写映射接口

@Mapper  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    Y source2Destination(X source);  
  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X source) {  
        if ( source == null ) {  
            return null;  
        }  
  
        Y.YBuilder y = Y.builder();  
  
        y.id( source.getId() );  
        y.name( source.getName() );  
        y.description( source.getDescription() );  
  
        return y.build();  
    }  
}

测试验证

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().id(1L).name("哈利路亚").description("哈哈").build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(id=1, name=哈利路亚, description=哈哈)
Y(id=1, name=哈利路亚, description=哈哈)

字段名不同的映射

创建需要映射的POJO

@Data  
@Builder  
public class X {  
    private Long id;  
    private String name;  
    private String description;  
}
@Data  
@Builder  
public class Y {  
    private Long targetId;  
    private String placeName;  
    private String desc;  
}

编写映射接口

使用 @Mapping 注解配置映射的字段名称。

@Mapper  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(source = "id", target = "targetId")  
    @Mapping(source = "name", target = "placeName")  
    @Mapping(source = "description", target = "desc")  
    Y source2Destination(X source);  
  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {

    @Override
    public Y source2Destination(X source) {
        if ( source == null ) {
            return null;
        }

        Y.YBuilder y = Y.builder();

        y.targetId( source.getId() );
        y.placeName( source.getName() );
        y.desc( source.getDescription() );

        return y.build();
    }
}

测试验证

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().id(1L).name("哈利路亚").description("哈哈").build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(id=1, name=哈利路亚, description=哈哈)
Y(targetId=1, placeName=哈利路亚, desc=哈哈)

子对象的映射

创建需要映射的POJO

@Data  
@Builder  
public class Foo {  
    private String fooName;  
}
@Data  
@Builder  
public class Bar {  
    private String barName;  
}
@Data  
@Builder  
public class X {  
    private Long id;  
    private String name;  
    private String description;  
  
    private Foo foo;  
}
@Data  
@Builder  
public class Y {  
    private Long targetId;  
    private String placeName;  
    private String desc;  
  
    private Bar bar;  
}

编写映射接口

@Mapper  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(source = "id", target = "targetId")  
    @Mapping(source = "name", target = "placeName")  
    @Mapping(source = "description", target = "desc")  
    @Mapping(source = "foo", target = "bar")  
    Y source2Destination(X source);  
  
    @Mapping(source = "fooName", target = "barName")  
    Bar foo2Bar(Foo foo);  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X source) {  
        if ( source == null ) {  
            return null;  
        }  
  
        Y.YBuilder y = Y.builder();  
  
        y.targetId( source.getId() );  
        y.placeName( source.getName() );  
        y.desc( source.getDescription() );  
        y.bar( foo2Bar( source.getFoo() ) );  
  
        return y.build();  
    }  
  
    @Override  
    public Bar foo2Bar(Foo foo) {  
        if ( foo == null ) {  
            return null;  
        }  
  
        Bar.BarBuilder bar = Bar.builder();  
  
        bar.barName( foo.getFooName() );  
  
        return bar.build();  
    }  
}

验证测试

public class Test {  
  
    public static void main(String[] args) {  
        Foo foo = Foo.builder().fooName("FOO").build();  
        X x = X.builder().id(1L).name("哈利路亚").description("哈哈").foo(foo).build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(id=1, name=哈利路亚, description=哈哈, foo=Foo(fooName=FOO))
Y(targetId=1, placeName=哈利路亚, desc=哈哈, bar=Bar(barName=FOO))

类型转换的映射

创建需要映射的POJO

@Data
@Builder
public class X {
    private Long id;
    private String name;
    private String description;

    private LocalDateTime localDateTime;
    private Double price;
}
@Data  
@Builder  
public class Y {  
    private Long targetId;  
    private String placeName;  
    private String desc;  
  
    private String time;  
    private String price;  
}

编写映射接口

@Mapper  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(source = "id", target = "targetId")  
    @Mapping(source = "name", target = "placeName")  
    @Mapping(source = "description", target = "desc")  
    @Mapping(source = "localDateTime", target = "time", dateFormat = "yyyy-MM-dd HH:mm:ss")  
    @Mapping(source = "price", target = "price", numberFormat = "¥#.00")  
    Y source2Destination(X source);  
  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    private final DateTimeFormatter dateTimeFormatter_yyyy_MM_dd_HH_mm_ss_11333195168 = DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" );  
  
    @Override  
    public Y source2Destination(X source) {  
        if ( source == null ) {  
            return null;  
        }  
  
        Y.YBuilder y = Y.builder();  
  
        y.targetId( source.getId() );  
        y.placeName( source.getName() );  
        y.desc( source.getDescription() );  
        if ( source.getLocalDateTime() != null ) {  
            y.time( dateTimeFormatter_yyyy_MM_dd_HH_mm_ss_11333195168.format( source.getLocalDateTime() ) );  
        }  
        if ( source.getPrice() != null ) {  
            y.price( new DecimalFormat( "¥#.00" ).format( source.getPrice() ) );  
        }  
  
        return y.build();  
    }  
}

验证测试

public class Test {  
  
    public static void main(String[] args) {  
        Foo foo = Foo.builder().fooName("FOO").build();  
        X x = X.builder().id(1L).name("哈利路亚").description("哈哈")  
            .localDateTime(LocalDateTime.now())  
            .price(34531D)  
            .build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(id=1, name=哈利路亚, description=哈哈, localDateTime=2023-08-10T10:19:02.003, price=34531.0)
Y(targetId=1, placeName=哈利路亚, desc=哈哈, time=2023-08-10 10:19:02, price=¥34531.00)

前置映射和后置映射

使用 @BeforeMapping 进行前置映射,使用 @AfterMapping 进行后置映射

创建需要映射的POJO

@Data  
@Builder  
public class X {  
    private String firstName;  
    private String lastName;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String lastName;  
    private String fullName;  
}

编写映射接口

注意:**@Mapper** 注解同lombok的 **@Builder** 使用时会出现问题,可以按照如下代码中的配置解决
@Mapper(builder = @Builder(disableBuilder = true))  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    Y source2Destination(X x);  
  
    @BeforeMapping  
    default void before(X x) {  
        x.setFirstName(x.getFirstName().toUpperCase());  
        x.setLastName(x.getLastName().toUpperCase());  
    }  
  
    @AfterMapping  
    default void after(@MappingTarget Y y) {  
        y.setFullName(y.getFirstName() + " " + y.getLastName());  
    }  
  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        before( x );  
  
        if ( x == null ) {  
            return null;  
        }  
  
        String firstName = null;  
        String lastName = null;  
  
        firstName = x.getFirstName();  
        lastName = x.getLastName();  
  
        String fullName = null;  
  
        Y y = new Y( firstName, lastName, fullName );  
  
        after( y );  
  
        return y;  
    }  
}

验证测试

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().firstName("gg").lastName("bond").build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(firstName=gg, lastName=bond)
Y(firstName=GG, lastName=BOND, fullName=GG BOND)

表达式映射

创建需要映射的POJO

@Data  
@Builder  
public class X {  
    private String firstName;  
    private String lastName;  
}
@Data  
@Builder  
public class Y {  
    private String firstName;  
    private String lastName;  
    private String uuid;  
}

编写映射接口

@Mapper(builder = @Builder(disableBuilder = true))  
public interface XYMapper {  
  
    XYMapper INSTANCE = Mappers.getMapper(XYMapper.class);  
  
    @Mapping(target = "uuid", expression = "java(java.util.UUID.randomUUID().toString())")  
    Y source2Destination(X x);  
}

生成的接口实现

public class XYMapperImpl implements XYMapper {  
  
    @Override  
    public Y source2Destination(X x) {  
        if ( x == null ) {  
            return null;  
        }  
  
        String firstName = null;  
        String lastName = null;  
  
        firstName = x.getFirstName();  
        lastName = x.getLastName();  
  
        String uuid = java.util.UUID.randomUUID().toString();  
  
        Y y = new Y( firstName, lastName, uuid );  
  
        return y;  
    }  
}

验证测试

public class Test {  
  
    public static void main(String[] args) {  
        X x = X.builder().firstName("gg").lastName("bond").build();  
        System.out.println(x);  
        Y y = XYMapper.INSTANCE.source2Destination(x);  
        System.out.println(y);  
    }  
  
}
X(firstName=gg, lastName=bond)
Y(firstName=gg, lastName=bond, uuid=3ef6875a-85ce-4d8c-807d-2c0d4193cfd4)

接上文,从阿里云下载整个目录的文件,文件有25w,总量约90G,文件数目多且总体积大。
将这些文件转移到不同的文件夹中,然后针对单个文件夹进行查看、压缩等会变得方便。
下面的例子是将文件按照文件的文件名的前三位将文件进行划分,创建对应的文件夹,然后将文件移动到文件夹中。
如果你有其他划分规则,按规则来即可。


import os
import shutil


src_folder = '/data/source'

folder = "/data/dest"

count = 0
for filename in os.listdir(src_folder):
  prefix = filename[:3]

  if not os.path.exists(folder+prefix):
    os.makedirs(folder+prefix)

  dst = os.path.join(folder+prefix, filename)

  shutil.move(os.path.join(src_folder, filename), dst)
  count = count + 1
  print(count)