Content Table

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 支持。

Vue :key

我们都知道使用 v-for 渲染数组时都要给每个元素绑定一个 key,官方文档也说的很清 (挠) 楚 (头):

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

相同父元素的子元素必须有独特的 key,重复的 key 会造成渲染错误。

需要记住一点: 当用来创建 DOM 元素的数据变化后,就会更新 DOM (更新了才能在界面上看到),为了提高效率,vue 使用了 key 来判断是复用已有的 DOM 还是创建新的 DOM (在同一个 parent DOM element 下进行比较):

  • 如果能找到相同 key 的 DOM 元素,则更新它的内容
  • 如果找不到相同 key 的 DOM 元素,则删除旧的 DOM 元素,新创建一个

上面的内容,懂的人一看就懂,不懂的人仍然是一头雾水。下面我们以一个很简单的修改用户名的例子演示一下应该就能明白什么时候复用,什么时候创建了。