# Optional

传统的写代码方式经常会遇到NullPointerException,这就需要我们在代码中经常判空。而判空的写法又会显得很累赘,这里就可以用到Optional来简化代码。

  • Optional是在java.util包下的一个用于代替null的一个工具类
  • Optional 是个容器:它可以保存类型T的值,或者仅仅保存null
  • Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象

Optional 类的引入很好的解决空指针异常的问题

# 实现可选参数的功能

javaCopy codepublic void myMethod(int a, Optional<Integer> b) {
    int value = b.orElse(10); // 使用默认值 10,如果提供了参数 b,则使用提供的值
    // 使用参数 a 和 value 进行处理逻辑
}

# 构建Optional对象

  • 创建一个空的 Optional 实例
Optional<String> emptyOptional = Optional.empty();
  • 创建一个 Optional 实例,当 t为null时,抛出NullPointerException异常
Optional<String> notNullOptional = Optional.of("aaa");
  • 创建一个 Optional 实例,但当 t为null时不会抛出异常,而是返回一个空的实例
Optional<String> notNullOptional = Optional.ofNullable("aaa");

# 相关方法使用

  • isPresent():持有非空值,返回true;否则false;
Optional optional = Optional.ofNullable(null);
Optional optional1 = Optional.of("");
Optional optional2 = Optional.empty();
System.out.println(optional.isPresent()); //fasle
System.out.println(optional1.isPresent()); //true
System.out.println(optional2.isPresent());  //false
  • ifPresent():如果 Optional 中有值,返回该值,否则什么也不做
Optional<String> optional = Optional.of("Hello optional");
System.out.println("optional get is :"+optional.get());
optional.ifPresent(System.out::println);
  • orElse:参数是一个值,如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数
//Optional 中有值
Optional optional = Optional.ofNullable("fdgshsgf");
System.out.println(optional.orElse("reiwgybv"));
  • orElseGet:功能与orElse一样,只不过orElseGet参数是一个对象
optional.orElseGet(() -> "Default Value")
  • orElseThrow:如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
String bb = optional.orElseThrow(() -> new Exception("抛出异常"));
  • map:为空返回Optional.empty,否则返回一个新的Optional,函数mapper在以value作为输入时的输出值可以多次使用map操作
Optional<String> username = Optional.ofNullable(getUserById(id))
		.map(user -> user.getUsername())
		.map(name -> name.replace('_', ' '));
			
System.out.println("Username is: " + username.orElse("Unknown"));
  • flatMap:map 方法参数中的函数 mapper 输出的是值,然后 map 方法会使用 Optional.ofNullable 将其包装为 Optional;而 flatMap 要求参数中的函数 mapper 输出的就是 Optional
Optional<String> username = Optional.ofNullable(getUserById(id))
		.flatMap(user -> Optional.of(user.getUsername()))
		.flatMap(name -> Optional.of(name.toLowerCase()));
		
System.out.println("Username is: " + username.orElse("Unknown"));
  • filter:如果有值并且满足条件,就返回该Optional,否则返回空Optional
Optional<User> result = Optional.ofNullable(user).filter(u -> u.getEmail().contains("@"));

# MapStruct

随着微服务和分布式应用程序迅速占领开发领域,数据完整性和安全性比以往任何时候都更加重要。在这些松散耦合的系统之间,安全的通信渠道和有限的数据传输是最重要的。大多数时候,终端用户或服务不需要访问模型中的全部数据,而只需要访问某些特定的部分。

数据传输对象(Data Transfer Objects, DTO)经常被用于这些应用中。DTO只是持有另一个对象中被请求的信息的对象。通常情况下,这些信息是有限的一部分。例如,在持久化层定义的实体和发往客户端的DTO之间经常会出现相互之间的转换。由于DTO是原始对象的反映,因此这些类之间的映射器在转换过程中扮演着关键角色。

这就是MapStruct解决的问题:手动创建bean映射器非常耗时。 但是该库可以自动生成Bean映射器类。

# 简单使用

  • 引入依赖,注意:当lombok和mapstruct一起用的时候,会导致mapstruct失效
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct</artifactId>
	<version>1.5.3.Final</version>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version> 1.18.28</version>
