当我使用自定义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
时调用了两次,是否可以避免这种情况?
答案 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 方法在异步处理中将始终执行两次。