xiaoxiao
发布于 2024-12-01 / 7 阅读 / 0 评论 / 0 点赞

[spring] HandlerInterceptor在使用异步servlet时会重复调用preHandle

在测试webflux在使用servlet容器时能否正确使用spring拦截器时发现的问题,特此记录。

如果项目有使用异步servlet,要避免preHandle中有io操作,以免有性能问题。

我上家公司就有拦截器的preHandle调用权限服务验证权限,如果有使用到异步servlet就会造成二次调用。

使用到异步servlet的常用情况

  1. DeferredResult

  2. SseEmitter

  3. webflux

Tomcat and Jetty can be used with both Spring MVC and WebFlux. Keep in mind, however, that the way they are used is very different. Spring MVC relies on Servlet blocking I/O and lets applications use the Servlet API directly if they need to. Spring WebFlux relies on Servlet non-blocking I/O and uses the Servlet API behind a low-level adapter. It is not exposed for direct use.

拦截器代码

package space.xiaoxiao.spring.demo;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class TestHandlerInterceptor implements HandlerInterceptor {
    AtomicInteger atomicInteger = new AtomicInteger();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        int i = atomicInteger.incrementAndGet();
        log.info("preHandle - {} - {}", i, request.getSession().getId());
        request.setAttribute("i", i);
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle - {} - {}", request.getAttribute("i"), request.getSession().getId());
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion - {} - {}", request.getAttribute("i"), request.getSession().getId());
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

控制器代码

package space.xiaoxiao.spring.demo.controller;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Mono;

import java.time.Duration;

@Slf4j
@RestController
public class TestController {

    @Autowired
    private HttpServletRequest request;


    @RequestMapping("/test")
    public DeferredResult<String> test(HttpServletRequest request) {
        log.info("test " + request.getAttribute("i"));
        DeferredResult<String> stringDeferredResult = new DeferredResult<>();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                stringDeferredResult.setResult("test result");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        return stringDeferredResult;
    }

    @RequestMapping("/test2")
    public Mono<Long> test2(HttpServletRequest request) {
        log.info("test2 " + request.getAttribute("i"));
        return Mono.delay(Duration.ofSeconds(2));
    }

    @RequestMapping("/test3")
    public SseEmitter test3() {
        log.info("test3 " + request.getAttribute("i"));
        SseEmitter sseEmitter = new SseEmitter();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    sseEmitter.send(String.format("data: %s\n\n", i));
                    Thread.sleep(1000);
                }
                sseEmitter.complete();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        return sseEmitter;
    }
}

对test、test2、test3依次调用的输出结果,发现每次访问都会触发两次preHandle。而且线程也被切换了,这里我开启了jdk21的虚拟线程特性,所以线程池都不一样,jetty-*是虚拟线程,qtp570294012-*为平台线程。

2024-12-01T14:38:31.283+08:00  INFO 25739 --- [spring-demo] [        jetty-0] s.x.spring.demo.TestHandlerInterceptor   : preHandle - 1 - node0yyidyo0rvokv17ywfevlqnyx30
2024-12-01T14:38:31.290+08:00  INFO 25739 --- [spring-demo] [        jetty-0] s.x.s.demo.controller.TestController     : test 1
2024-12-01T14:38:32.296+08:00  INFO 25739 --- [spring-demo] [qtp570294012-51] s.x.spring.demo.TestHandlerInterceptor   : preHandle - 2 - node0yyidyo0rvokv17ywfevlqnyx30
2024-12-01T14:38:32.321+08:00  INFO 25739 --- [spring-demo] [qtp570294012-51] s.x.spring.demo.TestHandlerInterceptor   : postHandle - 2 - node0yyidyo0rvokv17ywfevlqnyx30
2024-12-01T14:38:32.321+08:00  INFO 25739 --- [spring-demo] [qtp570294012-51] s.x.spring.demo.TestHandlerInterceptor   : afterCompletion - 2 - node0yyidyo0rvokv17ywfevlqnyx30
2024-12-01T14:38:38.780+08:00  INFO 25739 --- [spring-demo] [        jetty-1] s.x.spring.demo.TestHandlerInterceptor   : preHandle - 3 - node0dcq9labny7v5gc3a7wavatsl1
2024-12-01T14:38:38.780+08:00  INFO 25739 --- [spring-demo] [        jetty-1] s.x.s.demo.controller.TestController     : test2 3
2024-12-01T14:38:40.800+08:00  INFO 25739 --- [spring-demo] [qtp570294012-55] s.x.spring.demo.TestHandlerInterceptor   : preHandle - 4 - node0dcq9labny7v5gc3a7wavatsl1
2024-12-01T14:38:40.813+08:00  INFO 25739 --- [spring-demo] [qtp570294012-55] s.x.spring.demo.TestHandlerInterceptor   : postHandle - 4 - node0dcq9labny7v5gc3a7wavatsl1
2024-12-01T14:38:40.813+08:00  INFO 25739 --- [spring-demo] [qtp570294012-55] s.x.spring.demo.TestHandlerInterceptor   : afterCompletion - 4 - node0dcq9labny7v5gc3a7wavatsl1
2024-12-01T14:38:46.251+08:00  INFO 25739 --- [spring-demo] [        jetty-2] s.x.spring.demo.TestHandlerInterceptor   : preHandle - 5 - node013cj4bnarpu8714nzhybjw8d8r2
2024-12-01T14:38:46.252+08:00  INFO 25739 --- [spring-demo] [        jetty-2] s.x.s.demo.controller.TestController     : test3 5
2024-12-01T14:38:56.266+08:00  INFO 25739 --- [spring-demo] [qtp570294012-51] s.x.spring.demo.TestHandlerInterceptor   : preHandle - 6 - node013cj4bnarpu8714nzhybjw8d8r2
2024-12-01T14:38:56.266+08:00  INFO 25739 --- [spring-demo] [qtp570294012-51] s.x.spring.demo.TestHandlerInterceptor   : postHandle - 6 - node013cj4bnarpu8714nzhybjw8d8r2
2024-12-01T14:38:56.267+08:00  INFO 25739 --- [spring-demo] [qtp570294012-51] s.x.spring.demo.TestHandlerInterceptor   : afterCompletion - 6 - node013cj4bnarpu8714nzhybjw8d8r2

评论