背景

前段时间在弄关于文档转图片然后进行处理的工作,其中由于图片内容会多次读取,所以将其持久化在本地。
但是由于图片是临时文件,所以需要在使用完之后删除。
最初的实现是使用try-with-resource,但是随着流程变得复杂,图片的生命周期变得不可控,所以需要一个更好的解决方案。

思路

  1. 利用Cleaner的特性,在文件被GC回收时,进行删除操作。需要注意不要在cleaner中应用this对象,否则会导致内存泄漏。
  2. 利用MDC保存当前线程的MDC信息,这样cleaner执行时,用于跟踪上下文的MDC可以保留,使得traceId等信息不会丢失。

实现

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.haizhi.metis.document.parse.common;

import java.io.File;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;


@Slf4j
@Getter
public class AutoCleanCacheFile extends File {
private static final Cleaner cleaner = Cleaner.create();
private final static String TMP_FILE_PREFIX = "temp_";
private static final String tmpdir = System.getProperty("java.io.tmpdir");
private static final long serialVersionUID = 1L;
private final String fileType;
public AutoCleanCacheFile(@NotNull String fileType) {
super(createTempFile(fileType));
this.fileType = fileType;
Path path = toPath();
Map<String, String> mdcMap= MDC.getCopyOfContextMap();
cleaner.register(this, () -> {
//备份MDC用于还原
Map<String, String> backup = MDC.getCopyOfContextMap();
MDC.setContextMap(mdcMap);
boolean delete = path.toFile().delete();
log.debug("delete temp File [{}]: {}", delete, path);
if (backup != null){
MDC.setContextMap(backup);
}
});
}
private static String createTempFile(@NotNull String fileType) {
try {
String path = Path.of(tmpdir, String.format("%s%s.%s", TMP_FILE_PREFIX, UUID.randomUUID(), fileType)).toString();
boolean newFile = new File(path).createNewFile();
log.debug("create temp File [{}]: {}", newFile, path);
return path;
} catch (IOException e) {
//出现未知异常,直接抛出
//理论上不应该进入这里
throw new IllegalStateException(e);
}
}

public static AutoCleanCacheFile build(@NotNull String fileType){
return new AutoCleanCacheFile(fileType);
}
}