Spring MVC 拦截器处理程序在使用 DeferredResult 时被调用两次

20

当我使用自定义的 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方法的输出。

因此,我的问题是为什么在使用DeferredResultpreHandle被调用两次,是否有可能避免这种情况?


请查看此链接,其中详细解释了Spring MVC中DeferredResult类的行为。 - Bond - Java Bond
@JavaBond,能否请您提供更多细节,因为我从那篇帖子中没有找到解释。 - Stanislav Poslavsky
引用自被接受的答案:大致上来说,DeferredResult与一个开放的请求相关联。当请求完成时,DeferredResult将从映射中删除,然后客户端发出新的长轮询请求,该请求添加了一个新的DeferredResult实例,这意味着客户端发出了一个新的请求,其中附加了“DeferredResult”作为响应。这反过来解释了为什么每个请求都有两次拦截器调用。如果您需要进一步的解释,请告诉我。 - Bond - Java Bond
我想读取 HTTP Post 的数据并移除 SOAP 标签,然后将 XML 数据传递到 Rest 终端点,这可能吗? - PAA
6个回答

18

您需要使用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 - SomeGuyOnAComputer
这里什么时候调用处理程序?按照 preHandleafterConcurrentHandlingStarted 的顺序调用吗? - vizsatiz

16

10

我按照 @thunder 所提到的方法,通过检查 request.getDispatcherType() 的值来解决了这个问题。

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;
    }
}

3
为什么不检查实际的DispatchType? request.getDispatcherType()!= DispatcherType.REQUEST - xxtesaxx
4
你必须使用“equal”方法来比较字符串,而不是使用“!=”。 - ru51an
getDispatcherType() 返回一个枚举,不需要调用 name() 然后比较字符串。我会选择 @xxtesaxx 的建议。 - Tiago Leite

5

我在探索添加过滤器和拦截器时,我相当确定这是由异步调用引起的。你在这里使用了一个DeferredResult,Spring会在原始线程中进行过滤,并在新线程中再次过滤它。如果将日志级别设置为Debug,您将注意到像这样的日志。

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]]

总之,它在一个线程中执行一次,但这里有两个线程。
当我查找谷歌时,我发现没有好的解决方案。如果您的请求中有类似认证的东西,解决方法是添加 security.filter-dispatcher-types=REQUEST, ERROR ,然后新的线程(异步)将无法获取安全上下文。您需要检查它并停止其中的过滤器链。或者您可以使用传统的同步调用,如:
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
    return "blank";
}

我发现了另一个有用的答案。https://jira.spring.io/browse/SPR-12608希望这能有所帮助!

1

在preHandle方法中,如果您使用response.sendError(<>, <>),则每个API请求都会执行两次此preHandle方法。因此,请删除sendError()方法,以便仅执行一次。

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

       

0

在异步处理中,AsyncHandlerInterceptor 的 prehandle 方法将始终被执行两次。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接