二. spingmvc之整体处理流程

本文深入剖析SpringMVC的工作原理,重点介绍了DispatchServlet的作用及其处理流程,包括如何选择Handler、HandlerAdapter,以及视图解析的过程。同时分享了实用技巧,如通过监听事件记录日志和自定义拦截器。
摘要由CSDN通过智能技术生成

spingmvc之整体处理流程

一. 概述

springmvc对请求的处理主要是围绕DispatchServlet展开的。

一个请求进来以后,首先经过tomcat的一系列处理,然后根据不同的请求类型,servlet不同的方法,例如get请求调用doGet()。springmvc在处理该请求的过程中,先找到处理该请求的Handler,即我们编写的业务方法;接着找到调用该HandlerHandlerAdapter;然后执行handler;接着利用ViewResolver对视图进行解析,最后封装成response返回给浏览器。

Handler可以表现为多种形式,我们最常用的即添加了@RequestMapping的方法

HandlerAdapter是用来执行不同Handler的适配器,每一种Handler都需要一种适配器,通常用的是RequestMappingHandlerAdapter

视图层并不是必须的,当我们使用@RequestBody时,会在调用方法的时候就返回response,后续的视图解析就不进行了。

二. 整体流程

2.1 FrameworkServlet

​ 在FrameworkServlet中重写了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法。最后所有需要自己处理的请求都交给了processRequest方法进行统一处理。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;

      //设置LocaleContext信息
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext = buildLocaleContext(request);

 		//设置RequestAttributes,可以用来获取request和session的属性
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
		//异步处理相关
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		initContextHolders(request, localeContext, requestAttributes);

		try {
          //模板方法,被DispatchServlet重写
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
      	//恢复了LocaleContextHolder和RequestContextHolder,在Sevlet外面可能还有别的操作,如Filter(Spring-MVC自己的HandlerInterceptor是在doService内部的)等,为了不影响那些操作,所以需要进行恢复
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
          //发布了ServletRequestHandledEvent事件
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

这个方法,主要做了两件事情:

  1. LocaleContextRequestAttributes的设置及恢复
  2. 处理完后发布了ServletRequestHandledEvent消息

RequestAttribute属性如下,主要是包含了request和session的信息,放在RequestContextHolder中,可以直接调用

image-20210525195459466

Locale信息被放到了LocaleContextHolder中,因此可以用LocaleContextHolder.getLocale()直接获取到Locale信息。

小技巧:通过监听ServletRequestHandledEvent事件,可以在请求处理完成后,实现一些功能,比如日志的记录

@Component
public class ServletRequestHandlerEventListener {

 @EventListener(ServletRequestHandledEvent.class)
 public void servletListener(ServletRequestHandledEvent event) {
     System.out.println(event.getRequestUrl());
     System.out.println(event.getClientAddress());
     System.out.println(event.getServletName());
     System.out.println(event.getDescription());
 }
}

输出如下:

/favicon.ico
0:0:0:0:0:0:0:1
dispatcherServlet
url=[/favicon.ico]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcherServlet]; session=[4B300AAD654DDBCA03C2B3112B2F8C20]; user=[null]; time=[14ms]; status=[OK]

2.2 DispatchServlet

2.2.1 doService

DispatchServlet重写了doService方法

doService()对请求做了一些额外的处理,主要有如下几点:

  1. 如果是Include请求,则对request的Attribute做个快照备份,等doDispatch处理完之后(如果不是异步调用且未完成)进行还原

    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
        String attrName = (String) attrNames.nextElement();
        if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
          attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
      }
    }
    
  2. 设置了7个request的属性

    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    if (this.flashMapManager != null) {
      FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
      if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
      }
      request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
      request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }
    
    • WEB_APPLICATION_CONTEXT_ATTRIBUTE = org.springframework.web.servlet.DispatcherServlet.CONTEXT

      添加了springmvc容器AnnotationConfigServletWebServerApplicationContext,方便使用

    • LOCALE_RESOLVER_ATTRIBUTE = org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER

      添加了localeResolver

    • THEME相关的两个,添加了主题相关的解析器和来源

    • 后面3个属性与FlashMap相关,用来重定向时传递参数,之后详细分析

  3. 调用doDispath进一步处理请求

  4. include请求中request进行还原

2.2.2 doDispatch

doDispath中即使用到了前面提到的9大组件对请求进行处理,整体的处理逻辑还是比较清晰的,只是使用了九大组件对请求进行拆分。

