【问题标题】:How do i prevent a spring boot webapplication from accepting http requests while it is still starting up?如何防止 Spring Boot Web 应用程序在启动时接受 http 请求?
【发布时间】:2020-09-07 14:26:28
【问题描述】:

我有一个带有角度前端的 Spring Boot Web 应用程序。当我在 Web 应用程序仍在启动时使用 Angular 应用程序发送请求时,可能会发生 Web 应用程序可以接受该请求并尝试处理它。我相信应用程序能够在 WebSecurityConfigurerAdapter 扩展类被初始化的那一刻接收请求。

当其他类尚未完全初始化时,这会导致问题。例如,当我尝试注销仍然在前端登录的用户时,而 Web 应用程序仍在启动,它可能会导致 SQLException,因为它找不到数据库表 user

如何防止应用程序在最终类(带有@SpringBootApplication 注释的类)初始化之前接受请求?

【问题讨论】:

  • 你的数据库表用户是你的spring-boot应用创建的吗?
  • @cmlonder 是的。表是从实体创建的
  • 也许这会有所帮助:reflectoring.io/spring-boot-execute-on-startup。最有可能的@DependsOn 注释和ApplicationReadyEvent 事件似乎是一个可行的解决方案。
  • 但它已经与您期望的相似。 Spring 开始搜索其 bean(配置等)并将 Bean 注入到从您拥有 SpringBootApplication 的类开始的上下文中(除非您提供另一个上下文进行搜索,默认行为是从您的主类的当前包开始)。您是否使用任何可能在后台进行异步调用的反应式库?
  • @cmlonder 不,我没有。只要在configure(HttpSecurity httpSecurity)WebSecurityConfigurerAdapter 中定义了API 端点,应用程序就能够接收请求

标签: spring spring-boot spring-mvc


【解决方案1】:

根据@Fullslack.dev 的提示,我提出了以下解决方案。

@WebFilter(urlPatterns = {"/*"})
public class MyFilter implements Filter, ApplicationListener<ApplicationReadyEvent> {

    private boolean webApplicationReadyToAcceptRequests = false;

    @Override 
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        webApplicationReadyToAcceptRequests = true;
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
            if(!webApplicationReadyToAcceptRequests) {
                httpServletResponse.setHeader("Retry-After","35");
                httpServletResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "The server is currently starting up. Please try again in a moment.");
                return;
            }
            else
                filterChain.doFilter(servletRequest, servletResponse);
            }
    }

}

这是一个过滤器类,每个请求在呈现给@Controller 和/或@Service 注释类之前必须先通过。如果变量 webApplicationReadyToAcceptRequests 仍然为 false,则过滤器不会向下游传递请求,而是会发送带有 httpstatus 503(服务不可用)的错误消息响应。

确保您至少使用以下注释注释了一个 @Configuration 配置类:

@ComponentScan(basePackages={ "FolderWhereMyFilterIsLocated"})

这将确保 spring 知道过滤器的存在。

【讨论】:

  • 我觉得,对于成千上万的用户同时访问应用程序的关键业务应用程序来说,这不是一个可行的解决方案。
  • @NewBee 那你有什么替代方案。
  • 这对于独立应用程序来说是一个很好的解决方案,并且与我们为应用程序实施的方法相匹配。对于@NewBee 的观点,Spring Boot 通过检查 readiness 状态 并仅将流量路由到接受流量的实例,在使用负载均衡器时提供了更好的解决方案,但许多环境没有负载均衡器。更多信息:baeldung.com/spring-liveness-readiness-probes
  • 看来布尔值 webApplicationReadyToAcceptRequests 应该设置为 volatile。
【解决方案2】:

ApplicationReadyEvent 的方法不够好,因为它会在 Spring Web 应用程序启动并且 Web 服务器能够接受请求之后传播:

Tomcat started on port(s): 4134 (http) with context path ''

应用程序已启动并正在运行:

http://localhost:4134/actuator/health 返回{"status":"UP"}

如果您需要在此之前进行一些初始化,那么您必须将其附加到 Spring 上下文初始化中,类似于为 DB 连接完成的方式。最简单的方法是在一些 spring bean 的 @PostConstruct 方法中使用“init”代码。或者有一个老式的InitializingBean 接口,在afterPropertiesSet() 方法中具有类似的效果。

【讨论】:

  • 使用 ApplicationReadyEvent 的方法不够好,因为它在 Spring Web 应用程序启动并且 Web 服务器能够接受请求之后才传播。这就是@RomanSynakov 的重点。一旦ApplicationReadyEvent 被触发,webApplicationReadyToAcceptRequests 布尔值就会设置为true,这会导致应用程序接受所有传入的请求。
猜你喜欢
  • 2023-03-19
  • 2017-03-05
  • 2021-03-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-21
  • 2016-07-29
  • 1970-01-01
相关资源
最近更新 更多