Spring 相关文档版本问题
SpringBoot
当前版本:https://docs.spring.io/spring-boot/docs/current/reference/html/index.html
需要关注的是上面链接的 current,只要替换这个为指定版本就可以查看了,但是这里有一个问题:并不是所有版本都会有文档发布
所以:需要在文档的历史存档里面去找你所需要的版本是否存在,或则找一个比较接近的版本替换。
文档库:https://docs.spring.io/spring-boot/docs/
比如你想要 2.4.15 版本的文档,你会发现在上面的文档库中并没有发布过这个版本的文档,退而求其次,找到 2.4.13 版本的,那么文档链接就是:https://docs.spring.io/spring-boot/docs/2.4.13/reference/html/index.html
bootJar 部署到 docker 中,启动传参数与配置文件
背景
在 boot 中,有一部分参数是只能写在 application.yml 中,写在 application-xx.yml
中,由于使用时机问题,有些配置属性就不生效了(比如:spring.profiles.active=prod)
那么这一类首先想到解决方法是:在启动 jar 包的时候使用如下的形式传参
nohup java -jar ${APP_JAR} --spring.profiles.active=${ACTIVE} --server.servlet.session.store-dir=${RESOURCES}/session-store-dir --logging.file.path=${RESOURCES}/logs > /dev/null 2>&1 &
可以看到,传参变得很复杂,是个不小的挑战,
解决方案
Spring boot 版本:2.4.1
那么我可以基于 boot 的外部化配置文件 中的 外部应用程序属性,将需要覆盖程序内的配置文件属性放到与 bootJar 同级的 config
目录下,如下所示:
|- bootJar
|- config
|- application.yml
|- application-prod.yml
我们将所有的配置属性都可以写到这个 application.yml 文件中,此时外部文件的属性优先级最高,会覆盖掉程序内部的配置文件属性
日志变 JSON 格式输出
背景
放在 k8s 下,控制台输出的日志将被抽走,原来多行日志(特别是堆栈错误信息)会被解析成多行,而不是一行
解决方案
Spring boot 版本:2.4.1
解决思路如下:
- 利用 logback 中的
appender.encoder
格式化控制台输出格式 - 只在生产环境下生效:logback 配置文件只在生产环境下生效,该配置文件放在外部化配置文件目录中,通过外部化配置引用该配置文件
具体做法如下:
添加
logstash-logback-encoder
依赖,但是我们只使用它的 encoder 处理类// 利用 logstash 打印 json 格式的 日志信息
implementation 'net.logstash.logback:logstash-logback-encoder:6.6'
logback-spring.xml 配置文件,注意该配置文件只在生产环境下生效
配置 consoleAppender ,在里面使用 LogstashEncoder 进行格式化日志信息<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<providers>
<timestamp>
<timeZone>EST</timeZone>
</timestamp>
<pattern>
<pattern>
{
"level": "%level",
"service": "orders",
"traceId": "%X{X-B3-TraceId:-}",
"spanId": "%X{X-B3-SpanId:-}",
"thread": "%thread",
"class": "%logger{40}",
"message": "%message"
}
</pattern>
</pattern>
<stackTrace>
<throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
<maxDepthPerThrowable>30</maxDepthPerThrowable>
<maxLength>2048</maxLength>
<shortenedClassNameLength>20</shortenedClassNameLength>
<rootCauseFirst>true</rootCauseFirst>
</throwableConverter>
</stackTrace>
</providers>
</encoder>
</appender>
<logger name="jsonLogger" additivity="false" level="DEBUG">
<appender-ref ref="consoleAppender"/>
</logger>
<root level="debug">
<appender-ref ref="consoleAppender"/>
</root>
</configuration>
在外部化配置文件中指向该配置文件
我一般是和外部化文件放到一起,如下所示|- bootJar
|- config
|- application.yml
|- logback-spring.xml
application.yml 配置该文件
logging:
config: file:/app/config/logback-spring.xml
level:
root: info
Spring MVC 缓存控制(HTTP 缓存)
使用 bootJar 内嵌启动的话,Spring MVC 也提供了一些缓存控制功能,官方比较详细。
注意:你用 spring boot,但是里面 wen 层,用的是 Spring mvc ,那么就你要去找 Spring MVC 的官方文档,而不是 boot 的文档
该文档中有:
- Controllers:对 controller 提供缓存支持
- Static Resources:对静态资源提供缓存支持
这里讲解下如何对一个 Controller 提供 HTTP 缓存的支持
背景
提供了一个接口:根据 ID 查询一张图片,通过流的形式响应
一般来说,这种接口无法触发浏览器的 缓存机制,但是通过如下方式可以做到
解决方案
没有缓存的写法
/**
* 图片读取
*/
@GetMapping("/img/{tppFaceId}")
public void img(@PathVariable String tppFaceId,
HttpServletResponse response) throws IOException {
TppFace face = faceService.getById(tppFaceId);
if(face == null){
throw new Exception("没有该资源");
}
final String img = face.getImg();
response.addDateHeader("Expires", System.currentTimeMillis() + 1000 * 60 * 60);
response.addDateHeader("Last-Modified", System.currentTimeMillis());
response.addHeader("Cache-Control", "public");
final Path path = Paths.get(pathServiceProperties.getWorkPath(), img);
String contentType = new Tika().detect(path.getFileName().toString());
response.setContentType(contentType);
try (
final InputStream is = Files.newInputStream(path)
) {
IoUtil.copy(is, response.getOutputStream());
}
}
上述写了 缓存头,过期时间之类的,其实并不会生效。
下面是生效的写法
@GetMapping("/img/{tppFaceId}")
public void img(@PathVariable String tppFaceId,
WebRequest request,
HttpServletResponse response) throws IOException {
// 在本场景中,图片生成之后,就永远不会改变,这里的版本号我就写死成 1 了
final String eTag = "1";
// 这里检查该请求携带过来的 eTag 版本号,如果与我们这里的一致,就直接返回
// 返回时: 框架帮我们做了重要的一件事件,更改了响应状态码为 304
if (request.checkNotModified(eTag)) {
return;
}
TppFace face = faceService.getById(tppFaceId);
if (face == null) {
throw new Exception("没有该资源");
}
final String img = face.getImg();
response.addDateHeader("Last-Modified", System.currentTimeMillis());
// 利用缓存配置构建设置头 max-age 的时间等信息
response.addHeader(HttpHeaders.CACHE_CONTROL, CacheControl.maxAge(1, TimeUnit.DAYS).getHeaderValue());
// 该 id 首次响应的时候,添加响应头,版本也写 1
response.addHeader("eTag", "1");
final Path path = Paths.get(pathServiceProperties.getWorkPath(), img);
String contentType = new Tika().detect(path.getFileName().toString());
response.setContentType(contentType);
try (
final InputStream is = Files.newInputStream(path)
) {
IoUtil.copy(is, response.getOutputStream());
}
}
Bean 生命周期事件
:::tips 暂无实战例子,做个记录先 :::
Lifecycle 接口,它的作用是让开发者可以在所有的 bean 都创建完成( getBean)之后执行自己的初始化工作,或者在退出时执行资源销毁工作。
Lifecycle 定义了三个方法,任何 Bean 实现了 Lifecycle 方法,当 Application Context 收到 start、stop 和 restart 等信号时,就会调用对应的方法。因此可以通过实现 Lifecycle 接口获得容器生命周期的回调,实现业务扩展