Spring MVC InterceptorHandler使用DeferredResult调用了两次

时间:2014-11-18 13:21:53

标签: java spring-mvc

当我使用自定义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 {
    }
}

My 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以便仅返回&#34;空白&#34;模板(而不是DeferredResult使用&#34;空白&#34;模板),我在每个页面加载时只看到preHandle的一个输出。

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

6 个答案:

答案 0 :(得分:12)

您需要使用class Node { HashMap<Character, Node> children; boolean end; public Node(boolean b){ children = new HashMap<Character, Tries.Node>(); end = false; } } private Node root; public Tries(){ root = new Node(false); } public static void main(String args[]){ Tries tr = new Tries(); tr.add("dog"); tr.add("doggy"); System.out.println(tr.search("dogg"));; } private boolean search(String word) { Node crawl = root; int n = word.length(); for(int i=0;i<n;i++){ char ch = word.charAt(i); if(crawl.children.get(ch) == null){ return false; } else { crawl = crawl.children.get(ch); if(i==n-1 && crawl.end == true){ return true; } } } return false; } private void add(String word) { Node crawl = root; int n = word.length(); for(int i=0;i<n;i++){ char ch = word.charAt(i); if(crawl.children.containsKey(ch)){ crawl = crawl.children.get(ch); } else { crawl.children.put(ch, new Node(false)); Node temp = crawl.children.get(ch); if(i == n-1){ temp.end = true; } crawl = temp; System.out.println(ch + " " + crawl.end); } } }

org.springframework.web.servlet.AsyncHandlerInterceptor

Spring MVC 执行序列:

public interface AsyncHandlerInterceptor extends HandlerInterceptor {

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

}

答案 1 :(得分:6)

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

答案 2 :(得分:3)

当我正在探索添加过滤器和拦截器时,我确定这是由异步调用引起的。你在这里使用 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]]

总之,它在一个线程中执行一次,但这是两个线程。

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

或者你只是使用传统的同步调用,如:

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

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

希望它有所帮助!

答案 3 :(得分:2)

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

public class MyInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // only allows the REQUEST dispatcher and ignores the ERROR and any other dispatchers
        // this prevents the preHandle function from running multiple times
        if (request.getDispatcherType().name() != "REQUEST") {
            return true;
        }
        // ... continue
        return true;
    }
}

答案 4 :(得分:1)

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

       

答案 5 :(得分:0)

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