Content Table

Java Classpath 加载 jar 的顺序

以在 Test.java 中加载 app-1.jar 和 app-2.jar 中的类 com.xtuer.Aloha 为例演示 classpath 中 jar 被加载的顺序。

app-1.jar 和 app-2.jar

创建项目,里面只有 1 个类 com.xtuer.Aloha,分别打包成 2 个版本的 jar 包:

  • app-1.jar: 返回 Aloha-1
  • app-2.jar: 返回 Aloha-2
1
2
3
4
5
6
7
8
9
package com.xtuer;

public class Aloha {
@Override
public String toString() {
return "Aloha-1"; // app-1.jar 使用
// return "Aloha-2"; // app-2.jar 使用
}
}

项目的目录结构 (此 gradle 管理的 Java 项目结构仅作为参考):

1
2
3
4
5
6
7
8
app
├── build.gradle
└── src
└── main
└── java
└── com
└── xtuer
└── Aloha.java

Spring Boot 使用 loader.path 加载其他 jar

Classpath

可以使用 classpath 指定类加载的路径,但 classpath 的生效是有条件的:

命令 classpath 生效 说明
java -cp .;lib/x.jar Test 运行 class
java -cp lib/x.jar -jar app.jar 运行 jar

Loader.path

Spring Boot 程序大多是打成 jar 包,使用 java -jar boot.jar 的方式启动 (此时 -cp 无效),可以使用 loader.path 指定类加载路径加载其他 jar,但 loader.path 生效是有条件的:

命令 MANIFEST.MF 的 Main-Class loader.path 生效 打包配置
java -Dloader.path=./lib -jar app.jar JarLauncher 默认配置
java -Dloader.path=./lib -jar app.jar PropertiesLauncher 额外配置

loader.path 实现了 classpath 的功能。

配置 Main-Class

为了使用 loader.path,需要把 jar 包的 Main-Class 配置为 PropertiesLauncher,在 build.gradle 中如下配置,可参考 Using the PropertiesLauncher:

1
2
3
4
5
bootJar {
manifest {
attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher'
}
}

LaunchedURLClassLoader

无论启动类是 JarLauncher 或者 PropertiesLauncher,loader.path 引入的 jar 和 Spring Boot 中 lib/*.jar 都是使用类加载器 org.springframework.boot.loader.LaunchedURLClassLoader 进行加载,也即是说他们使用的是同一个类加载器。

注意到同一个程序,打包成不同类型时,PropertiesLauncher (20s) 比 JarLauncher (8s) 启动慢很多。

Spring Boot Jasypt

使用 Jasypt 加密 Spring Boot 配置中的项,例如数据库密码。详细介绍可阅读 Springboot 配置文件、隐私数据脱敏的最佳实践Spring Boot 配置文件密码加密两种方案

本文主要介绍具体使用部分,注意 jasypt-spring-boot-starter 2.0 和 3.0 的区别。3.0 前后虽然都是使用 jasypt-1.9.3.jar,但是生成密文命令的参数有点区别:

版本 algorithm 默认值 iv-generator 默认值 加密命令
2.1.2 PBEWithMD5AndDES org.jasypt.iv.NoIvGenerator java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input=root password=xtuer algorithm=PBEWithMD5AndDES
3.0.3 PBEWITHHMACSHA512ANDAES_256 org.jasypt.iv.RandomIvGenerator java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input=root password=xtuer algorithm=PBEWITHHMACSHA512ANDAES_256 ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator

提示:

  • input 为要加密的内容,password 为加密的密钥
  • jasypt-1.9.3.jar 可以从 maven 或者 gradle 本地仓库中找到

Spring 与多线程

直接创建线程

1
2
3
4
5
6
7
8
9
10
11
12
public void thread1() {
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println("thread-1: " + i);
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}

Go 使用 Yaml

使用 kubernetes-sigs/yaml 来把对象序列化为 yaml 字符串,把 yaml 字符串反序列化为 go 对象。

简单对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main
import (
"fmt"
"sigs.k8s.io/yaml"
)

type Student struct {
Id int `json:"id"` // 可以是 `yaml:"id"`,但大多数第三方 struct 使用 json,所以这里就不使用 yaml 了
Name string `json:"name`
}

func main() {
// [1] 对象序列化为 yaml 字符串
student1 := Student{1, "Alice"}
yamlBytes, _ := yaml.Marshal(student1)
yamlString := string(yamlBytes)
fmt.Println(yamlString)

// [2] yaml 字符串反序列化为对象
student2 := Student{}
err := yaml.Unmarshal([]byte(yamlString), &student2)

// yaml 有错误时报错
if (err != nil) {
fmt.Println(err)
}

fmt.Println(student2)
}

下载 yaml:

1
2
go mod init
go mod download

编译执行:

1
go run test.go

运行输出:

1
2
3
4
id: 1
name: Alice

{1 Alice}

Axure 创建弹窗

Axure 创建弹窗,主要涉及以下几个方面:

  • 弹窗内容的元素放到动态面板里,这样所有元素都是动态面板的子元素,通过隐藏和显示动态面板来控制弹窗及其内容的可见性
  • 弹窗在页面中是可见的,页面加载的时候隐藏弹窗,需要的时候才显示弹窗。这样编辑的时候可以看到弹窗的内容,否则就是一个黄色块,不方便编辑与查看
  • 弹窗在所属页面的放置位置,不要挡住其他内容,放到页面的旁边即可
  • 弹窗的动态面板居中显示 (Pin to Browser)
  • 如果是需要重复使用的弹窗,可以创建为模板 (Master),或者根据分模块的思想,也可以把弹窗直接作为 Master,然后在页面中引用

下面我们实现一个如图的弹窗功能:

Java 操作 Yaml

Java 里可以使用类 Yaml 序列化和反序列化 Bean,有 2 个反序列化 Yaml 文件为 Java Bean 的方式:

  • 在 Yaml 的构造函数中指定 Bean 的类型,然后调用方法 load() 生成 Bean:

    1
    2
    Yaml yaml = new Yaml(new Constructor(User.class));
    User user = yaml.load(inputStream);
  • 使用 Yaml 的默认构造函数,然后调用方法 loadAs() 生成 Bean:

    1
    2
    Yaml yaml = new Yaml();
    User user = yaml.loadAs(inputStream, User.class);
  • 需要注意的是,下面的代码报错:

    1
    2
    Yaml yaml = new Yaml();
    User user = yaml.load(inputStream); // Error: java.util.LinkedHashMap cannot be cast to yaml.User

下面就以类 User 以及类把 User 作为属性时的多种情况为例进行演示,其中:

  • Yaml 反序列化为 User 的代码是一样的
  • Yaml 文件的写法需要注意

快捷键复制粘贴组件

访问剪贴板

使用 navigator.clipboard 对象访问系统的剪贴板:

  • readText: 读取剪贴板的内容
  • writeText: 设置剪贴板的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Clipboard</title>
</head>

<body>
<button id="button-copy">Copy</button>
<button id="button-paste">Paste</button>

<script>
// 设置系统剪贴板的内容
document.querySelector('#button-copy').addEventListener('click', () => {
navigator.clipboard.writeText('Hello');
});

// 读取系统剪贴板的内容
document.querySelector('#button-paste').addEventListener('click', () => {
navigator.clipboard.readText().then(text => {
console.log(text);
});
});
</script>
</body>
</html>

提示: IE 不支持 Navigator.clipboard,但 Edge 支持。