文章内容输出来源:拉钩教育Java高薪训练营# Spring MVC 高级框架
Spring MVC应用
简介
-
经典三层
表现层(接受请求,完成响应);业务层(service层,需要保证事务一致性);持久层(dao层,数据库交互);
-
MVC
表现层的设计模式
- Model(模型):数据模型封装数据,业务模型处理业务
- View(视图):展示数据,jsp或者html
- Controller(控制器):处理用户交互
Spring Web MVC 工作流程
开发过程
-
pom.xml
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.12.RELEASE</version> </dependency> -
web.xml
<servlet> <!--配置前端控制器DispatcherServlet--> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--springmvc的配置文件--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!--1. 带后缀 *.action, *.do--> <!--2. REST 风格 / 但是会拦截.html等静态资源(除了servlet和jsp之外的js,css,html等等),见url-pattern章节--> <!--3. 拦截所有,包括jsp, 不推荐/* --> <url-pattern>/</url-pattern> </servlet-mapping> -
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd "> <!-- 扫描controller --> <context:component-scan base-package="wenbin.carol.wu.controller" /> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 自动适配最合适的处理器、映射器--> <mvc:annotation-driven /> </beans> -
Controller.java
package wenbin.carol.wu.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import java.util.Date; @Controller @RequestMapping("/demo") public class DemoController { @RequestMapping("/handle01") public ModelAndView handle01(){ Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); // modelAndView.setViewName("/WEB-INF/jsp/success.jsp"); // 使用视图解析器,逻辑视图名 modelAndView.setViewName("success"); return modelAndView; } } -
success.jsp
<%-- Created by IntelliJ IDEA. User: Carol Date: 2020/10/20 Time: 1:53 PM To change this template use File | Settings | File Templates. --%> <%@ page isELIgnored="false" pageEncoding="utf-8" contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> 跳转成功,服务器时间:${date} </body> </html>
核心组件
三大件
- 处理器映射器(HandlerMapping, Map<Url, Handler(处理链Handler+Interceptor)>)
- 处理器适配器(HandlerAdapter, 根据配置调用handler,返回ModelAndView)
- 视图解析器(逻辑视图名 -> 物理视图地址)
其余组件
- HandlerExceptionResolver(异常解析器)
- RequestToViewNameTranslator (从请求中获得viewname)
- LocaleResolver(国际化相关)
- ThemeResolver(主题解析器)
- MultipartResolver(多部件解析器,文件上传请求)
- FlashMapManager(重定向的参数传递)
url-pattern配置及原理
如果直接使用/来作为url-pattern,会导致静态资源也被拦截
-
父 web.xml(tomcat中)配置了default servlet用来处理静态资源(但是被子web.xml覆盖);jsp则因为有JspServlet不受影响
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <!--省略--> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <!--省略--> </servlet> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping> -
处理方式
-
springmvc.xml添加以下标签,会在springmvc上下文中定义DefaultServletHttpRequestHandler,会检查进入DispatcherServlet的请求,如果是静态资源则返回给tomcat处理。但只能放在webapp文件夹下,存在局限。
<mvc:default-servlet-handler/> -
SpringMVC框架自己处理静态资源,存放位置<-->URL规则
<mvc:resources location="classpath:/" mapping="/resources/**"/> <mvc:resources location="/" mapping="/resources/**"/>
-
数据输出机制(Map, Model, ModelMap)
可以直接在handler方法上传入Map、Model和ModelMap参数,并向这些参数中保持数据(放入到请求域)
运行时的具体实现类均为org.springframework.validation.support.BindingAwareModelMap
@RequestMapping("/handle02")
public String handle02(ModelMap modelMap){
Date date = new Date();
modelMap.addAttribute("date", date);
return "success";
}
@RequestMapping("/handle03")
public String handle03(Model model){
Date date = new Date();
model.addAttribute("date", date);
return "success";
}
@RequestMapping("/handle04")
public String handle04(Map<String, Object> map){
Date date = new Date();
map.put("date", date);
return "success";
}
请求参数绑定
-
示例
// 原生servlet api支持 // url:/demo/handle02?id=1 public String handle02(HttpServletRequest request, HttpServletResponse response, HttpSession session) { String id = request.getParameter("id"); // 省略 } // 简单类型参数 // ⼋种基本数据类型及其包装类型,推荐使用包装类型,因为基础类型不可为null // 如果传递的参数名与型参不同,需要使用@RequestParam注解 // url:/demo/handle03?ids=1&flag=true public ModelAndView handle03(@RequestParam("ids") Integer id, Boolean flag) { // public ModelAndView handle06(@RequestParam("ids") Integer[] ids, Boolean flag) { // 省略 } // pojo类型参数 // 型参名无所谓,但是传递的参数名必须与Pojo属性名保持一致 // url:/demo/handle04?id=1&username=zhangsan public ModelAndView handle04(User user) { // 省略 } // pojo包装对象(pojo嵌套) // 属性名 + "." 的方式进一步锁定 // url:/demo/handle05?user.id=1&user.username=zhangsan public ModelAndView handle05(QueryVo queryVo) { // 省略 } // 日期类型参数 // url:/demo/handle06?birthday=2019-10-08 // 需要自定义一个SpringMVC的接口转换器,扩展实现接口,注册实现 public ModelAndView handle06(Date birthday) { // 省略 } -
自定义接口转换器
DateConverter.java
package wenbin.carol.wu.converter; import org.springframework.core.convert.converter.Converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateConverter implements Converter<String, Date> { @Override public Date convert(String s) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { return sdf.parse(s); } catch (ParseException e) { e.printStackTrace(); } return null; } }springmvc.xml
<!-- 自动适配最合适的处理器、映射器--> <mvc:annotation-driven conversion-service="conversionServiceBean"/> <!--注册自定义类型转换器--> <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="wenbin.carol.wu.converter.DateConverter" /> </set> </property> </bean>
RESTful风格请求支持
-
url请求的风格,不是标准和协议。可以根据这种风格设计请求的url。
-
所有东西都是资源,则资源对应唯一的uri标识(统一资源定位符)
-
根据请求方式不同,代表不同的操作
- GET 查询,获取资源
- POST 增加,新建资源
- PUT 更新
- DELETE 删除
-
直观体验:传递参数方式的变化,参数在URI中
-
@PathVariable 示例
html
<a href="/demo/handle/15">rest_get</a> <form method="post" action="/demo/handle"> <input type="text" name="username" /> <input type="submit" value="提交rest_post请求" /> </form> <form method="post" action="/demo/handle/15/lisi"> <input type="hidden" name="_method" value="put" /> <input type="submit" value="提交rest_put请求" /> </form> <form method="post" action="/demo/handle/15"> <input type="hidden" name="_method" value="delete" /> <input type="submit" value="提交rest_delete请求" /> </form>handle
@RequestMapping(value = "/handle/{id}", method = {RequestMethod.GET}) public ModelAndView handleGet(@PathVariable("id") Integer id) { // 省略 } @RequestMapping(value = "/handle", method = {RequestMethod.POST}) public ModelAndView handlePost(String username) { // 省略 } @RequestMapping(value = "/handle/{id}/{name}", method = {RequestMethod.PUT}) public ModelAndView handlePut(@PathVariable("id") Integer id, @PathVariable("name") String username) { // 省略 } @RequestMapping(value = "/handle/{id}", method = {RequestMethod.DELETE}) public ModelAndView handleDelete(@PathVariable("id") Integer id) { // 省略 }web.xml(添加过滤器,请求方式转换(针对PUT和DELETE))
<!--配置springmvc请求方式转换过滤器--> <!--会检查参数中是否有_method,有则进行转换--> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>web.xml (添加filter过滤器,处理POST请求乱码)
<!--springmvc提供的针对POST请求的编码过滤器--> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>tomcat下server.xml的配置(处理GET请求乱码)
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" />
Ajax Json交互
-
前端ajax发送json字符串,后端直接接收为pojo,使用注解 @RequestBody
-
后端返回pojo对象,前端直接接收为json对象或字符串,使用注解**@ResponseBody**
-
示例
pom.xml 引入jar包
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>handler
@RequestMapping("/handle20") // 添加注解之后,不再⾛视图解析器的流程,⽽是等同于response直接输出数据 // @ResponseBody public @ResponseBody User handle20(@RequestBody User user) { user.setName("张三"); return user; }js
$(function(){ $('#ajaxBtn').bind('click', function(){ // 发送ajax请求 $.ajax({ url:'/demo/handle20', type:'POST', data:'{"id":1,"name":"李四"}', contentType:'application/json;charset=utf-8', success: function(data){ // 拿到json对象 alert("success: " + data.name); } }) }); })
Spring MVC 高级技术
拦截器(Interceptor)使用
JavaEE标准组件,配置在web.xml中
-
Servlet:处理Request请求和Response响应
-
过滤器(filter):对Request请求起到过滤作用,在Servlet之前生效(编码过滤器,请求方式转换)
-
监听器(Listener):随着web应用启动而启动,只初始化一次,然后一直运行监听,随着Web应用停止而销毁
-
实现
org.springframework.web.context.ContextLoaderListener接口 -
示例一:spring容器启动
<!--使用监听器启动Spring的IoC容器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> -
示例二:监听特定事件,利用HttpSessionLisener统计在线人数等等
-
SpringMVC,Struts等表现层框架自己的组件,应配置在框架自己的配置文件中
-
拦截器(Interceptor)
-
仅拦截访问的控制器方法(Handler),不拦截jsp/html/css/js/image等
-
实现
org.springframework.web.servlet.HandlerInterceptor接口 -
拦截时机:1. 业务逻辑执行之前(preHandle);2. 业务逻辑执行之后,跳转页面之前(postHandle);3. 跳转页面之后(afterCompletion)

-
示例
-
单个拦截器
springmvc.xml
<mvc:interceptors> <!--拦截所有handler--> <!--<bean class="wenbin.carol.wu.interceptor.MyInterceptor01" />--> <mvc:interceptor> <mvc:mapping path="/**"/> <!--排除一部分url--> <!--<mvc:exclude-mapping path="/demo/**"/>--> <bean class="wenbin.carol.wu.interceptor.MyInterceptor01" /> </mvc:interceptor> </mvc:interceptors>Interceptor.java
package wenbin.carol.wu.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor01 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor01 preHandle"); // 是否放行,常用于权限校验 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor01 postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor01 afterCompletion"); } } -
多个拦截器
- preHandle:顺序执行 01 -> 02
- postHandle, afterCompletion:倒序执行 02 -> 01
-
-
处理Multipart形式的数据
-
文件上传,原生servlet是可以处理文件上传的,springmvc又进行了封装
-
要点
- 客户端
- form表单
- method=POST
- enctype=multipart
- file组件
- 服务端
- 重命名(唯一标识)
- 存储到磁盘(同一个目录不能文件过多,例如按照日期创建新的文件夹)
- 存储路径更新到数据库
- 文件上传解析器
- 客户端
-
示例
pom.xml引入jar包
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>前端
<fieldset> <form method="post" enctype="multipart/form-data" action="/demo/upload"> <input type="file" name="uploadFile"> <input type="submit" value="上传"> </form> </fieldset>后端
@RequestMapping("/upload") public ModelAndView upload(MultipartFile uploadFile, HttpSession session) throws IOException { String originalFilename = uploadFile.getOriginalFilename(); if (originalFilename != null) { String ext = originalFilename.substring(originalFilename.lastIndexOf('.') + 1); String newFilename = UUID.randomUUID().toString() + "." + ext; String realPath = session.getServletContext().getRealPath("/uploads"); String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); File folder = new File(realPath + "/" + datePath); if (!folder.exists()) { // mkdirs会创建路径上需要的所有文件夹 folder.mkdirs(); } uploadFile.transferTo(new File(folder, newFilename)); } Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date",date); modelAndView.setViewName("success"); return modelAndView; }springmvc配置,id固定为
multipartResolver<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--文件大小上限,单位字节,默认-1无限制--> <property name="maxUploadSize" value="5000000" /> </bean>
异常处理
-
实现
org.springframework.web.servlet.HandlerExceptionResolver接口 -
使用注解
@ExceptionHandler-
示例1
@Controller @RequestMapping("/demo") public class DemoController { // SpringMVC的异常处理机制(异常处理器) // 只对当前的类生效 @ExceptionHandler(ArithmeticException.class) public void handleException(ArithmeticException e, HttpServletResponse response) { try { response.getWriter().write(e.getMessage()); } catch (IOException ex) { ex.printStackTrace(); } } @RequestMapping("/handle01") public ModelAndView handle01(){ // 省略 } } -
示例2
// 捕获全局所有controller对象抛出的异常 @ControllerAdvice public class GlobalExceptionResolver { @ExceptionHandler(ArithmeticException.class) public ModelAndView handleException(ArithmeticException e, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("exception", e.getMessage()); modelAndView.setViewName("fail"); return modelAndView; } }
-
重定向数据传递
-
转发 v.s. 重定向
- 转发:url不会变,参数也不会丢失
- 重定向:url会变,参数也丢失
-
示例
@RequestMapping("/handleRedirect") public String handleRedirect(String name, RedirectAttributes redirectAttributes) { // 方法一:拼接,get请求参数长度有限制,且不安全 // return "redirect:handle30?name=" + name; // 或者(等价) // redirectAttributes.addAttribute("name", name); // return "redirect:handle30"; // 方法二:flash属性机制,向session中记录,跳转之后销毁 redirectAttributes.addFlashAttribute("name", name); return "redirect:handle30"; }
手写MVC框架
-
原理步骤
-
tomcat加载web.xml
配置前端控制器DispatcherServlet
加载配置文件springmvc.xml
-
包扫描,扫描注解
@Controller
@Service
@RequestMapping @AutoWired
-
Bean初始化及依赖注入
-
SpringMVC相关组件的初始化
建立url和method之间的映射关系(HandlerMapping)
-
等待请求进入,处理请求
-
-
项目代码
Spring MVC 源码深度剖析
DispatcherServlet 前端控制器
-
继承结构:DispatcherServlet <--- FrameworkServlet <--- HttpServletBean <--- HttpServlet
-
请求时序
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 {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 1. 获得处理当前请求的执行链 HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 2. 获取能够执行handler的适配器HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(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.
// 3. 适配器调用Handler执行,返回ModelAndView对象
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 NestedServletException("Handler dispatch failed", err);
}
// 4. 视图渲染跳转
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", 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&getHandlerAdaptor
组件初始化
org.springframework.context.support.AbstractApplicationContext#refresh
|
org.springframework.context.support.AbstractApplicationContext#finishRefresh
|
发布事件&监听
|
org.springframework.web.servlet.DispatcherServlet#onRefresh
|
org.springframework.web.servlet.DispatcherServlet#initStrategies
|
org.springframework.web.servlet.DispatcherServlet#initHandlerMappings(以及其他组件)
以initHandlerMappings为例
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 1. 可配参数,默认为true
if (this.detectAllHandlerMappings) {
// 寻找所有实现了HandlerMapping的bean
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 2. 按照固定id, "handerMapping",加载自定义bean
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// 3. 均没有则使用default,---> DispatcherServlet.properties
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
只有initMultipartResolver不同,必须使用固定的id
private void initMultipartResolver(ApplicationContext context) {
try {
// public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
// 省略
}
catch (NoSuchBeanDefinitionException ex) {
// 省略
}
}
handler方法执行细节
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
|
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
|
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
|
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
|
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
|
org.springframework.web.method.support.InvocableHandlerMethod#doInvoke
参数解析
视图渲染细节剖析
- 获取view
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
|
org.springframework.web.servlet.DispatcherServlet#render
|
org.springframework.web.servlet.DispatcherServlet#resolveViewName
|
org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName
|
org.springframework.web.servlet.view.UrlBasedViewResolver#createView
|
org.springframework.web.servlet.view.UrlBasedViewResolver#buildView
redirect:RedirectView
forward:InternalResourceView
- 渲染
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
|
org.springframework.web.servlet.DispatcherServlet#render
|
org.springframework.web.servlet.view.AbstractView#render
|
org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel(见下图)
|
org.apache.catalina.core.ApplicationDispatcher#forward
把modelmap中的数据暴露到request域中,model.add() --> jsp可以取出的原因