</dependency>

<!--MapStruct在编译时工作,并且会集成到像Maven和Gradle这样的构建工具上-->
<!--所以必须在添加插件maven-compiler-plugin-->
<plugins>
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-compiler-plugin</artifactId>
		<version>3.10.1</version>
		<configuration>
			<source>1.8</source>
			<target>1.8</target>
			<!--注意lombok和mapstruct的顺序-->
			<annotationProcessorPaths>
				<path>
					<groupId>org.projectlombok</groupId>
					<artifactId>lombok</artifactId>
					<version> 1.18.28</version>
				</path>
				<path>
					<groupId>org.mapstruct</groupId>
					<artifactId>mapstruct-processor</artifactId>
					<version>1.5.3.Final</version>
				</path>
			</annotationProcessorPaths>
		</configuration>
	</plugin>
</plugins>
  • 创建 DTO、VO
//DTO
@Data
public class StudentDto {
    private String userName;
    private String userId;
    private String address;
    private String school;
    private int age;
    private String email;
}

//VO
@Data
@Builder
public class StudentVo {
    private String userName;
    private String userId;
    private String address;
    private String school;
    private int age;
    private String emailAddress;
}
  • 创建mapstruct转换器
//componentModel = "spring" 交给spring管理
@Mapper(componentModel = "spring")
public interface MainMapper {
    StudentDto studentVo2Dto(StudentVo vo);
}
  • 编写测试用例
@SpringBootTest
class SpringbootMapstructApplicationTests {
    @Autowired
    private MainMapper mainMapper;

    @Test
    void testSimpleMap() {
        StudentVo studentVo = StudentVo.builder()
                .school("清华大学")
                .userId("ams")
                .userName("AI码师")
                .age(27)
                .address("合肥")
                .build();
        StudentDto studentDto = mainMapper.studentVo2Dto(studentVo);
        System.out.println(studentDto);
    }
}
  • 查看MainMapper生成的代码

查看MainMapper生成的代码

# 不同字段映射

@Mapper
public interface DoctorMapper {
    
	//Doctor中的specialty字段对应于DoctorDto类的 specialization
    @Mapping(source = "doctor.specialty", target = "specialization")
    DoctorDto toDto(Doctor doctor);
}

# 多个数据源类

有时,单个类不足以构建DTO,我们可能希望将多个类中的值聚合为一个DTO,供终端用户使用

@Data
public class Doctor {
    private int id;
    private String name;
	private String specialty;
}

@Data
public class Education {
    private String degreeName;
    private String institute;
    private Integer yearOfPassing;
}

@Data
public class DoctorDto {
    private int id;
    private String name;
    private String degree;
    private String specialization;
}

接下来,将 DoctorMapper 接口更新为如下代码:

@Mapper
public interface DoctorMapper {
    
	//如果Education和Doctor包含同名的字段,必须让映射器知道使用哪一个,否则它会抛出一个异常
	@Mapping(source = "doctor.specialty", target = "specialization")
    @Mapping(source = "education.degreeName", target = "degree")
    DoctorDto toDto(Doctor doctor, Education education);
}

# 子对象映射

多数情况下,POJO中不会只包含基本数据类型,其中往往会包含其它类。比如说,一个Doctor类中会有多个患者类:

public class Patient {
    private int id;
    private String name;
}

在Doctor中添加一个患者列表List:

public class Doctor {
    private int id;
    private String name;
    private String specialty;
    private List<Patient> patientList;
}

因为Patient需要转换,为其创建一个对应的DTO:

public class PatientDto {
    private int id;
    private String name;
}

最后,在 DoctorDto 中新增一个存储 PatientDto的列表:

public class DoctorDto {
    private int id;
    private String name;
    private String degree;
    private String specialization;
    private List<PatientDto> patientDtoList;
}

在修改 DoctorMapper之前,我们先创建一个支持 Patient 和 PatientDto 转换的映射器接口:

@Mapper
public interface PatientMapper {
    PatientDto toDto(Patient patient);
}

然后,我们再来修改 DoctorMapper 处理一下患者列表:

