Content Table

自定义 Content Type Prober

在浏览器中点击 PDF 文件的链接:

  • 在 A 网站点击 a.pdf,浏览器自动下载 a.pdf
  • 在 B 网站点击 b.pdf,浏览器直接打开 b.pdf

被访问都是 PDF 文件,为啥在网站 A 和在网站 B 访问时,浏览器的行为不一样,是什么东西影响它在浏览器中的行为呢?答案就是浏览器会根据响应的 Content-Type 来决定下载还是打开它们 (当然 Content-Type 的值只是一个 hit,具体的操作还是要看浏览器的实现)。

文件的类型非常多,怎么获取文件的 Content-Type 是什么呢?Java 1.7 提供 java.nio.file.Files.probeContentType(Path path) 用于尝试获取文件的 Content-Type,但发现支持的文件类型不够全面,查看方法 probeContentType 的帮助文档:

This method uses the installed FileTypeDetector implementations to probe the given file to determine its content type. Each file type detector’s probeContentType is invoked, in turn, to probe the file type. If the file is recognized then the content type is returned. If the file is not recognized by any of the installed file type detectors then a system-default file type detector is invoked to guess the content type.

A given invocation of the Java virtual machine maintains a system-wide list of file type detectors. Installed file type detectors are loaded using the service-provider loading facility defined by the ServiceLoader class. Installed file type detectors are loaded using the system class loader. If the system class loader cannot be found then the extension class loader is used; If the extension class loader cannot be found then the bootstrap class loader is used. File type detectors are typically installed by placing them in a JAR file on the application class path or in the extension directory, the JAR file contains a provider-configuration file named java.nio.file.spi.FileTypeDetector in the resource directory META-INF/services, and the file lists one or more fully-qualified names of concrete subclass of FileTypeDetector that have a zero argument constructor. If the process of locating or instantiating the installed file type detectors fails then an unspecified error is thrown. The ordering that installed providers are located is implementation specific.

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 文件的写法需要注意