今天从源码的角度,靠断电跟踪技术分析一下springMVC内部的执行流程,(包括拦截器、过滤器、servlet的dispatch、异常处理等)
1.我们先自定义一个拦截器(springmvc的全局拦截器)
//定义一个拦截器
public class TimeAccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LocalTime now=LocalTime.now();//JDK8中的时间对象
int hour=now.getHour();//获取当前时间对应小时
log.info("hour {}",hour);
if(hour<=7||hour>=22)
throw new RuntimeException("请在6~10点进行访问");
return true;
}
}
//在springmvc全局引入这个拦截器
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
//配置spring mvc 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimeAccessInterceptor())
.addPathPatterns("/user/*");
}
}
然后我们断点跟踪,发现生效了。注意这里的调用栈,拦截器是在servlet分发请求之后调用的
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
.......
try {
this.doDispatch(request, response); //分发请求
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
2.由于我们这里使用抛异常的方式对请求进行拦截的,所以客户端的请求结果会直接显示500异常,但我们可以将异常返回结果进行处理,处理成客户端看得懂的格式
我们用一个springmvc的全局异常处理GlobalControllerAdvice
@RestControllerAdvice
public class GlobalControllerAdvice {
@ExceptionHandler({RuntimeException.class})
public SysResult fail(Exception e){
return SysResult.fail(null,e.getMessage());
}
}
现在我们再来看返回结果:就变成了这样:就好看多了
这个时候DispatchServlet类的doDispatch方法必定会抛出异常,但是源码在抛出异常之后,仍然对分发的结果做出了处理:processDispatchResult
2.我们再给自定义的拦截器重写它的afterCompletion,看看他的执行时机
这时候我们发现,如果在perHandle里抛出了异常或者返回值为false的时候,afterCompletion里的内容是不会被执行的。为什么?
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
这段源码告诉我们,当preHandle返回值为false的时候,会去执行下面的triggerAfterCompletion方法,而这个方法内部执行的时候,根据mappedHandler的interceptorIndex来决定是否执行循环体,而这个interceptorIndex值在初始化的时候为-1,如果抛出异常或者preHandle返回false的时候,interceptorIndex值不会有变化,所以永远不会执行到aftetCompletion方法。而只有当返回值未true的时候,interceptorIndex才会累加到数组长度-1,之后就会执行afterCompletion方法了。(这里的mappedHandler,可以把他理解为一个处理对象,同时他里面包含了handler和一些拦截器)
那有人会问:这个mappedHandler是从哪里来的呢?再往前看,在doDispatch方法内
mappedHandler = this.getHandler(processedRequest);
有这样一行代码,往里看在AbstractHandlerMapping类的方法中
while(var5.hasNext()) {
HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else {
chain.addInterceptor(interceptor);
}
}
这里将拦截器添加到了mappedHandler中,当然还包括那个handler(也就是controller里的那个method),而这个adaptedInterceptors哪里来的呢?容器初始化的。。。