mapstruct可以很好的解决DTO,VO,PO等等实体直接属性copy的问题,比BeanUtils.copyProperties()更好用。但是mapstruct与lombok会有版本冲突。

一。MapStruct怎么使用

1.MapStruct是用来做什么的?

现在有这么个场景,从数据库查询出来了一个user对象(包含id,用户名,密码,手机号,邮箱,角色这些字段)和一个对应的角色对象role(包含id,角色名,角色描述这些字段),现在在controller需要用到user对象的id,用户名,和角色对象的角色名三个属性。一种方式是直接把两个对象传递到controller层,但是这样会多出很多没用的属性。更通用的方式是需要用到的属性封装成一个类(DTO),通过传输这个类的实例来完成数据传输。
User.java

@AllArgsConstructor
@Data
public class User {private Long id;private String username;private String password;private String phoneNum;private String email;private Role role;
}

Role.java

@AllArgsConstructor
@Data
public class Role {private Long id;private String roleName;private String description;
}

UserRoleDto.java,这个类就是封装的类

@Data
public class UserRoleDto {/*** 用户id*/private Long userId;/*** 用户名*/private String name;/*** 角色名*/private String roleName;
}

测试类,模拟将user对象转换成UserRoleDto对象

public class MainTest {User user = null;/*** 模拟从数据库中查出user对象*/@Beforepublic void before() {Role role  = new Role(2L, "administrator", "超级管理员");user  = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);}/*** 模拟把user对象转换成UserRoleDto对象*/@Testpublic void test1() {UserRoleDto userRoleDto = new UserRoleDto();userRoleDto.setUserId(user.getId());userRoleDto.setName(user.getUsername());userRoleDto.setRoleName(user.getRole().getRoleName());System.out.println(userRoleDto);}
}

从上面代码可以看出,通过getter、setter的方式把一个对象属性值复制到另一个对象中去还是很麻烦的,尤其是当属性过多的时候。而MapStruct就是用于解决这种问题的。

2.使用MapStruct解决上述问题

这里我们沿用User.java、Role.java、UserRoleDto.java。
新建一个UserRoleMapper.java,这个来用来定义User.java、Role.java和UserRoleDto.java之间属性对应规则:

POM.xml引入依赖

<!-- bean属性转换工具 --><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-jdk8</artifactId><version>1.3.0.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.3.0.Final</version></dependency>

UserRoleMapper.java

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;/*** @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则*          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制*/
@Mapper
public interface UserRoleMapper {/*** 获取该类自动生成的实现类的实例* 接口中的属性都是 public static final 的 方法都是public abstract的*/UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);/*** 这个方法就是用于实现对象属性复制的方法** @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性** @param user 这个参数就是源对象,也就是需要被复制的对象* @return 返回的是目标对象,就是最终的结果对象*/@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "username", target = "name"),@Mapping(source = "role.roleName", target = "roleName")})UserRoleDto toUserRoleDto(User user);}

在测试类中测试:

public class MainTest {User user = null;/*** 模拟从数据库中查出user对象*/@Beforepublic void before() {Role role  = new Role(2L, "administrator", "超级管理员");user  = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);}/*** 模拟通过MapStruct把user对象转换成UserRoleDto对象*/@Testpublic void test2() {UserRoleDto userRoleDto = UserRoleMapper.INSTANCES.toUserRoleDto(user);System.out.println(userRoleDto);}
}

通过上面的例子可以看出,使用MapStruct方便许多。

3.添加默认方法

添加默认方法是为了这个类(接口)不只是为了做数据转换用的,也可以做一些其他的事。

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;/*** @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则*          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制*/
@Mapper
public interface UserRoleMapper {/*** 获取该类自动生成的实现类的实例* 接口中的属性都是 public static final 的 方法都是public abstract的*/UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);/*** 这个方法就是用于实现对象属性复制的方法** @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性** @param user 这个参数就是源对象,也就是需要被复制的对象* @return 返回的是目标对象,就是最终的结果对象*/@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "username", target = "name"),@Mapping(source = "role.roleName", target = "roleName")})UserRoleDto toUserRoleDto(User user);/*** 提供默认方法,方法自己定义,这个方法是我随便写的,不是要按照这个格式来的* @return*/default UserRoleDto defaultConvert() {UserRoleDto userRoleDto = new UserRoleDto();userRoleDto.setUserId(0L);userRoleDto.setName("None");userRoleDto.setRoleName("None");return userRoleDto;}}