//因为我们要处理另一个需要映射的类,所以这里设置了@Mapper注解的uses标志
@Mapper(uses = {PatientMapper.class})
public interface DoctorMapper {

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

    @Mapping(source = "doctor.patientList", target = "patientDtoList")
    @Mapping(source = "doctor.specialty", target = "specialization")
    DoctorDto toDto(Doctor doctor);
}

# List集合映射

@Mapper(componentModel = "spring")
public interface MainMapper {
	
    @Mapping(source = "emailAddress", target = "email")
    StudentDto studentVo2Dto(StudentVo vo);
    List<StudentDto> studentListVo2Dto(List<StudentVo> vo);
}

# Set和Map映射

@Mapper
public interface DoctorMapper {

    Set<DoctorDto> setConvert(Set<Doctor> doctor);
    Map<String, DoctorDto> mapConvert(Map<String, Doctor> doctor);
}

# Map和JavaBean的映射

@Mapper
public interface DoctorMapper {

    Doctor toBean(Map<String,String> map);
}


//测试
HashMap<String, String> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("id","10");
stringObjectHashMap.put("name","lisi");
Doctor doctor1 = doctorMapper.toBean(stringObjectHashMap);
System.out.println(doctor1);

# 数据类型转换

  • 时间格式转换
public class PatientDto {
    private int id;
    private String name;
    private LocalDate dateOfBirth;
}

public class Patient {
    private int id;
    private String name;
    private String dateOfBirth;
}

//映射器
@Mapper
public interface PatientMapper {

    @Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
    Patient toModel(PatientDto patientDto);
}
  • 数字格式转换
 @Mapping(source = "price", target = "price", numberFormat = "$#.00")
  • 枚举格式转换
public enum PaymentType {
    CASH,
    CHEQUE,
    CARD_VISA,
    CARD_MASTER,
    CARD_CREDIT
}

public enum PaymentTypeView {
    CASH,
    CHEQUE,
    CARD
}

//两个enum之间的映射器接口
@Mapper
public interface PaymentTypeMapper {
    @ValueMappings({
            @ValueMapping(source = "CARD_VISA", target = "CARD"),
            @ValueMapping(source = "CARD_MASTER", target = "CARD"),
            @ValueMapping(source = "CARD_CREDIT", target = "CARD")
    })
    PaymentTypeView paymentTypeToPaymentTypeView(PaymentType paymentType);
}

# 添加默认值

@Mapping 注解有两个很实用的标志就是常量 constant 和默认值 defaultValue

@Mapper(uses = {PatientMapper.class}, componentModel = "spring")
public interface DoctorMapper {
	
    @Mapping(target = "id", constant = "-1")
    @Mapping(source = "doctor.patientList", target = "patientDtoList")
    @Mapping(source = "doctor.specialty", target = "specialization", defaultValue = "aa")
    DoctorDto toDto(Doctor doctor);
}

# 文件头类型校验

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.0.M3</version>
</dependency>
//文件上传拦截器
public class FileInterceptor implements HandlerInterceptor {
    Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        // 判断是否为文件上传请求
        if (req instanceof MultipartHttpServletRequest) {
            MultipartHttpServletRequest multipartReq = (MultipartHttpServletRequest) req;
            Map<String, MultipartFile> files = multipartReq.getFileMap();

            for (String formKey : files.keySet()) {
                MultipartFile file = multipartReq.getFile(formKey);
                //后缀名文件类型
                String filename = file.getOriginalFilename();
                //String suffixType = FileUtil.extName(filename);
                String suffixType = StringUtils.substringAfterLast(filename, ".");
                //文件头文件类型
                String headerType = FileTypeUtil.getType(file.getInputStream());
                //文件头与文件名后缀不匹配
                if (!StringUtils.equalsIgnoreCase(suffixType, headerType)) {
                    String info = String.format("文件头与文件名后缀不匹配。文件名:%s,文件头:%s。", filename, headerType);
                    log.error(info);

                    resp.setStatus(HttpServletResponse.SC_OK);
                    resp.getWriter().write(info);
                    resp.flushBuffer();
                    return false;
                }
            }
        }

