一,初探SpringMVC请求处理流程
SpringMVC相对比较简单,先使用一张图来了解SpringMVC的核心组件和大致处理流程。
从图中可以查看到:
DispatcherServlet是SpringMVC中的前端接收器,负责接受Request并将Request转发给相应的处理组件。
HandlerMapping是Spring中完成URL到controller映射的组件。DispatcherServlet接受Request然后从HandlerMapping查找处理Request的controller。
Controller处理Request,并返回ModelAndView对象。Controller是SpringMVC中负责处理Request的组件(类似于Status2中的Action)ModelAndView是封装结果试图的组件。
4,5,6是视图解析器,解析ModelAndView对象并返回对应的视图给客户端的过程。
容器初始化时会建立所有URL和Controller中方法的对应关系,保存到Handler Mapping中,用户请求时根据请求的 URL 快速定位到Controller 中的某个方法。在 Spring 中先将 URL 和Controller的对应关系保存到Map<url,Controller>中。Web容器启动时会通知Spring初始化容器(加载Bean的定义信息和初始化所有单例Bean),然后SpringMVC会遍历容器中的Bean,获取每一个Controller中的所有方法访问的URL,将URL和Controller保存到一个Map中。这样就可以根据请求快速定位到Controller,因为最终处理请求的是Controller中的方法,Map中只保留了URL和Controller的对应关系,所以要根据请求的URL进一步确认Controller中的方法。其原理就是拼接 Controller 的 URL (Controller 上@RequestMapping 的值)和方法的 URL (Method上@RequestMapping的值),与请求的URL进行匹配,找到匹配的方法。确定处理请求的方法后,接下来的任务就是参数绑定,把请求中的参数绑定到方法的形式参数上,这是整个请求处理过程中最复杂的一步。
二,SpringMVC九大组件
HandlerMapping:用来查找Handler,也就是处理器可以是类,也可以是方法。比如标注了@RequestMapping的每个方法都可以看成一个Handler。Handler负责实际的请求处理,在请求到达后,HandlerMapping的作用便是找到相应的请求器Handler和Interceptor。
HanderAdapter:是一个适配器,在SpringMVC中Handler可以是任意形式,只要可以处理请求便可。但是把请求交给Servlet的时候,由于Servlet的方法结构都是doService(HttpServletRequest,HttpServletResponse)形式,要让固定的Servlet处理方法调用Handler处理,这就是它所做的事情。
HandlerExceptionResolver:主要处理Handler产生的异常情况组件,具体来说,就是根据一场设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将ModelAndView渲染称页面。需要注意的是HandlerExceptionResolver只用于解析对请求处理阶段的异常,渲染阶段不归他管。
ViewResolver:视图解析器,SpringMVC配置文件中都会配置一个实现类进行视图解析。这个组件作用是将String类型的视图名和Locale,解析为view类型的视图,只有一个resolveViewName()方法,Controller层返回String类型的视图名viewName最终会在这里被解析称view,view用来渲染页面,它将程序返回的参数和数据填入模板中,生成HTML文件。ViewResolver主要做两件事 查找到要渲染的模板和所用的技术(技术就是指找到视图类型)。
RequestToViewNameTranslator:从请求中获取ViewName。因为ViewResolver根据ViewName查找View,但有的Handler处理后没有设置View和ViewName。就要用这个组件从请求中查找到ViewName。
LocalleResolver:ViewResolver组件中resolveViewName()方法必要的两个参数一个为视图名,一个为Locale。而Locale从哪里来,这个就是LocaleResolver组件要做的事情。LocalResolver用于从请求中解析除Locale。
ThemeResolver:ThemeResolver组件是用来解析主题的,主题就是样式,图片,及他们所形成的显示要过的集合。SpringMVC中一套主题对应一个properties文件,文件中放了与主题相关的所有资源。ThemeResolver负责从请求中解析出主题名,ThemeSource根据主题名来找到具体的主题,抽象也就是Theme可以通过Theme来获取主题和具体的资源。
MultipartResolver:封装普通请求,使其具有文件上传的功能。处理上传请求时,通过将普通的请求包装成一个MultipartHttpServletRequest来实现。可以通过getFile()获取到文件,如果多个文件可以调取getFileMap() 方法得到Ma
FlashMapManager: 先说FlashMap用于重定向时的参数传递,就是避免重复提交请求,可以处理完post请求后重定向到一个get请求,这个get请求可以用来显示订单详情之类的信息。但是重定向无法传递参数当然你可以拼接URL,此时可以用flashMap传递。而FlashMapManager就是管理FlashMap的。
三,源码分析
SpringMVC处理过程基本就分为:
1,ApplicationContext初始化时Map保存所有URL和Controller类的对应关系。
2,根据请求URL找到对应的Controller,并从Controller中找到处理请求的方法。
3,将Request参数绑定到方法的形参上,执行方法处理请求,并返回结果视图。
(一),初始化阶段
首先找到DispatcherServlet类,寻找init(),在其父类HttpServletBean中。
/**
init()初始话发方法
*/
@Override
public final void init() throws ServletException {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 定位资源
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 加载配置信息:web.xml
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;
}
}
// 真正完成初始化容器动作的代码 由子类进行实现
initServletBean();
}
initServletBean()方法主要就是初始化IOC容器,最终会调用refresh()方法,该方法已经讲解过,不在过多讲解。之后又调用了onRefresh()方法,该方法由DispatcherServlet来进行实现。
// 间接调用
protected void initStrategies(ApplicationContext context) {
// 初始化策略
// 多文件上传的组件
initMultipartResolver(context);
// 初始化本地语言环境
initLocaleResolver(context);
// 初始化模板处理器
initThemeResolver(context);
// 初始换handlMapping
initHandlerMappings(context);
// 初始化参数适配器
initHandlerAdapters(context);
// 初始化一场拦截器
initHandlerExceptionResolvers(context);
// 初始化视图预处理器
initRequestToViewNameTranslator(context);
// 初始化视图转换器
initViewResolvers(context);
// 初始化Flashmap管理器
initFlashMapManager(context);
}
initStrategies()方法完成SpringMVC的九大组件的初始化,接下来看URL与Colltroller的关系是如何建立的。HandlerMapping的子类AbstractDetectingUrlHandlerMapping实现了initApplicationContext()方法,下面查看源码:
// 建立当前ApplicationContext中所有Contrller和URL的对应关系
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
// 获取ApplicationContext中容器所有的Bean名称。
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// 遍历Bean并找到这些Bean对应的URL
for (String beanName : beanNames) {
// 查找bean上所有URL(controller与方法上的URL),由子类实现。
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// 保存urls和BeanName的对应关系,放入Map
// 该方法在父类AbstractUrlHandlerMapping中实现。
registerHandler(urls, beanName);
}
}
if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappingName() + " " + getHandlerMap());
}
else if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
}
}
determinUrlsForHander(String beanName)方法就是获取controller中所有的URL,不同子类有不同的实现,这是典型的模板模式。而项目中使用最多的就是用注解方式配置URL,所以BeanNameUrlHandlerMapping是AbstractDetectingUrlHandlerMapping的子类,用于处理注解形式的URL。
// 获取Cotroller中所有的URL
@Override
protected String[] determineUrlsForHandler(String beanName) {
List urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
此时HandlerMapping组件已经建立了所有URL和Controller的对应关系。
(二),运行调用阶段
运行调用是由请求触发的所以入口为DispatcherServlet的核心方法doService(),该方法中核心有doDispatch()实现:
// 中央控制器,控制请求的转发
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 {
// 1,检查是否文件上传的请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
/***
* 2,去到处理当前请求的Controller,即Handler。
* 这里并不是直接返回controller而是返回HanlderExceptionChain请求处理链对象。
* 封装了Handler和interceptor
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 3,获取请求处理的处理适配器HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理last-modified请求头
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4,返实际处理器处理请求,返回ModelAndView对象。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 对ModelAndView对象的处理。
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
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);
}
}
}
}
getHandler方法就是从HandlerMapping中找到URL和Controller的对应关系,也就是Map
从Map取得Controller后,经过拦截器的预处理方法,再通过反射调用方法获取ModelAndView结果视图,然后调用RequestMappingHandlerAdapter的Handle()中的核心代码handlerInternal(request,response,handler)实现。
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
整个过程就是拼接Controller的URL和方法的URL,与Request的URL进行匹配,找到匹配方法,然后找到处理请求的Controller中的方法,解析方法参数,利用反射调用该方法。
invokeAndHandler()最终要实现的目的是,完成请求参数和方法参数上的数据绑定,SpringMVC提供两种从请求参数到方法中参数的绑定方式:
通过注解进行绑定@RequestParam
通过参数名称进行绑定
通过注解绑定秩序在方法参数前面声明@RequestParam("name"),就可以将请求中参数name的值绑定到方法的该参数上。
通过参数名称进行绑定的前提必须获取方法中参数的名称,Java反射只提供了获取方法形参类型的方法,并没有提供获取参数名的方法,SpringMVC使用ASM框架读取字节码文件。
所以使用注解更快,无序asm读取字节码文件的操作。
1,先初始化完成九大组件的初始化操作。
2,DispatcherServlet接受到Request请求,遍历HandlerMapping集合,找到Spring容器中@Controller修饰的bean以及@RequestMapping修饰的方法,封装对象RequestMappingInfo对象。
3,找到对应的HandlerMapping并得到HandlerExceptionChain,内部包含了拦截器。使用内部封装的Handler遍历HandlerAdapter集合找到支持此Handler的HandlerAdapter,并使用处理适配器得到ModelAndView对象。如果发生异常会使用HandlerExceptionResolver策略解决异常。
4,使用ViewResolver来对ModelAndView进行解析得到view后进行返回。
(三),SpringMVC优化建议
1,Controller尽可能使用的单例模式,减少创建对象和回收对象的开销。这样可以减小创建对象和回收对象的开销。也就是说,如果Controller的类变量和实例变量可以以方法形参声明就尽量以方法形参声
明,不要以类变量和实例变量声明,这样可以避免线程安全问题。
2,处理请求的方法尽可能使用@RequestParam注解避免使用asm框架读取字节码文件。
3,源码没有缓存URL,每次都需要根据请求URL去匹配Controller中的方法URL,若进行缓存性能会不会提高?但是负责解析URL和方法对应关系的ServletHandlerMethodResolver是一个私有内部类,无法继承来对其增强,必须在代码后重新编译。当然若URL缓存起来要考虑线程安全问题。