Spring Framework 官方文档阅读翻译(V5.3.15)

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 包的时候使用如下的形式传参

  1. 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 目录下,如下所示:

  1. |- bootJar
  2. |- config
  3. |- application.yml
  4. |- application-prod.yml

我们将所有的配置属性都可以写到这个 application.yml 文件中,此时外部文件的属性优先级最高,会覆盖掉程序内部的配置文件属性

日志变 JSON 格式输出

背景

放在 k8s 下,控制台输出的日志将被抽走,原来多行日志(特别是堆栈错误信息)会被解析成多行,而不是一行

解决方案

Spring boot 版本:2.4.1

解决思路如下:

  1. 利用 logback 中的 appender.encoder 格式化控制台输出格式
  2. 只在生产环境下生效:logback 配置文件只在生产环境下生效,该配置文件放在外部化配置文件目录中,通过外部化配置引用该配置文件

具体做法如下:

  1. 添加 logstash-logback-encoder 依赖,但是我们只使用它的 encoder 处理类

    1. // 利用 logstash 打印 json 格式的 日志信息
    2. implementation 'net.logstash.logback:logstash-logback-encoder:6.6'
  2. logback-spring.xml 配置文件,注意该配置文件只在生产环境下生效
    配置 consoleAppender ,在里面使用 LogstashEncoder 进行格式化日志信息

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <configuration>
    3. <include resource="org/springframework/boot/logging/logback/base.xml" />
    4. <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
    5. <encoder class="net.logstash.logback.encoder.LogstashEncoder">
    6. <providers>
    7. <timestamp>
    8. <timeZone>EST</timeZone>
    9. </timestamp>
    10. <pattern>
    11. <pattern>
    12. {
    13. "level": "%level",
    14. "service": "orders",
    15. "traceId": "%X{X-B3-TraceId:-}",
    16. "spanId": "%X{X-B3-SpanId:-}",
    17. "thread": "%thread",
    18. "class": "%logger{40}",
    19. "message": "%message"
    20. }
    21. </pattern>
    22. </pattern>
    23. <stackTrace>
    24. <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
    25. <maxDepthPerThrowable>30</maxDepthPerThrowable>
    26. <maxLength>2048</maxLength>
    27. <shortenedClassNameLength>20</shortenedClassNameLength>
    28. <rootCauseFirst>true</rootCauseFirst>
    29. </throwableConverter>
    30. </stackTrace>
    31. </providers>
    32. </encoder>
    33. </appender>
    34. <logger name="jsonLogger" additivity="false" level="DEBUG">
    35. <appender-ref ref="consoleAppender"/>
    36. </logger>
    37. <root level="debug">
    38. <appender-ref ref="consoleAppender"/>
    39. </root>
    40. </configuration>
  3. 在外部化配置文件中指向该配置文件
    我一般是和外部化文件放到一起,如下所示

    1. |- bootJar
    2. |- config
    3. |- application.yml
    4. |- logback-spring.xml
  4. application.yml 配置该文件

    1. logging:
    2. config: file:/app/config/logback-spring.xml
    3. level:
    4. root: info

Spring MVC 缓存控制(HTTP 缓存)

使用 bootJar 内嵌启动的话,Spring MVC 也提供了一些缓存控制功能,官方比较详细

注意:你用 spring boot,但是里面 wen 层,用的是 Spring mvc ,那么就你要去找 Spring MVC 的官方文档,而不是 boot 的文档

该文档中有:

  • Controllers:对 controller 提供缓存支持
  • Static Resources:对静态资源提供缓存支持

这里讲解下如何对一个 Controller 提供 HTTP 缓存的支持

背景

提供了一个接口:根据 ID 查询一张图片,通过流的形式响应

一般来说,这种接口无法触发浏览器的 缓存机制,但是通过如下方式可以做到

解决方案

没有缓存的写法

  1. /**
  2. * 图片读取
  3. */
  4. @GetMapping("/img/{tppFaceId}")
  5. public void img(@PathVariable String tppFaceId,
  6. HttpServletResponse response) throws IOException {
  7. TppFace face = faceService.getById(tppFaceId);
  8. if(face == null){
  9. throw new Exception("没有该资源");
  10. }
  11. final String img = face.getImg();
  12. response.addDateHeader("Expires", System.currentTimeMillis() + 1000 * 60 * 60);
  13. response.addDateHeader("Last-Modified", System.currentTimeMillis());
  14. response.addHeader("Cache-Control", "public");
  15. final Path path = Paths.get(pathServiceProperties.getWorkPath(), img);
  16. String contentType = new Tika().detect(path.getFileName().toString());
  17. response.setContentType(contentType);
  18. try (
  19. final InputStream is = Files.newInputStream(path)
  20. ) {
  21. IoUtil.copy(is, response.getOutputStream());
  22. }
  23. }

上述写了 缓存头,过期时间之类的,其实并不会生效。

下面是生效的写法

  1. @GetMapping("/img/{tppFaceId}")
  2. public void img(@PathVariable String tppFaceId,
  3. WebRequest request,
  4. HttpServletResponse response) throws IOException {
  5. // 在本场景中,图片生成之后,就永远不会改变,这里的版本号我就写死成 1 了
  6. final String eTag = "1";
  7. // 这里检查该请求携带过来的 eTag 版本号,如果与我们这里的一致,就直接返回
  8. // 返回时: 框架帮我们做了重要的一件事件,更改了响应状态码为 304
  9. if (request.checkNotModified(eTag)) {
  10. return;
  11. }
  12. TppFace face = faceService.getById(tppFaceId);
  13. if (face == null) {
  14. throw new Exception("没有该资源");
  15. }
  16. final String img = face.getImg();
  17. response.addDateHeader("Last-Modified", System.currentTimeMillis());
  18. // 利用缓存配置构建设置头 max-age 的时间等信息
  19. response.addHeader(HttpHeaders.CACHE_CONTROL, CacheControl.maxAge(1, TimeUnit.DAYS).getHeaderValue());
  20. // 该 id 首次响应的时候,添加响应头,版本也写 1
  21. response.addHeader("eTag", "1");
  22. final Path path = Paths.get(pathServiceProperties.getWorkPath(), img);
  23. String contentType = new Tika().detect(path.getFileName().toString());
  24. response.setContentType(contentType);
  25. try (
  26. final InputStream is = Files.newInputStream(path)
  27. ) {
  28. IoUtil.copy(is, response.getOutputStream());
  29. }
  30. }

Bean 生命周期事件

官方文档

:::tips 暂无实战例子,做个记录先 :::

Lifecycle 接口,它的作用是让开发者可以在所有的 bean 都创建完成( getBean)之后执行自己的初始化工作,或者在退出时执行资源销毁工作。

Lifecycle 定义了三个方法,任何 Bean 实现了 Lifecycle 方法,当 Application Context 收到 start、stop 和 restart 等信号时,就会调用对应的方法。因此可以通过实现 Lifecycle 接口获得容器生命周期的回调,实现业务扩展