测试代码:

@Test
public void test3() {UserRoleMapper userRoleMapperInstances = UserRoleMapper.INSTANCES;UserRoleDto userRoleDto = userRoleMapperInstances.defaultConvert();System.out.println(userRoleDto);
}

4. 可以使用abstract class来代替接口

mapper可以用接口来实现,也可以完全由抽象来完全代替

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;/*** @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则*          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制*/
@Mapper
public abstract class UserRoleMapper {/*** 获取该类自动生成的实现类的实例* 接口中的属性都是 public static final 的 方法都是public abstract的*/public static final UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);/*** 这个方法就是用于实现对象属性复制的方法** @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性** @param user 这个参数就是源对象,也就是需要被复制的对象* @return 返回的是目标对象,就是最终的结果对象*/@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "username", target = "name"),@Mapping(source = "role.roleName", target = "roleName")})public abstract UserRoleDto toUserRoleDto(User user);/*** 提供默认方法,方法自己定义,这个方法是我随便写的,不是要按照这个格式来的* @return*/UserRoleDto defaultConvert() {UserRoleDto userRoleDto = new UserRoleDto();userRoleDto.setUserId(0L);userRoleDto.setName("None");userRoleDto.setRoleName("None");return userRoleDto;}}

5.可以使用多个参数

可以绑定多个对象的属性值到目标对象中:

package com.mapstruct.demo;import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;/*** @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则*          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制*/
@Mapper
public interface UserRoleMapper {/*** 获取该类自动生成的实现类的实例* 接口中的属性都是 public static final 的 方法都是public abstract的*/UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);/*** 这个方法就是用于实现对象属性复制的方法** @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性** @param user 这个参数就是源对象,也就是需要被复制的对象* @return 返回的是目标对象,就是最终的结果对象*/@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "username", target = "name"),@Mapping(source = "role.roleName", target = "roleName")})UserRoleDto toUserRoleDto(User user);/*** 多个参数中的值绑定 * @param user 源1* @param role 源2* @return 从源1、2中提取出的结果*/@Mappings({@Mapping(source = "user.id", target = "userId"), // 把user中的id绑定到目标对象的userId属性中@Mapping(source = "user.username", target = "name"), // 把user中的username绑定到目标对象的name属性中@Mapping(source = "role.roleName", target = "roleName") // 把role对象的roleName属性值绑定到目标对象的roleName中})UserRoleDto toUserRoleDto(User user, Role role);

对比两个方法~

5.直接使用参数作为属性值

package com.mapstruct.demo;import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;/*** @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则*          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制*/
@Mapper
public interface UserRoleMapper {/*** 获取该类自动生成的实现类的实例* 接口中的属性都是 public static final 的 方法都是public abstract的*/UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);/*** 直接使用参数作为值* @param user* @param myRoleName* @return*/@Mappings({@Mapping(source = "user.id", target = "userId"), // 把user中的id绑定到目标对象的userId属性中@Mapping(source = "user.username", target = "name"), // 把user中的username绑定到目标对象的name属性中@Mapping(source = "myRoleName", target = "roleName") // 把role对象的roleName属性值绑定到目标对象的roleName中})UserRoleDto useParameter(User user, String myRoleName);}

测试类:

public class Test1 {Role role = null;User user = null;@Beforepublic void before() {role = new Role(2L, "administrator", "超级管理员");user = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);}@Testpublic void test1() {UserRoleMapper instances = UserRoleMapper.INSTANCES;UserRoleDto userRoleDto = instances.useParameter(user, "myUserRole");System.out.println(userRoleDto);}
}

6.更新对象属性

在之前的例子中UserRoleDto useParameter(User user, String myRoleName);都是通过类似上面的方法来生成一个对象。而MapStruct提供了另外一种方式来更新一个对象中的属性。@MappingTarget

public interface UserRoleMapper1 {UserRoleMapper1 INSTANCES = Mappers.getMapper(UserRoleMapper1.class);@Mappings({@Mapping(source = "userId", target = "id"),@Mapping(source = "name", target = "username"),@Mapping(source = "roleName", target = "role.roleName")})void updateDto(UserRoleDto userRoleDto, @MappingTarget User user);@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "username", target = "name"),@Mapping(source = "role.roleName", target = "roleName")})void update(User user, @MappingTarget UserRoleDto userRoleDto);}

