Spring Boot 2.1 和 Java 11 中的 Bean 生命周期

Posted

技术标签:

【中文标题】Spring Boot 2.1 和 Java 11 中的 Bean 生命周期【英文标题】:Bean lifecycle in Spring Boot 2.1 and Java 11 【发布时间】:2019-05-01 14:36:01 【问题描述】:

我的项目正在从带有 Java 8 的 Spring Boot 2.0.4 迁移到带有 Java 11 的 Spring Boot 2.1.0。当应用程序使用 Spring Boot 2.0.4 和 Java 8 构建并在 Docker / Docker Compose 中运行时,@ 987654322@-annotated 方法被调用,但在迁移到 Spring Boot 2.1.0 和 Java 11 之后,@PreDestroy-annotated 方法不再被调用。

我已经尝试从注解切换到实现InitializingBeanDisposableBean,如here 所述,但没有调用DisposableBean.destroy 方法。

我也尝试向 javax.annotation-api 版本 1.3.2 添加依赖项,结果相同。

如何重现:

使用生命周期 bean 创建一个最小的 Spring 应用程序:

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class Life implements InitializingBean, DisposableBean 
    @Override
    public void destroy() throws Exception 
        System.out.println("--- Life.shutdown");
    
    @Override
    public void afterPropertiesSet() throws Exception 
        System.out.println("--- Life.startup");
    

从目标子文件夹启动 Spring 应用程序:

cd target
java -jar demo-0.0.1-SNAPSHOT.jar

当使用 Ctrl+C 停止应用程序时,会调用 DisposableBean.destroy。

返回父文件夹:

cd ..

使用 Maven 启动 Spring 应用程序:

mvn spring-boot:run

当使用 Ctrl+C 停止应用程序时,会调用 DisposableBean.destroy。

Dockerfile:

FROM openjdk:11.0.1-jre-slim
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT java -jar /app.jar

构建、运行和停止 Docker 映像:

docker build -t demo .
docker run -p 8080:8080 demo
docker ps
docker stats 3ca5b804ab13
docker stop 3ca5b804ab13
docker logs 3ca5b804ab13

当使用 docker stop 停止应用程序时,不会调用 DisposableBean.destroy。

docker-compose.yml:

demo:
  image: demo
  ports:
  - '8080:8080'

使用 Docker Compose 运行 Docker 镜像(模拟 OpenShift):

docker-compose up
docker-compose down
demo_demo_1 exited with code 137

当使用 docker-compose down 停止应用程序时,不会调用 DisposableBean.destroy。

我怀疑 Docker 在发出 SIGKILL 之前尝试了 SIGTERM,因为在容器被杀死之前有 10 秒的延迟。

【问题讨论】:

有没有什么东西正在用SIGKILL杀死JVM? 很难在没有看到任何类型的配置文件、docker compose 文件等的情况下诊断任何东西。当你直接在金属上运行而不涉及 docker 时,你能重现这个问题吗? 【参考方案1】:

设置可能出错的地方很多。 首先,我建议确定 java/spring 部分是否有一些问题,或者它是与 docker/environment 相关的问题。从这个问题来看,它听起来像是与 java 相关的,但实际上我怀疑它不在 java/spring 中。

所以,mvn spring-boot:run 按预期工作,我看到您可能使用 spring boot 插件将 spring boot 应用程序打包为 jar (app.jar)。这也是可能出错的地方,因为 Spring Boot 使用特殊的类加载器在运行时加载内容。

因此,为了完全消除“java/spring”部分,导航到您的target 目录并运行java -jar app.jar(当然,请确保您的本地计算机上安装了 java 11)。如果它不起作用 - 调查 java /spring 部分,否则继续 docker 部分。

应用程序可能会按预期工作。

现在,关于 docker 设置。 运行 docker compose 后发现失败,

您可以使用以下命令:

docker ps -a // -a flag to see container ids of containers that were stopped for whatever reason as well.

现在找到退出的 java 进程的 ID 并检查其日志:

docker logs <ID_OF_THE_EXTED_CONTAINER_GOES_HERE> 

现在有可能是应用程序上下文无法启动(可能是网络相关问题或其他问题,如果没有看到实际日志就很难判断),因此是问题。

另一个可能的问题是应用程序“太重”(我的意思是它超过了对 docker 容器施加的一些配额)。

您可以运行docker stats &lt;CONTAINER_ID&gt; 命令实时查看其内存/cpu 使用情况或从应用程序内收集指标。

【讨论】:

感谢您的建议 - 我已编辑我的问题以包含额外的步骤。因此,应用程序在直接在 JVM 中运行(从命令行或通过 Maven)时按预期工作,但在 Docker 中运行时则不然。我的演示应用程序是一个非常轻量级的 Spring 项目,它具有一个简单的“hello”方法,可以响应 localhost:8080 的 HTTP 请求。 docker logs 命令显示的和控制台窗口一样,所以我没有找到任何额外的信息。 docker logs 命令的输出(使用 docker stop 命令停止容器后)以容器启动时显示的最后一行结束:2018-12-03 05:40:12.487 INFO 6 --- [main] com.example.demo.DemoApplication:在 3.791 秒内启动 DemoApplication(JVM 运行 4.374)【参考方案2】:

我想我找到了解决方案(在this blog entry 中):在 Dockerfile 中使用 exec 形式而不是 shell 形式,以便 Docker 发出的 SIGTERM 命中 java 进程而不是 bash 进程(它不会将信号转发到任何子进程)。

Shell 形式(使用 /bin/sh -c shell 执行): ENTRYPOINT java -jar /app.jar 执行形式(不带外壳执行): ENTRYPOINT ["java", "-jar", "/app.jar"]

【讨论】:

以上是关于Spring Boot 2.1 和 Java 11 中的 Bean 生命周期的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 2.1 MVC 日志记录

如何使用 gradle 6+ 和 java 11+ 在 Spring Boot 中配置 spock

Spring Boot 2.1.0 和 Java 11 上的 Spring Data Redis 无法正常工作

Spring Boot 2.1 缺少多个 org.hibernate.jpa.event 类

springboot1.5,2.1 + jdk8,jdk11的 内存占用对比

带有 Java 11、HTTPS 和 HTTP 2.0 的 Spring Boot 2