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.xmllevel: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 版本号,如果与我们这里的一致,就直接返回// 返回时: 框架帮我们做了重要的一件事件,更改了响应状态码为 304if (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 首次响应的时候,添加响应头,版本也写 1response.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 接口获得容器生命周期的回调,实现业务扩展