先整体对该方法的逻辑有一个了解,对于一个请求的处理,主要分为以下几步

  1. 先判断是不是上传文件请求,如果是request转为专门的处理上传请求的MultipartHttpServletRequest
  2. 根据request找到对应处理请求的handler
  3. 根据handler的类型,找到对应的适配器HandlerAdapter
  4. 如果有拦截器,执行拦截器的preHandle()
  5. 使用适配器调用handler的逻辑,处理业务,得到ModelAndView对象
  6. 如果有拦截器,执行拦截器的postHandle()
  7. 对处理请求过程中的异常进行处理
  8. 如果ModelAndView需要渲染,则进行视图的渲染
  9. 如果有拦截器,执行拦截器的afterCompletion()
  10. 最后,如果是上传请求,做一些清理工作

小提示

  1. 处理请求的异常(即Handler中的异常)和渲染视图的异常是分开的
  2. 中间如果有任何一环发生异常,都会执行完拦截器的afterCompletion后再抛出

springmvc

三. 使用技巧

3.1 关于拦截器的使用

拦截器是springmvc提供给我们的不同于Filter的介入请求生命周期的方式

由上述的分析可知拦截器三个方法生效的具体时机

  1. preHandler(): 发生在Handler处理业务之前,并且如果返回false,会直接进入afterCompletion
  2. postHandler():发生在Handler处理业务之后,视图渲染之前
  3. afterCompletion(): 发生在视图渲染之后,如果中间有异常发生,也会调用

根据触发的时机,我们只要实现HandlerInterceptor接口,即可注入自定义的拦截器

如果是异步请求,可以实现AsyncHandlerInterceptor接口,此时就不会执行postHandlerafterConcurrentHandlingStarted了,而是会在异步执行handler的同时,执行afterConcurrentHandlingStarted

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

finally {
if (asyncManager.isConcurrentHandlingStarted()) {
 // Instead of postHandle and afterCompletion
 if (mappedHandler != null) {
   mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
 }
}
}

下面定义一个基于注解的拦截器的抽象类,方便业务使用

@Slf4j
public abstract class AbstractAnnotationInterceptor<T extends Annotation> extends HandlerInterceptorAdapter {

    private Class<T> clazz;

    public AbstractAnnotationInterceptor(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        if (!(handler instanceof HandlerMethod)) {
            return;
        }
        T anno = getAnnotationObj(handler, clazz);
        if (null != anno) {
            doPostHandle(request, response, (HandlerMethod) handler, anno, modelAndView);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
        if (!(handler instanceof HandlerMethod)) {
            return;
        }
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);
        T anno = getAnnotationObj(handler, clazz);
        if (null != anno) {
            doAfterCompletion(request, response, (HandlerMethod) handler, anno, exception);
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        T annotationObj = getAnnotationObj(handler, clazz);
        if (null == annotationObj) {
            return true;
        }
        return doPreHandle(request, response, (HandlerMethod) handler, annotationObj);
    }

    protected <A extends Annotation> A getAnnotationObj(Object handler, Class<A> clazz) {
        A annotationObj = null;
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            annotationObj = method.getBean().getClass().getAnnotation(clazz);
            if (annotationObj == null) {
                annotationObj = method.getMethodAnnotation(clazz);
            }
        } else {
            annotationObj = handler.getClass().getAnnotation(clazz);
        }
        return annotationObj;
    }

    protected void doPostHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, T anno, ModelAndView modelAndView) {

    }

    protected void doAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, T anno, Exception exception) {

    }

    protected boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handler, T anno) throws Exception {
        return true;
    }
}

3.2 关于过滤器的使用

  • 过滤器Filter是Servlet提供给我们的组件,用于对请求和响应进行处理。

  • Filter的触发时机是在servlet处理之前,和servlet处理完毕之后

  • Servlet API提供了一个Filter接口,编写的过滤器必须实现该接口。

  • 实现URL级别的权限访问控制,过滤敏感词汇,压缩响应信息等。

拦截过程:

  • 当客户端发生请求后,在HttpServletRequest 到达Servlet 之前,过滤器拦截客户的HttpServletRequest 。
  • 根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
  • 在过滤器中调用doFilter方法,对请求放行。请求到达Servlet后,对请求进行处理并产生HttpServletResponse发送给客户端。
  • 在HttpServletResponse 到达客户端之前,过滤器拦截HttpServletResponse 。
  • 根据需要检查HttpServletResponse ,可以修改HttpServletResponse 头和数据。
  • 最后,HttpServletResponse到达客户端。

Filter接口中有三个重要的方法。

  • init()方法:初始化参数,在创建Filter时自动调用。当我们需要设置初始化参数的时候,可以写到该方法中。
  • doFilter()方法:拦截到要执行的请求时,doFilter就会执行。这里面写我们对请求和响应的预处理。
  • destroy()方法:在销毁Filter时自动调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值