# 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("@"));
# SPI 机制
SPI 全称为 Service Provider Interface,是一种服务发现机制
# SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类
# 这样可以在运行时,动态为接口替换实现类
# 示例代码
// 定义一个接口,名称为 Robot
public interface Robot {
void sayHello();
}
// 定义两个实现类,分别为 OptimusPrime 和 Bumblebee
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
# Java SPI 示例
在 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:
org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee
编写代码进行测试:
public class JavaSPITest {
@Test
public void sayHello() throws Exception {
// 使用 SPI 来获取驱动的实现类
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
// 1. forEach 模式
serviceLoader.forEach(Robot::sayHello);
// 2. 迭代器模式
Iterator<Robot> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
Robot robot = iterator.next();
robot.sayHello();
}
// 输出
// Java SPI
// Hello, I am Optimus Prime.
// Hello, I am Bumblebee.
}
}
# Spring SPI 机制
在资源文件目录,创建一个固定的文件 META-INF/spring.factories
#key是接口的全限定名,value是接口的实现类
org.apache.spi.Robot = org.apache.spi.OptimusPrime,org.apache.spi.Bumblebee
运行代码:
// 调用 SpringFactoriesLoader.loadFactories 方法加载 Robot 接口所有实现类的实例
List<Robot> myTestServices = SpringFactoriesLoader.loadFactories(
Robot.class,
Thread.currentThread().getContextClassLoader()
);
for (Robot testService : myTestServices) {
testService.sayHello();
}
注意
和 Java SPI 一样,Spring SPI 也无法获取某个固定的实现,只能按顺序获取所有实现
# 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生成的代码
# 不同字段映射
@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里面即可