通过@MappingTarget来指定目标类是谁(谁的属性需要被更新)。@Mapping还是用来定义属性对应规则。
以此为例说明:

@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "username", target = "name"),@Mapping(source = "role.roleName", target = "roleName")})void update(User user, @MappingTarget UserRoleDto userRoleDto);

@MappingTarget标注的类UserRoleDto 为目标类,user类为源类,调用此方法,会把源类中的属性更新到目标类中。更新规则还是由@Mapping指定。

7.没有getter/setter也能赋值

对于没有getter/setter的属性也能实现赋值操作

public class Customer {private Long id;private String name;//getters and setter omitted for brevity
}public class CustomerDto {public Long id;public String customerName;
}@Mapper
public interface CustomerMapper {CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );@Mapping(source = "customerName", target = "name")Customer toCustomer(CustomerDto customerDto);@InheritInverseConfigurationCustomerDto fromCustomer(Customer customer);
}

@Mapping(source = “customerName”, target = “name”)不是用来指定属性映射的,如果两个对象的属性名相同是可以省略@Mapping的
MapStruct生成的实现类:

@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2019-02-14T15:41:21+0800",comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
public class CustomerMapperImpl implements CustomerMapper {@Overridepublic Customer toCustomer(CustomerDto customerDto) {if ( customerDto == null ) {return null;}Customer customer = new Customer();customer.setName( customerDto.customerName );customer.setId( customerDto.id );return customer;}@Overridepublic CustomerDto toCustomerDto(Customer customer) {if ( customer == null ) {return null;}CustomerDto customerDto = new CustomerDto();customerDto.customerName = customer.getName();customerDto.id = customer.getId();return customerDto;}
}

@InheritInverseConfiguration在这里的作用就是实现customerDto.customerName = customer.getName();功能的。如果没有这个注解,toCustomerDto这个方法则不会有customerName 和name两个属性的对应关系的。
8.使用Spring依赖注入

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {private Long id;private String name;
}@Data
public class CustomerDto {private Long id;private String customerName;
}// 这里主要是这个componentModel 属性,它的值就是当前要使用的依赖注入的环境
@Mapper(componentModel = "spring")
public interface CustomerMapper {@Mapping(source = "name", target = "customerName")CustomerDto toCustomerDto(Customer customer);
}

@Mapper(componentModel = “spring”),表示把当前Mapper类纳入spring容器。可以在其它类中直接注入了:

