文章目录
- 1. 自定义拦截器
- 2. 注册配置拦截器
- 3. 拦截器详解
- 3.1 拦截路径
- 3.2 拦截器执行流程
- 3.3 DispatcherServlet源码分析
- 3.3.1 初始化:
- 3.3.2 处理请求
- 3.3.3 适配器
拦截器是Spring框架提供的核心功能,主要用来拦截用户的请求,在指定方法前后执行预先设定的代码(如判断用户是否登录等)
以图书管理系统作为案例展示:
有一个登录接口:
以及图书信息的相关接口:
希望能做到,在用户直接访问图书信息接口的时候,进行拦截,判定用户是否登录,如果已经登录则放行,未登录则拦截,跳转到登录页面
1. 自定义拦截器
实现HandlerInterceptor接口,并重写所有方法
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("拦截:检验用户是否登录");
HttpSession session = request.getSession();
UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_INFO_SESSION_KEY);
if (userInfo == null) {
log.info("校验失败");
response.setStatus(401);
response.getOutputStream().write("noLogin".getBytes());
return false;
}
log.info("校验成功");
return true;
}
}
2. 注册配置拦截器
实现WebMvcConfigurer接口,并重写addInterceptor方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截对象
@Autowired
private LoginInterceptor loginInterceptor;
//addPath--添加拦截对象
//excludePath -- 排除拦截对象(如登录接口不能被拦截
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").
excludePathPatterns("/user/login").
excludePathPatterns("/css/**").
excludePathPatterns("/js/**").
excludePathPatterns("/imgs/**").
excludePathPatterns("/**/*.html").excludePathPatterns("/**/*.ico");
}
}
当我们直接访问图书相关接口的时候:
就能起到拦截效果
3. 拦截器详解
3.1 拦截路径
拦截路径用来定义拦截器对哪些请求生效
通过addPathPatterns()
方法指定要拦截哪些请求,通过 excludePathPatterns()
指定哪些请求不拦截
拦截路径 | 含义 | 举例 |
---|---|---|
/* | 一级路径 | 能匹配/user, /book,但是不能匹配/user/login |
/** | 任意级路径 | 能匹配/user, /use/login |
/book/* | /book下的一级路径 | 能匹配/book/addBook, 不能匹配/book/addBook/add |
/book/** | /book下的任意级路径 | 能匹配/book/addBook, 也能匹配/book/addBook/add |
3.2 拦截器执行流程
@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("目标方法前执行....");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("目标方法后执行....");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("视图渲染完后执行....");
}
}
访问后:
preHandle()
:目标方法执行前执行,返回true则继续执行后续操作;返回false则中断后续操作postHandle()
:目标方法执行后执行afterCompletion()
:视图渲染完后执行,最后执行(现在前后端分离后基本不涉及了)
3.3 DispatcherServlet源码分析
当Tomcat启动的时候,有一个核心的类DispatcherServlet,用来控制程序的执行顺序
所有请求都会先进到DispatcherServlet,执行doDisPatch调度方法
3.3.1 初始化:
DispatcherServlet
的初始化方法init是在其父类FrameworkServlet
的父类HttpServletBean
中实现的,源码如下:
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
实际上这个方法就是在初始化Servlet,包括初始化DispatcherServlet
在这个方法的最后,调用了initServletBean
方法,是在FrameworkServlet
里面实现的
主要的作用就是建立WebApplicationContext
容器(即上下文),用来存储Bean
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
我们平时启动项目自动打印的日志,有一部分就是这里实现的(这一部分是在接受请求后才能打印出来的)
在这个方法内,调用了initWebApplicationContext
方法初始化Bean容器的过程中,调用了onRefresh
方法,用来初始化SpringMVC的容器,就是存储的Controller之类的很多Bean
3.3.2 处理请求
此时接受到的请求是在DispatcherServlet.doDispatch
进行处理的
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//处理请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//获取适配器(后文解释)
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//执行拦截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//执行目标方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//执行拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
//处理视图 在这个方法里面执行拦截器的afterCompletion方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//捕获异常也要执行拦截器的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//捕获异常也要执行拦截器的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new ServletException("Handler processing failed: " + err, err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
我们来看applyPreHandle方法:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
此时就会获取到所有的拦截器,执行拦截器里面的preHand方法
3.3.3 适配器
HandlerAdapter
主要用于支持不同类型的处理器,在Spring MVC中,处理器有很多种,如基于JavaBean的控制器,基于注解的控制器(如@Controller)等等,每个控制器可能需要不同的方式来调用,为了让他们能够适配统一的请求流程,就使用了HandlerAdapter
,这样Spring MVC就能通过一个统一的接口来处理来自各种控制器的请求
这就涉及到设计模式之一 – 适配器模型的思想