【问题标题】:Spring MVC InterceptorHandler called twice with DeferredResultSpring MVC InterceptorHandler 使用 DeferredResult 调用了两次
【发布时间】:2015-01-15 16:55:22
【问题描述】:

当我使用自定义HandlerInterceptor 并且我的控制器返回DeferredResult 时,我的自定义拦截器的preHandle 方法会在每个请求上调用两次。考虑一个玩具示例。

我的自定义拦截器:

public class MyInterceptor implements HandlerInterceptor {
    static int i = 0;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(i++);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

我的 Spring Java 配置:

@Configuration
@EnableWebMvc
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor());
    }
}

我的控制器:

@Controller
public class MyController {

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public DeferredResult<String> test() {
        DeferredResult<String> df = new DeferredResult<String>();
        df.setResult("blank");
        return df;
    }
}

因此,在每个页面加载时,我都会看到 preHandle 方法的两个输出。但是,如果我修改MyController 以仅返回“空白”模板(而不是带有“空白”模板的DeferredResult),我在每个页面加载时只会看到preHandle 的一个输出。

所以,我的问题是为什么在我使用 DeferredResult 时会调用两次 preHandle,是否可以避免这种情况?

【问题讨论】:

  • 查看this,其中详细解释了该行为。
  • @JavaBond 您能否提供更多详细信息,因为我还没有从那篇帖子中找到解释。
  • 引用accepted answer Roughly speaking. A DeferredResult is associated with an open request. When the request completes, the DeferredResult is removed from the map, and then, the client issues a new long polling request, which adds a new DeferredResult instance 这意味着客户端发出了一个新请求,DeferredResult 作为响应附加到该请求。这反过来解释了为什么每个请求都有两个拦截器调用。如果您需要进一步的解释,请告诉我。
  • 我想阅读 HTTP Post 并删除 SOAP 标签,然后将 XML 数据简单地传递给 Rest 端点,这可能吗?

标签: java spring-mvc


【解决方案1】:

你需要使用org.springframework.web.servlet.AsyncHandlerInterceptor:

public interface AsyncHandlerInterceptor extends HandlerInterceptor {

    void afterConcurrentHandlingStarted(
            HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception;

}

Spring MVC执行顺序:

preHandle
afterConcurrentHandlingStarted
preHandle
postHandle
afterCompletion

【讨论】:

  • 我无法运行 afterConcurrentHandlingStarted 方法。我的应用程序不会执行代码,但会执行 preHandle
  • 这里何时调用处理程序?在preHandleafterConcurrentHandlingStarted 之间按顺序排列?
【解决方案2】:

通过检查request.getDispatcherType() 的值可以看出两个调用之间的区别。

【讨论】:

    【解决方案3】:

    我通过检查request.getDispatcherType() 的值来遵循@thunder 提到的内容,这对我有用。

    public class MyInterceptor extends HandlerInterceptorAdapter {
    
        @Override
        public boolean preHandle(HttpServletRequest request, 
                                 HttpServletResponse response,
                                 Object handler) throws Exception {
            // NOTE: For all dispatchers that are not "REQUEST" like "ERROR", do
            // an early return which prevents the preHandle function from
            // running multiple times
            if (request.getDispatcherType() != DispatcherType.REQUEST) {
                return true;
            }
            // ... do other stuff and then do a final return
            return true;
        }
    }
    

    【讨论】:

    • 为什么不检查实际的 DispatchType? request.getDispatcherType() != DispatcherType.REQUEST
    • 你必须用相等的方法比较字符串,而不是!=
    • getDispatcherType() 返回一个枚举,不需要调用 name() 然后比较字符串。我会接受@xxtesaxx 的建议。
    【解决方案4】:

    当我正在探索添加过滤器和拦截器时,我很确定这是由异步调用引起的。您在此处使用 DeferredResult,spring 将在原始线程中进行过滤并在新线程中再次过滤。如果您将日志级别设置为调试,您会注意到这样的日志。

    15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@efe6068
    15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
    15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@163cab73
    15:14:07.148 [http-nio-8199-exec-6] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Bound request context to thread: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ org.apache.catalina.core.ApplicationHttpRequest@42f1262]]
    

    一句话,在一个线程中执行一次,但这里是两个线程。

    当我挖掘谷歌时,我发现没有好的解决方案。 如果您有类似 auth 的请求,请添加一个四处走走的方式 security.filter-dispatcher-types=请求,错误 然后新线程(异步线程)将不会获得安全上下文。您需要检查它并停止其中的过滤器链。

    或者您只使用传统的同步调用,例如:

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public String test() {
        return "blank";
    }
    

    我找到了另一个有用的答案。 https://jira.spring.io/browse/SPR-12608

    希望对你有帮助!

    【讨论】:

      【解决方案5】:

      在 preHandle 方法中,如果你有 response.sendError(, ),那么这个 preHandle 方法会为每个 API 请求执行两次。所以删除 sendError() 方法只执行一次。

      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          response.sendError(401, "Token is invalid");
          return false;
      }
          
      
             
      

      【讨论】:

        【解决方案6】:

        AsyncHandlerInterceptor 的 prehandle 方法在异步处理中总是会执行两次。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-15
          • 2017-06-12
          • 1970-01-01
          • 2016-07-19
          • 2017-02-16
          • 2023-04-04
          相关资源
          最近更新 更多