        return true;
    }
}

# Resource接口

在日常程序开发中,处理外部资源是很繁琐的事情,我们可能需要处理URL资源、File资源资源、ClassPath相关资源、服务器相关资源(JBoss AS 5.x上的VFS资源)等等很多资源。因此处理这些资源需要使用不同的接口,这就增加了我们系统的复杂性;而且处理这些资源步骤都是类似的(打开资源、读取资源、关闭资源),因此如果能抽象出一个统一的接口来对这些底层资源进行统一访问,是不是很方便,而且使我们系统更加简洁,都是对不同的底层资源使用同一个接口进行访问。

Spring 提供一个Resource接口来统一这些底层资源一致的访问,而且提供了一些便利的接口,从而能提供我们的生产力。

# Resource接口

Spring的Resource接口代表底层外部资源,提供了对底层外部资源的一致性访问接口

public interface InputStreamSource {
	//每次调用都将返回一个新鲜的资源对应的java.io.InputStream字节流
	//调用者在使用完毕后必须关闭该资源
    InputStream getInputStream() throws IOException;
}

//Resource接口继承InputStreamSource接口,并提供一些便利方法
public interface Resource extends InputStreamSource {
	//返回当前Resource代表的底层资源是否存在,true表示存在
    boolean exists();
	//返回当前Resource代表的底层资源是否可读,true表示可读
    boolean isReadable();
	//返回当前Resource代表的底层资源是否已经打开
	//如果返回true,则只能被读取一次然后关闭以避免资源泄露
	//常见的Resource实现一般返回false
    boolean isOpen();
	//如果当前Resource代表的底层资源能由java.util.URL代表,则返回该URL
	//否则抛出IOException
    URL getURL() throws IOException;
	//如果当前Resource代表的底层资源能由java.util.URI代表,则返回该URI
	//否则抛出IOException
    URI getURI() throws IOException;
	//如果当前Resource代表的底层资源能由java.io.File代表,则返回该File
	//否则抛出IOException
    File getFile() throws IOException;
	//返回当前Resource代表的底层文件资源的长度,一般是值代表的文件资源的长度
    long contentLength() throws IOException;
	//返回当前Resource代表的底层资源的最后修改时间
    long lastModified() throws IOException;
	//用于创建相对于当前Resource代表的底层资源的资源,比如当前Resource代表文件资源
	//"d:/test/"则createRelative("test.txt")将返回文件资源"d:/test/test.txt"Resource资源
    Resource createRelative(String relativePath) throws IOException;
	//返回当前Resource代表的底层文件资源的文件路径
	//比如File资源"file://d:/test.txt"将返回"d:/test.txt"
	//而URL资源http://www.baidu.com将返回"",因为只返回文件路径
    String getFilename();
	//返回当前Resource代表的底层资源的描述符,通常就是资源的全路径
    String getDescription();
}

# 内置Resource实现

Resource接口提供了很多内置Resource实现:

  • ByteArrayResource:代表byte[]数组资源,对于getInputStream操作将返回一个ByteArrayInputStream
public void testByteArrayResource() {
    Resource resource = new ByteArrayResource("Hello World!".getBytes());
    if(resource.exists()) {
        dumpStream(resource);
    }
}

private void dumpStream(Resource resource) {
    InputStream is = null;
    try {
        //1.获取文件资源
        is = resource.getInputStream();
        //2.读取资源
        byte[] descBytes = new byte[is.available()]; 
        is.read(descBytes);
        System.out.println(new String(descBytes));
    } catch (IOException e) {
        e.printStackTrace();
    }
    finally {
        try {
            //3.关闭资源
            is.close();
        } catch (IOException e) {
        }
    }
}

注意

ByteArrayResource可多次读取数组资源,即isOpen()永远返回false

  • InputStreamResource:代表java.io.InputStream字节流,对于getInputStream操作将直接返回该字节流
public void testInputStreamResource() {
    ByteArrayInputStream bis = new ByteArrayInputStream("Hello World!".getBytes());
    Resource resource = new InputStreamResource(bis);
    if (resource.exists()) {
        dumpStream(resource);
    }
    System.out.println(resource.isOpen());
}