@SpringBootApplication
@RestController
public class DemoMapstructApplication {// 注入Mapper@Autowiredprivate CustomerMapper mapper;public static void main(String[] args) {SpringApplication.run(DemoMapstructApplication.class, args);}@GetMapping("/test")public String test() {Customer customer = new Customer(1L, "zhangsan");CustomerDto customerDto = mapper.toCustomerDto(customer);return customerDto.toString();}}

看一下由mapstruct自动生成的类文件,会发现标记了@Component注解。

@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2019-02-14T15:54:17+0800",comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
@Component
public class CustomerMapperImpl implements CustomerMapper {@Overridepublic CustomerDto toCustomerDto(Customer customer) {if ( customer == null ) {return null;}CustomerDto customerDto = new CustomerDto();customerDto.setCustomerName( customer.getName() );customerDto.setId( customer.getId() );return customerDto;}
}

9.自定义类型转换

有时候,在对象转换的时候可能会出现这样一个问题,就是源对象中的类型是Boolean类型,而目标对象类型是String类型,这种情况可以通过@Mapper的uses属性来实现:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {private Long id;private String name;private Boolean isDisable;
}@Data
public class CustomerDto {private Long id;private String customerName;private String disable;
}

定义转换规则的类:

public class BooleanStrFormat {public String toStr(Boolean isDisable) {if (isDisable) {return "Y";} else {return "N";}}public Boolean toBoolean(String str) {if (str.equals("Y")) {return true;} else {return false;}}
}

定义Mapper,@Mapper( uses = { BooleanStrFormat.class}),注意,这里的users属性用于引用之前定义的转换规则的类:

@Mapper( uses = { BooleanStrFormat.class})
public interface CustomerMapper {CustomerMapper INSTANCES = Mappers.getMapper(CustomerMapper.class);@Mappings({@Mapping(source = "name", target = "customerName"),@Mapping(source = "isDisable", target = "disable")})CustomerDto toCustomerDto(Customer customer);
}

这样子,Customer类中的isDisable属性的true就会转变成CustomerDto中的disable属性的yes。
MapStruct自动生成的类中的代码:

@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2019-02-14T16:49:18+0800",comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
public class CustomerMapperImpl implements CustomerMapper {// 引用 uses 中指定的类private final BooleanStrFormat booleanStrFormat = new BooleanStrFormat();@Overridepublic CustomerDto toCustomerDto(Customer customer) {if ( customer == null ) {return null;}CustomerDto customerDto = new CustomerDto();// 转换方式的使用customerDto.setDisable( booleanStrFormat.toStr( customer.getIsDisable() ) );customerDto.setCustomerName( customer.getName() );customerDto.setId( customer.getId() );return customerDto;}
}

要注意的是,如果使用了例如像spring这样的环境,Mapper引入uses类实例的方式将是自动注入,那么这个类也应该纳入Spring容器:
CustomerMapper.java指定使用spring

@Mapper(componentModel = "spring", uses = { BooleanStrFormat.class})
public interface CustomerMapper {CustomerMapper INSTANCES = Mappers.getMapper(CustomerMapper.class);@Mappings({@Mapping(source = "name", target = "customerName"),@Mapping(source = "isDisable", target = "disable")})CustomerDto toCustomerDto(Customer customer);
}

转换类要加入Spring容器:

@Component
public class BooleanStrFormat {public String toStr(Boolean isDisable) {if (isDisable) {return "Y";} else {return "N";}}public Boolean toBoolean(String str) {if (str.equals("Y")) {return true;} else {return false;}}
}

MapStruct自动生成的类:

@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2019-02-14T16:55:35+0800",comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
@Component
public class CustomerMapperImpl implements CustomerMapper {// 使用自动注入的方式引入@Autowiredprivate BooleanStrFormat booleanStrFormat;@Overridepublic CustomerDto toCustomerDto(Customer customer) {if ( customer == null ) {return null;}CustomerDto customerDto = new CustomerDto();customerDto.setDisable( booleanStrFormat.toStr( customer.getIsDisable() ) );customerDto.setCustomerName( customer.getName() );customerDto.setId( customer.getId() );return customerDto;}
}

二、Mapstruct的使用会有哪些问题

从上面我们可以看到mapstruct在使用时主要依赖自动生成的实现类,如果生成不了实现类就会导致报错:java.lang.ClassNotFoundException

一般在pom.xml文件中引入jar依赖:

<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-jdk8</artifactId>
</dependency>
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version>
</dependency>

在编译的时候就会自动给我们生成好实现类。

但是在跟某些插件一起使用的时候,有的插件会影响到mapstruct生成实现类。比如:lombok.

本人在项目生产环境就发生过mapstruct报错的问题:

当时mapstruct的版本是:

<!-- bean属性转换工具 --><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-jdk8</artifactId><version>1.3.0.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.3.0.Final</version></dependency>

lombok的版本是:

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version></dependency>

pom.xml的Builder插件是

<plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.3.2</version><configuration><source>1.8</source><target>1.8</target><annotationProcessors><annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor></annotationProcessors></configuration></plugin></plugins>

经过各种尝试把这个插件改为:

<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><!-- Lombok 在编译时会通过这个插件生成代码 --><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version></path><!-- MapStruct 在编译时会通过这个插件生成代码 --><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.3.0.Final</version></path></annotationProcessorPaths><!--<annotationProcessors>--><!--<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>--><!--</annotationProcessors>--></configuration></plugin></plugins>

就解决了lombok影响 mapstruct编译时生成实现类的问题了。

mapstruct 使用与问题解决相关推荐