注意

InputStreamResource只能读取一次该字节流,即isOpen()永远返回true

  • FileSystemResource:代表java.io.File资源,对于getInputStream操作将返回底层文件的字节流
public void testFileResource() {
    File file = new File("d:/test.txt");
    Resource resource = new FileSystemResource(file);
    if(resource.exists()) {
        dumpStream(resource);
    }
    Assert.assertEquals(false, resource.isOpen());
}

注意

FileSystemResource可多次读取数组资源,即isOpen()永远返回false

  • UrlResource:代表URL资源,用于简化URL资源访问
//http:通过标准的http协议访问web资源,如new UrlResource("http://地址");
//ftp:通过ftp协议访问资源,如new UrlResource("ftp://地址");
//file:通过file协议访问本地文件系统资源,如new UrlResource("file:d:/test.txt");


public void testUrlResource() throws IOException {
    Resource resource = new UrlResource("file:d:/test.txt");
    if (resource.exists()) {
        dumpStream(resource);
    }
    System.out.println("path:" + resource.getURL().getPath());
    Assert.assertEquals(false, resource.isOpen());
 
    Resource resource2 = new UrlResource("http://www.baidu.com");
    if (resource2.exists()) {
        dumpStream(resource2);
    }
    System.out.println("path:" + resource2.getURL().getPath());
    Assert.assertEquals(false, resource2.isOpen());
}

注意

UrlResource可多次读取数组资源,即isOpen()永远返回false

  • ClassPathResource:代表classpath路径的资源,将使用ClassLoader进行加载资源
//ClassPathResource提供了三个构造器:

//使用默认的ClassLoader加载path类路径资源;
public ClassPathResource(String path)//使用指定的ClassLoader加载path类路径资源;
public ClassPathResource(String path, ClassLoader classLoader)//使用指定的类加载path类路径资源,将加载相对于当前类的路径的资源;
public ClassPathResource(String path, Class<?> clazz)/**
 * 使用默认的加载器加载资源,将加载当前ClassLoader类路径上相对于根路径的资源
 * @throws IOException
 */
@Test
public void testClasspathResourceByDefaultClassLoader() throws IOException {
	Resource resource = new ClassPathResource("test1.properties");
	if (resource.exists()) {
		dumpStream(resource);
	}
	System.out.println("path:" + resource.getFile().getAbsolutePath());
	Assert.assertEquals(false, resource.isOpen());
}

/**
 * 使用指定的ClassLoader进行加载资源,将加载指定的ClassLoader类路径上相对于根路径
 * 的资源
 * @throws IOException
 */
@Test
public void testClasspathResourceByClassLoader() throws IOException {
//ClassLoader loader = Thread.currentThread().getContextClassLoader();
//System.out.println(loader.getResource("").getPath());
	ClassLoader cl = this.getClass().getClassLoader();
	Resource resource = new ClassPathResource("test1.properties", cl);
	if (resource.exists()) {
		dumpStream(resource);
	}
	System.out.println("path:" + resource.getFile().getAbsolutePath());
	Assert.assertEquals(false, resource.isOpen());
}

/**
 * 使用指定的类进行加载资源,将尝试加载相对于当前类的路径的资源
 * @throws IOException
 */
@Test
public void testClasspathResourceByClass() throws IOException {
	Class clazz = this.getClass();
	Resource resource1 = new ClassPathResource("/test1.properties", clazz);
	if (resource1.exists()) {
		dumpStream(resource1);
	}
	System.out.println("path:" + resource1.getFile().getAbsolutePath());
	Assert.assertEquals(false, resource1.isOpen());

	Resource resource2 = new ClassPathResource("/test1.properties", this.getClass());
	if (resource2.exists()) {
		dumpStream(resource2);
	}
	System.out.println("path:" + resource2.getFile().getAbsolutePath());
	Assert.assertEquals(false, resource2.isOpen());

}

/**
 * 加载jar包里的资源,首先在当前类路径下找不到,最后才到Jar包里找,而且在第一个Jar
 * 包里找到的将被返回
 * @throws IOException
 */