  1. undefined reference to 'pthread_create'问题解决(转)

    undefined reference to 'pthread_create' undefined reference to 'pthread_join' 问题原因:     pthread 库不是 ...

  2. mysql 4.0.27 下载,CentOS编译Mysql 4.0.27数据库问题解决

    CentOS编译Mysql 4.0.27数据库问题解决以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 现在好多人都用起了 ...

  3. oracle实例没有连到监听上6,oracle LISTENER未监听到oracle实例问题解决

    oracle LISTENER未监听到oracle实例问题解决 LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS = (PROTOCOL = ...

  4. linux+bin+写入引导区,CentOS 6.4 U盘启动盘制作、安装及遇到的问题解决

    用UltraISO Premium Edition  9.3 制作的CentOS 6.4 U盘安装盘, 安装时提示Press the key to begin the installation pro ...

  5. Oracle不能在本地计算机启动,Windows 不能在本地计算机启动 OracleDBConsoleorcl的问题解决方法...

    解决步骤如下: 1.开始->运行cmd 2.执行 emctl start dbconsole 输入:C:\Documents and Settings\xcl>emctl start db ...

  6. 【python】 OSError:sift not found 问题解决

    [python] OSError: XXXX.sift not found 问题解决 在python环境下通过sift描述子对图像进行特征匹配时出现错误(心累):mpire.sift not foun ...

  7. VS 打开No EditorOptionDefinition export found for the given option name问题解决

    打开vs项目文件时,无法加载解决方案,报错如下: 解决方法: 1.关闭VS 2.找到ComponentModelCache所在文件夹%LocalAppData%\Microsoft\VisualStu ...

  8. C语言 #ifndef 引起的redefinition of xxx 问题解决

    问题如下 多个.c和.h文件 其中cloth.h分布被hat.h和paths.h包含,编译时出现如下问题: error: redefinition of struct _Cloth 我的cloth.h ...

  9. 029 浏览器不能访问虚拟机的问题解决

    1.在搭建分布式时 ssh一直不能进行scp,后来发现一个问题. windows中的hosts配置了三台虚拟机的映射,但是在虚拟机中的hosts没有配置. 做法是在每台虚拟机上都配置三台虚拟机的映射. ...

最新文章

  1. 通过脚本案例学习shell(五) 通过创建DNS脚本一步一步教你将一个普通脚本规范到一个生产环境脚本...
  2. 一起谈.NET技术,ASP.NET MVC Membership 权限 漫谈
  3. TechEd 2007 HOL分享
  4. AI电话机器人源码买断 后私有云部署如何上线?
  5. 相对客观的权重计算方法——熵权法
  6. PHP中smart原则,制定目标时的SMART原则不包括什么
  7. origin 快捷键
  8. rl滤波器原理_滤波器的基础知识
  9. android中adb是什么意思,adb是什么意思?安卓系统常用adb命令怎么用?
  10. scratch3.0教程 水果忍者游戏
  11. 基于C++的深度学习模型部署
  12. 增长黑客:如何低成本实现爆发式增长
  13. Oracle 日期时间查询
  14. 推荐:武汉java培训哪家教育机构比较好
  15. 互联网协会与IDGVC发布Web2.0 100(附名单)
  16. Oracle VBox 克隆虚拟机
  17. 5-3 列车调度 (25分)
  18. 国家标准修订参与情况
  19. 画动漫第一步先画好人体?基础应该这么画~
  20. 魅族手机如何刷flyme国际版(跳过检查固件损坏这一步)

热门文章

  1. Jenkins - 默认用户名和密码及启动方法
  2. python3基础教程雪峰_[雪峰磁针石博客]python3快速入门教程2数据结构1变量与赋值...
  3. 冈萨雷斯《数字图像处理》学习笔记(九)形态学图像处理
  4. 如何用Graphpad Prism 8作折线图
  5. 运维初学者必备之SRS音视频服务器搭建
  6. 关于汽车遥控器芯片EV1527的研究
  7. 汽车钥匙芯片工作原理 浅谈汽车钥匙芯片作用及分类
  8. Adobe Audition 入门教程
  9. gitblit本地版本管理库-localhost访问
  10. 【Android】SerialPortFinder学习笔记,显示串口列表