@Test
public void testClasspathResourceFromJar() throws IOException {
	Resource resource = new ClassPathResource("overview.html");
	if (resource.exists()) {
		dumpStream(resource);
	}
	System.out.println("path:" + resource.getURL().getPath());
	Assert.assertEquals(false, resource.isOpen());
}

# ServerHttpRequest

ServerHttpRequest和HttpServletRequest的区别

HttpServletRequest 是tomcat提供的,ServerHttpRequest 是 spring框架提供的

ServerHttpRequest接口的实现类ServletServerHttpRequest,可通过方法getServletRequest()获取HttpServletRequest

if(exchange.getRequest() instanceof ServletServerHttpRequest) {
   ServletServerHttpRequest request = (ServletServerHttpRequest) exchange.getRequest();
   HttpServletRequest httpServletRequest = request.getServletRequest();
}  

ServerHttpRequest的主要适用场景:

  • 在使用springboot的websocket时,获取url中的参数
public class HandShake extends HttpSessionHandshakeInterceptor {
 
    /**
     * 握手前参数和权限设置与校验
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) {
        // 初始化Session信息
        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
        // 获取参数 必填校验
        String xxx = servletRequest.getServletRequest().getParameter(xxx);
	}
}
  • 在网关接口中获取相关request
@PostMapping("/captch")
public Mono<Result> getCaptch(ServerWebExchange exchange){
    ServerHttpRequest request = exchange.getRequest();
}
  • webflux使用ServerHttpRequest 获取多body体内容
@RequestMapping(value="/testBodys",method=RequestMethod.GET)
@ResponseBody
public Mono<ResponseEntity> testHttpRequest(ServerHttpRequest httpRequest){
   return Mono.justOrEmpty(new ResponseEntity(HttpStatus.OK));
}

# Jar包加密

# 采用classfinal-maven-plugin插件

直接配置一个插件就可以实现源码的安全性保护。并且可以对yml、properties配置文件以及lib目录下的maven依赖进行加密处理。若想指定机器启动,支持绑定机器,项目加密后只能在特定机器运行

# 功能特点

  • 加密后,方法体被清空,保留方法参数、注解等信息.主要兼容swagger文档注解扫描
  • 方法体被清空后,反编译只能看到方法名和注解,看不到方法体的具体内容
  • 加密后的项目需要设置javaagent来启动,启动过程中解密class,完全内存解密,不留下任何解密后的文件
  • 启动加密后的jar,生成xxx-encrypted.jar,这个就是加密后的jar文件,加密后不可直接执行
# 无密码启动方式
java -javaagent:xxx-encrypted.jar -jar xxx-encrypted.jar

# 有密码启动方式
java -javaagent:xxx-encrypted.jar='-pwd= 密码' -jar xxx-encrypted.jar
<!-- 在启动类的pom.xml文件中加如下插件-->
<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
		<plugin>
			<groupId>net.roseboy</groupId>
			<artifactId>classfinal-maven-plugin</artifactId>
			<version>1.2.1</version>
			<configuration>
			    <!-- #表示启动时不需要密码,只是一个启动密码,对于代码混淆来说没什么用 -->
				<password>#</password>
				<excludes>org.spring</excludes>
				<!-- 加密的包名,多个包用逗号分开 -->
				<packages>${groupId}</packages>
				<!-- 加密的配置文件,多个包用逗号分开 -->
				<cfgfiles>application.yml,application-dev.yml</cfgfiles>
				<!-- jar包lib下面要加密的jar依赖文件,多个包用逗号分开 -->
				<libjars>hutool-all.jar</libjars> 
				<!-- 指定机器启动,机器码 -->
				<code>xxxx</code> 
			</configuration>
			<executions>
				<execution>
					<phase>package</phase>
					<goals>
						<goal>classFinal</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

注意

该插件时要放到spring-boot-maven-plugin插件后面,否则不起作用

# 如何绑定机器启动

下载到classfinal-fatjar-1.2.1.jar依赖,在当前依赖下cmd执行java -jar classfinal-fatjar-1.2.1.jar -C命令,会自动生成一串机器码
绑定机器启动
将此生成好的机器码,放到maven插件中的code里面即可