SpringMVC
概述
三层架构
MVC是一种软件架构模式,三层模型(Controller,Service,Dao)更加关注业务逻辑组件的划分。MVC架构模式关注的是整个应用程序的层次关系和分离思想。MVC将应用分为三块:
- M:Model(模型),负责业务处理及数据的收集。
- V:View(视图),负责数据的展示
- C:Controller(控制器),负责调度。它是一个调度中心,它来决定什么时候调用Model来处理业务,什么时候调用View视图来展示数据。
前端浏览器发送请求给web服务器,web服务器中的Controller接收到用户的请求,Controller负责将前端提交的数据进行封装,然后Controller调用Model来处理业务,当Model处理完业务后会返回处理之后的数据给Controller,Controller再调用View来完成数据的展示,最终将结果响应给浏览器,浏览器进行渲染展示页面。

MVC作用
- 数据自动绑定,表单提交时可以自动将表单数据绑定到相应的JavaBean对象中,只需要在控制器方法的参数列表中声明该JavaBean对象即可,无需手动获取和赋值表单数据。而在纯粹的Servlet开发中,这些都是需要自己手动完成的。
- IoC容器:SpringMVC框架通过IoC容器管理对象,只需要在配置文件中进行相应的配置即可获取实例对象,而在Servlet开发中需要手动创建对象实例。
- 统一处理请求:SpringMVC框架提供了拦截器、异常处理器等统一处理请求的机制,并且可以灵活地配置这些处理器。而在Servlet开发中,需要自行编写过滤器、异常处理器等,增加了代码的复杂度和开发难度。
- 视图解析:SpringMVC框架提供了多种视图模板,如JSP、Freemarker、Velocity等,并且支持国际化、主题等特性。而在Servlet开发中需要手动处理视图层,增加了代码的复杂度。
mvc搭建

pom文件
<!--spring-webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 导入thymeleaf与spring5的整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<!--servlet-api-->
<!--provided表示有效范围在main和test中,因为Tomcat服务器中已经有了Servlet.api的包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理,该类为DispatcherServlet -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
<init-param>
<!-- contextConfigLocation为固定值 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
设置springMVC的核心控制器所能处理的请求的请求路径
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径
但是/不能匹配.jsp请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>springMVC.xml
<!-- 自动扫描包 -->
<context:component-scan base-package="com.atguigu.mvc.controller"/>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
<!--
处理静态资源,例如html、js、css、jpg
若只设置该标签,则只能访问静态资源,其他请求则无法访问
此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>
<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 处理响应中文内容乱码 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8" />
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>视图控制器
view-controller标签,在只需要进行简单跳转时可以使用
步骤
- 添加<mvc:view-controller>标签:为指定URL映射html页面
- 添加<mvc:annotation-driven>
- 有20+种功能
- 配置了<mvc:view-controller>标签之后会导致其他请求路径都失效,添加<mvc:annotation-driven>解决
xml<!--位于springmvc中,用于页面跳转--> <mvc:view-controller path="/testView" view-name="success"></mvc:view-controller> <mvc:annotation-driven></mvc:annotation-driven>
转发与重定向
转发
由服务器再发送一次资源跳转请求,浏览器一共发送了一次请求。不可实现跨域访问,可以访问WEB-INF目录下受保护的资源。
//通过Servlet的原生api
request.getRequestDispatcher("/index").forward(request, response);
//SpringMvc使用forward进行转发
@RequestMapping("/a")
public String toA(){
return "forward:/b";
}重定向
由服务器通知浏览器重新发送一次请求,浏览器一共发送了两次请求,可以完成内部资源的跳转,也可以完成跨域跳转,但无法访问WEB-INF目录下受保护的资源的。
//通过Servlet的原生api
response.sendRedirect("/webapproot/index");
//SpringMvc使用redirect进行重定向
@RequestMapping("/a")
public String toA(){
return "redirect:/b";
}加载静态资源
一个项目可能会包含大量的静态资源,比如:css、js、images等。由于DispatcherServlet的url-pattern配置的是“/”,之前我们说过,这个"/"代表的是除jsp请求之外的所有请求,也就是说访问应用中的静态资源,也会走DispatcherServlet,这会导致404错误,无法访问静态资源
方式一
由DefaultServlet加载静态资源【html、css、js等】到服务器
- Tomcat服务器中已经为我们提前配置好了,在CATALINA_HOME/conf/web.xml文件中
xml<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>只需要在springmvc.xml文件中启用这个默认的Servlet
xml<!-- 解决静态资源加载问题--> <mvc:default-servlet-handler></mvc:default-servlet-handler> <!-- 开启注解驱动,添加上述标签,会导致Controller无法正常使用,需要添加mvc:annotation-driven解决 --> <mvc:annotation-driven></mvc:annotation-driven>
方式二
访问静态资源,也可以在springmvc.xml文件中添加如下的配置:
<!-- 开启注解驱动 -->
<mvc:annotation-driven />
<!-- 配置静态资源处理,表示凡是请求路径是"/static/"开始的,都会去"/static/"目录下找该资源。 -->
<mvc:resources mapping="/static/**" location="/static/" />请求参数注解
@RequestMapping详解
为指定的类或方法设置相应URL
注解位置
书写在类上面:为当前类设置映射URL,不能单独使用,需要与方法上的@RequestMapping配合使用。最终方法的URL=类的URL+方法的URL
书写在方法上面:为当前方法设置映射URL,可以单独使用
@Controller
@RequestMapping("/test")
public class RequestMappingController {
//此时请求映射所映射的请求的请求路径为:/test/testRequestMapping
@RequestMapping("/testRequestMapping")
public String testRequestMapping(){
return "success";
}
}衍生注解:
对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
- 处理get请求的映射-->@GetMapping
- 处理post请求的映射-->@PostMapping
- 处理put请求的映射-->@PutMapping
- 处理delete请求的映射-->@DeleteMapping
注解属性
value属性
String[]类型,用于设置URL信息,能够匹配多个请求地址所对应的请求,注意:若是仅有一个value属性,可以省略value
@RequestMapping(
value = {"/testRequestMapping", "/test"}//两个地址都能访问
)
public String testRequestMapping(){
return "success";//跳转到success页面
}path属性
String[]类型,与value属性作用一致。
method属性
RequestMethod[]类型,为当前URL【类或方法】设置请求方式。默认情况下所有请求方式均支持,如请求方式不支持,会报如下错误:405 Request method 'GET' not supported
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
@RequestMapping(
value = {"/testRequestMapping", "/test"},
method = {RequestMethod.GET, RequestMethod.POST}//可以有多个值,接受GET,POST请求
)
public String testRequestMapping(){
return "success";
}params
String[]类型,获取当前URL的指定请求参数。若URL中未携带指定指定参数,会报如下错误
400 Parameter conditions "lastName" not met for actual request parameters:
- "param":要求请求映射所匹配的请求必须携带param请求参数
- "!param":要求请求映射所匹配的请求必须不能携带param请求参数
- "param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value
- "param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value
@RequestMapping(
value = {"/testRequestMapping", "/test"}
,method = {RequestMethod.GET, RequestMethod.POST}
,params = {"username","password!=123456"}
)
public String testRequestMapping(){
return "success";
}headers
String[]类型,获取当前URL的指定请求头信息。若URL中未携带指定请求头信息,会报: 404:请求资源未找到
"header":要求请求映射所匹配的请求必须携带header请求头信息
"!header":要求请求映射所匹配的请求必须不能携带header请求头信息
"header=value":要求请求映射所匹配的请求必须携带header请求头信息且header=value
"header!=value":要求请求映射所匹配的请求必须携带header请求头信息且header!=value
@RequestMapping(value = {"/saveEmp","/insertEmp"},
method = RequestMethod.GET,
params = "lastName=lisi",
headers = "User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36")
public String saveEmp(){
System.out.println("添加员工信息!!!!");
return SUCCESS;
}@PathVariable
获取请求路径中的参数
@RequestMapping(value="/testRESTful/{id}/{username}/{age}")
public String testRESTful(
@PathVariable("id")
int id,
@PathVariable("username")
String username,
@PathVariable("age")
int age){
System.out.println(id + "," + username + "," + age);
return "testRESTful";
}@RequestParam
作用:将请求参数与方法上的形参映射。
value:String类型,用于设置需要入参的参数名
name:String类型,与value属性作用一致
required:Boolean类型,设置当前参数,是否必须入参
true【默认值】:表示当前参数必须入参,如未入参会报如下错误
400 Required String parameter 'sName' is not present- false:表示当前参数不必须入参,如未入参,装配null值
defaultValue:String类型,当装配数值为null时,指定当前defaultValue默认值
@PostMapping(value = "/register")
public String register(
@RequestParam(value="username")
String a,
@RequestParam(value="password")
String b) {
System.out.println(a);
System.out.println(b);
return "success";
}
//若方法形参的名字和提交数据时的name相同,则 @RequestParam可以省略,与入参参数名一致的参数,自动入参【自动类型转换】,不一致赋值null
//Spring6+版本,你需要在pom.xml文件中指定编译参数'-parameter',配置如下:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
//使用POJO类/JavaBean来接收请求参数使用POJO类/JavaBean来接收请求参数,POJO类的属性名`必须和`请求参数的参数名`保持一致。@RequestHeader
该注解的作用是:将请求头信息映射到方法的形参上。
value:String类型,用于设置需要获取请求头名称
name:String类型,与value属性作用一致
required:Boolean类型,设置当前当前请求头,是否必须入参
true:默认值,设置当前请求头为必须入参,如未入参会报如下错误
400 Required String parameter 'sName' is not present- false:表示当前参数不必须入参,如未入参,装配null值
defaultValue:String类型,当装配数值为null时,指定当前defaultValue默认值
@RequestMapping(value = "/testGetHeader")
public String testGetHeader(@RequestHeader("Accept-Language")String al,
@RequestHeader("Referer") String ref){
System.out.println("al = " + al);
System.out.println("ref = " + ref);
return SUCCESS;
}@CookieValue
该注解的作用:将请求提交的Cookie数据映射到方法形参上
value:String类型,用于设置需要获取Cookie名称
name:String类型,与value属性作用一致
required:boolean类型,设置当前Cookie是否为必须入参
true:设置当前Cookie为必须入参,如未入参会报如下错误
400 Required String parameter 'sName' is not presentfalse:表示当前Cookie不必须入参,如未入参,装配null值
defaultValue:String类型,当装配数值为null时,指定当前defaultValue默认值
/**
* 获取Cookie
* @return
*/
@RequestMapping("/getCookie")
public String getCookie(@CookieValue("JSESSIONID")String cookieValue){
System.out.println("cookieValue = " + cookieValue);
return SUCCESS;
}Ant 风格路径(了解):
常用通配符
a) ?:匹配一个字符
b) *:匹配任意字符
c) **:匹配多层路径
@RequestMapping("/testAnt/**")//表示testAnt下的任意路径均可以访问该方法
public String testAnt(){
System.out.println("==>testAnt!!!");
return SUCCESS;
}
//注意:** 通配符在使用时,左右不能出现字符,只能是 /,以下即为无效的路径
@RequestMapping("/**/testValueAnt")RESTful风格CRUD
请求方式
- GET:获取资源,只允许读取数据,不影响数据的状态和功能。使用 URL 中传递参数或者在 HTTP 请求的头部使用参数,服务器返回请求的资源。
- POST:向服务器提交资源,可能还会改变数据的状态和功能。通过表单等方式提交请求体,服务器接收请求体后,进行数据处理。
- PUT:更新资源,用于更新指定的资源上所有可编辑内容。通过请求体发送需要被更新的全部内容,服务器接收数据后,将被更新的资源进行替换或修改。
- DELETE:删除资源,用于删除指定的资源。将要被删除的资源标识符放在 URL 中或请求体中。
- HEAD:请求服务器返回资源的头部,与 GET 命令类似,但是所有返回的信息都是头部信息,不能包含数据体。主要用于资源检测和缓存控制。
- PATCH:部分更改请求。当被请求的资源是可被更改的资源时,请求服务器对该资源进行部分更新,即每次更新一部分。
- OPTIONS:请求获得服务器支持的请求方法类型,以及支持的请求头标志。“OPTIONS *”则返回支持全部方法类型的服务器标志。
- TRACE:服务器响应输出客户端的 HTTP 请求,主要用于调试和测试。
- CONNECT:建立网络连接,通常用于加密 SSL/TLS 连接。
风格对比
传统风格CRUD
- 功能 URL 请求方式
- 增 /insertEmp POST
- 删 /deleteEmp?empId=1001 GET
- 改 /updateEmp POST
- 查 /selectEmp?empId=1001 GET
RESTful风格CRUD
- 功能 URL 请求方式
- 增 /emp POST
- 删 /emp/1001 DELETE
- 改 /emp PUT
- 查 /emp/1001 GET
GET与POST请求区别
get没有请求体,请求发送数据的时候,数据拼接在URI的后面,以?分割URL和传输数据,参数之间以&相连。POST请求有请求体,数据可以放到请求体(也可以放到URL中)。
get请求只能发送普通的字符串。并且发送的字符串长度有限制;post请求可以发送任何类型的数据,理论上没有长度限制。
get请求支持缓存。 也就是说当第二次发送get请求时,会走浏览器上次的缓存结果,不再真正的请求服务器。post请求不支持缓存。每一次发送post请求都会真正的走服务器。
PUT&DELETE提交方式
注册过滤器HiddenHttpMethodFilter
xml<!--在web.xml中创建restful的过滤器,用于将post请求转化为delete和put--> <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>设置表单的提交方式为POST
设置参数:_method=PUT或_method=DELETE
xml<form th:action="@{/emp}" method="post"> <input type="hidden" name="_method" value="PUT"> <input type="submit" value="修改信息"> </form> <form th:action="@{/emp/1001}" method="post"> <input type="hidden" name="_method" value="DELETE"> <input type="submit" value="删除信息"> </form>
HiddenHttpMethodFilter源码:
java//这里写死的参数,所以参数设置时,参数名需要为这个 public static final String DEFAULT_METHOD_PARAM = "_method"; private String methodParam = DEFAULT_METHOD_PARAM; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; //如果不是Post请求,此处无法进入 if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HttpMethodRequestWrapper(request, method); } } } filterChain.doFilter(requestToUse, response); } /** * Simple {@link HttpServletRequest} wrapper that returns the supplied method for * {@link HttpServletRequest#getMethod()}. */ private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper(HttpServletRequest request, String method) { super(request); this.method = method; } //传过来的_method此处使用 @Override public String getMethod() { return this.method; } }
响应数据
无论方法返回是ModelAndView还是String,最终SpringMVC底层,均会封装为ModelAndView对象
ModelAndView:ModelAndView是模型数据与视图对象的集成对象,可以作为响应数据的返回值
ModelAndView源码
javapublic class ModelAndView { //View instance or view name String. //view代表view对象或viewName【建议使用viewName】 @Nullable private Object view; //Model Map. //ModelMap集成LinkedHashMap,存储数据 @Nullable private ModelMap model; //设置视图名称 public void setViewName(@Nullable String viewName) { this.view = viewName; } 图名称 @Nullable public String getViewName() { return (this.view instanceof String ? (String) this.view : null); } //获取数据,返回Map【无序,model可以为null】 @Nullable protected Map<String, Object> getModelInternal() { return this.model; } //获取数据,返回 ModelMap【有序】,为空会new一个 public ModelMap getModelMap() { if (this.model == null) { this.model = new ModelMap(); } return this.model; } //获取数据,返回Map【无序】 public Map<String, Object> getModel() { return getModelMap(); } //设置数据 public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) { getModelMap().addAttribute(attributeName, attributeValue); return this; } }示例代码
java@GetMapping("/testMvResponsedata") public ModelAndView testMvResponsedata(){ ModelAndView mv = new ModelAndView(); //设置逻辑视图名 mv.setViewName("response_success"); //设置数据【将数据共享到请求域中】 mv.addObject("stuName","zhouxu"); return mv; }
Model、ModelMap、Map:将其作为方法入参,处理响应数据
示例代码
java@GetMapping("/testMapResponsedata") public String testMapResponsedata(Map<String,Object> map /* Model model ModelMap modelMap*/){ //这样就将数据添加到了域中 map.put("stuName","zhangsan"); //model.addAttribute("stuName","lisi"); //使用modelMap可以添加map集合 //modelMap.addAttributes("stuName",map); //进行页面跳转 return "response_success"; }
SpringMVC其他域:SpringMVC封装数据,默认使用request域对象
会话域的使用
方式一:通过Servlet的原装API进行调用
java/** * 测试响应数据【其他域对象】 * @return */ @GetMapping("/testScopeResponsedata") public String testScopeResponsedata(HttpSession session){ session.setAttribute("stuName","xinlai"); return "response_success"; }方式二:通过将request域中数据同步到session中
java@Controller @SessionAttributes(value = "stuName") //将request域中stuName数据,同步到session域中 public class TestResponseData { /** * 使用ModelAndView处理响应数据 * @return */ @GetMapping("/testMvResponsedata") public ModelAndView testMvResponsedata(){ ModelAndView mv = new ModelAndView(); //设置逻辑视图名 mv.setViewName("response_success"); //设置数据【将数据共享到域中(request\session\servletContext)】 mv.addObject("stuName","zhouxu"); return mv; } }
应用域的使用:
java@RequestMapping("/testApplication") public String testApplication(HttpSession session){ ServletContext application = session.getServletContext(); application.setAttribute("testApplicationScope", "hello,application"); return "success"; }
处理请求响应乱码
get请求乱码
对于低版本Tomcat:CATALINA_HOME/config/server.xml文件,找到其中配置端口号的标签<Connector>,在该标签中添加 URIEncoding="UTF-8"。
对于高版本的Tomcat服务器来说,是不需要设置的,例如Tomcat10,
POST请求乱码
实现一个过滤器,设置编码:request.setCharacterEncoding("UTF-8");也可以使用SpringMVC提供的过滤器CharacterEncodingFilter(对于高版本的Tomcat10服务器来说,针对请求体中的字符编码也是配置好的,默认也是采用了UTF-8)
- 注册CharacterEncodingFilter,其必须是第一Filter位置
- 为CharacterEncodingFilter中属性encoding赋值
- 为CharacterEncodingFilter中属性forceRequestEncoding赋值
<!--必须是第一过滤器位置,该配置在web.xml-->
<filter>
<!--注册CharacterEncodingFilter-->
<filter-name>CharacterEncodingFilter</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>
<!--调用setForceRequestEncoding方法,将请求域响应均设为true-->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>CharacterEncodingFilter源码:
public class CharacterEncodingFilter extends OncePerRequestFilter {
//需要设置字符集
@Nullable
private String encoding;
//true:处理请乱码
private boolean forceRequestEncoding = false;
//true:处理响应乱码
private boolean forceResponseEncoding = false;
public String getEncoding() {
return this.encoding;
}
public boolean isForceRequestEncoding() {
return this.forceRequestEncoding;
}
public void setForceResponseEncoding(boolean forceResponseEncoding) {
this.forceResponseEncoding = forceResponseEncoding;
}
public void setForceEncoding(boolean forceEncoding) {
this.forceRequestEncoding = forceEncoding;
this.forceResponseEncoding = forceEncoding;
}
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String encoding = getEncoding();
if (encoding != null) {
if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
}Servlet
处理请求和响应接口
// 获取请求对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 强制转换
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
// 请求对象
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取响应对象
HttpServletResponse response = servletRequestAttributes.getResponse();HttpServletRequest
该接口是ServletRequest接口的子接口,封装了HTTP请求的相关信息(请求报文中的所有信息都被封装到request对象中)
获得请求头信息
request.getHeader(String key);
获得url的路径信息
request.getContextPath();//获得上下文路径 ★
request.getServerName();
request.getServerPort();
获得请求方式//get,post等等
request.getMethod();
获得请求参数 ★
String request.getParameter(String key); 根据key值返回一个value
String[] request.getParameterValues(String key); 根据key值返回多个value
Map<String,String[]> request.getParameterMap(); 将整个表单的所有数据都放在map集合内
/*Map<String, String[]> parameterMap = request.getParameterMap();
Set<String> strings = parameterMap.keySet();
for (String string : strings) {
System.out.println("key = " + string);
String[] strings1 = parameterMap.get(string);
for (String s : strings1) {
System.out.println("s = " + s);
}
}*/
转发
a. 获得转发器对象
RequestDispatcher requestDispatcher = request.getRequestDispatcher(目标路径);
b. 进行转发操作(将request和response需要传递过去)
requestDispatcher.forward(request,response);
原理:因为转发将request对象传递过去了,所以SecondServlet能获得到请求中的请求参数
作为域对象共享数据
应用域(ServletContext):数据共享范围是整个web应用
请求域(HttpServletRequest) :数据共享的范围是本次请求,当响应结束了,请求也结束。即页面-服务器-……-页面均可以使用。
setAttribute(String key,Object value);
getAttribute(String key)
removeAttribute(String key)HttpServletResponse
通过输出流将响应数据输出给客户端
PrintWriter writer = response.getWriter();
writer.write("<h1>success</h1>");//也可以按照HTML写一个页面传过去
可以设置响应的乱码(添加响应头的方式)
response.addHeader("Content-Type","text/html;charset=utf-8");
简写:response.setContentType("text/html;charset=utf-8");
重定向:页面跳转的主要手段之一
a. 重定向至另一个Servlet
response.sendRedirect("second");
b. 重定向至页面
response.sendRedirect("admin.html");三个域对象
request
接口名:HttpServletRequest request对象代表了一次请求。一次请求一个request。 使用请求域的业务场景:在A资源中通过转发的方式跳转到B资源,因为是转发,因此从A到B是一次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到request域中。
无论你使用哪种方式,最终都要返回一个ModelAndView对象。
原生Servlet API方式
//在创建方法时写参数HttpServletRequest,会自动传入
@Controller
public class RequestScopeTestController {
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
// 向request域中存储数据
request.setAttribute("testRequestScope", "在SpringMVC中使用原生Servlet API实现request域数据共享");
return "view";
}
}使用Model接口
@RequestMapping("/testModel")
public String testModel(Model model){
// 向request域中存储数据
model.addAttribute("testRequestScope", "在SpringMVC中使用Model接口实现request域数据共享");
return "view";
}使用Map接口
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
// 向request域中存储数据
map.put("testRequestScope", "在SpringMVC中使用Map接口实现request域数据共享");
return "view";
}使用ModelMap类
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
// 向request域中存储数据
modelMap.addAttribute("testRequestScope", "在SpringMVC中使用ModelMap实现request域数据共享");
return "view";
}Model、Map还是ModelMap,底层实例化的对象都是:BindingAwareModelMap。BindingAwareModelMap继承了ModelMap,而ModelMap又实现了Map接口
使用ModelAndView类
ModelAndView。这个类的实例封装了Model和View。也就是说这个类既封装业务处理之后的数据,也体现了跳转到哪个视图。使用它也可以完成request域数据共享。
@RequestMapping("/testModelAndView")
//方法的返回值类型不是String,而是ModelAndView对象。
public ModelAndView testModelAndView(){
// 创建“模型与视图对象”。ModelAndView不是出现在方法的参数位置,而是在方法体中new的。
ModelAndView modelAndView = new ModelAndView();
// 绑定数据
modelAndView.addObject("testRequestScope", "在SpringMVC中使用ModelAndView实现request域数据共享");
// 绑定视图
modelAndView.setViewName("view");
// 返回
return modelAndView;
}session
接口名:HttpSession session对象代表了一次会话。从打开浏览器开始访问,到最终浏览器关闭,这是一次完整的会话。每个会话session对象都对应一个JSESSIONID,而JSESSIONID生成后以cookie的方式存储在浏览器客户端。浏览器关闭,JSESSIONID失效,会话结束。
使用会话域的业务场景:
- 在A资源中通过重定向的方式跳转到B资源,因为是重定向,因此从A到B是两次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到session域中。
- 登录成功后保存用户的登录状态。
使用原生Servlet API
//接受参数HttpSession,SpringMvc会自动传入这个参数
@Controller
public class SessionScopeTestController {
@RequestMapping("/testSessionScope1")
public String testServletAPI(HttpSession session) {
// 向会话域中存储数据
session.setAttribute("testSessionScope1", "使用原生Servlet API实现session域共享数据");
return "view";
}
}使用SessionAttributes注解
SessionAttributes注解使用在Controller类上。标注了当key是 x 或者 y 时,数据将被存储到会话session中。如果没有 SessionAttributes注解,默认存储到request域中。
@Controller
@SessionAttributes(value = {"x", "y"})
public class SessionScopeTestController {
@RequestMapping("/testSessionScope2")
public String testSessionAttributes(ModelMap modelMap){
// 向session域中存储数据
modelMap.addAttribute("x", "我是埃克斯");
modelMap.addAttribute("y", "我是歪");
return "view";
}
}application
接口名:ServletContext application对象代表了整个web应用,服务器启动时创建,服务器关闭时销毁。对于一个web应用来说,application对象只有一个。 使用应用域的业务场景:记录网站的在线人数。
Servlet API
@Controller
public class ApplicationScopeTestController {
@RequestMapping("/testApplicationScope")
public String testApplicationScope(HttpServletRequest request){
// 获取ServletContext对象
ServletContext application = request.getServletContext();
// 向应用域中存储数据
application.setAttribute("applicationScope", "我是应用域当中的一条数据");
return "view";
}
}SpringMvc消息转换器
HTTP消息
HTTP消息其实就是HTTP协议。HTTP协议包括请求协议和响应协议。
POST请求协议
POST /springmvc/user/login HTTP/1.1 --请求行
Content-Type: application/x-www-form-urlencoded --请求头
Content-Length: 32
Host: www.example.com
User-Agent: Mozilla/5.0
Connection: Keep-Alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
--空白行
username=admin&password=1234 --请求体响应协议
HTTP/1.1 200 OK --状态行
Date: Thu, 01 Jul 2021 06:35:45 GMT --响应头
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Connection: keep-alive
Server: Apache/2.4.43 (Win64) OpenSSL/1.1.1g
--空白行
<!DOCTYPE html> --响应体
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>HttpMessageConverter
HttpMessageConverter是Spring MVC中非常重要的一个接口。翻译为:HTTP消息转换器。该接口下提供了很多实现类,不同的实现类有不同的转换方式。

FormHttpMessageConverter是负责将请求协议转换为Java对象的。StringHttpMessageConverter是负责将Java对象转换为响应协议的。MappingJackson2HttpMessageConverter将java对象转换为json格式字符串

@RequestBody
获取请求体(注意get方法无请求体),主要用于将Ajax异步传入的JOSN数据进行转化为对应类型。
- 在使用了axios发送异步请求之后,浏览器发送到服务器的请求参数有两种格式:
key=value&key=value…,此时的请求参数可以通过request.getParameter()获取{key:value,key:value…},此时无法通过request.getParameter()获取,通过@RequestBody进行转换为java对象
//将json格式的数据转换为string类型
RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}
//将json格式的数据转换为map类型
RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Map<String,Object> requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}
//将json格式的数据转换为对象类型
RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody User user){
System.out.println("requestBody:"+requestBody);
return "success";
}
//将字符串格式的数据转换为对象类型
//username=zhangsan&password=1234&email=zhangsan@powernode.com=>User
RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody User user){
System.out.println("requestBody:"+requestBody);
return "success";
}@ResponseBody
用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器,而不是进行跳转页面。也可标注类上,标明该类所有方法返回值均响应到浏览器。
//结果:浏览器页面显示success
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
@RestController
@RestController =@Controller+@ResponseBody。标注在类上即可,被它标注的Controller中所有的方法上都会自动标注 @ResponseBody
@ModelAttribute
使用表单提交数据时,可以将其转化为对象
RequestEntity
继承了HttpEntity,是封装请求报文的一种类型,封装了整个请求协议。可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息
@RequestMapping("/testRequestEntity")
//填写后会自动传入RequestEntity参数
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}ResponseEntity
封装响应协议,包括:状态行、响应头、响应体,可用于文件下载等。
@Controller
public class UserController {
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
} else {
return ResponseEntity.ok(user);
}
}
}处理JSON
<!--导入依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
<!--开启注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串-->
<mvc:annotation-driven />文件上传和下载
文件下载
使用ResponseEntity实现下载文件的功能,将返回值类型设置为ResponseEntity<byte[]>
@RequestMapping("/downloadPicture")
//filename为前端传过来的文件名
public ResponseEntity<byte[]> download(HttpSession session,String filename) throws IOException {
//获取真实路径
String realPath = session.getServletContext().getRealPath("/static/" + filename);
//获取文件流,并且通过真实路径将数据读取到内存中(即byte数组)
FileInputStream fileInputStream=new FileInputStream(realPath);
byte[] bytes=new byte[fileInputStream.available()];
fileInputStream.read(bytes);
//返回ResponseEntity对象,需要三个参数:下载数据, ,状态码
HttpHeaders headers=new HttpHeaders();
//设置要下载的文件名字,及文件格式为附件格式,通知服务器下载当前资源而不是打开
headers.add("Content-Disposition", "attachment;filename="+filename);
//处理中文件名问题
headers.setContentDispositionFormData("attachment",new String(filename.getBytes("utf-8"),"ISO-8859-1"));
fileInputStream.close;
//返回下载文件
return new ResponseEntity(bytes,headers,HttpStatus.OK);
}文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype="multipart/form-data"。SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
a>添加依赖:
xml<!-- SpringMVC6版本,不需要添加以下依赖 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>b>在SpringMVC的配置文件中添加配置:
xml<!--在SpringMVC的配置文件中添加配置: 必须通过文件解析器的解析才能将文件转换为MultipartFile对象 id必须为multipartResolver--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"></property> <!--设总上传的文件大小--> <property name="maxUploadSize" value="1024"></property> <!--设置上传单个文件的大小,单位是字节--> <property name="maxUploadSizePerFile" value="1024"></property> </bean>c>html页面
xml<form th:action="@{/upload}" method="post" enctype="multipart/form-data"><!--即encoding type--> 上传文件<input type="file" name="uploadFile"><br> <input type="submit" value="提交"> </form>d>控制器方法:
java@PostMapping("upload") //此处MultipartFile的属性值,是前端设置对应的名字 public String upload(MultipartFile uploadFile,HttpSession Session) throws IOException { //UUID:获得32位16进制的随机数,不重复。也可以使用时间戳解决。拼接上uploadFile.getOriginalFilename()[图片名 字],避免出现重名 String originalFilename = UUID.randomUUID().toString()+uploadFile.getOriginalFilename(); //设置文件夹,如果没有就创建 File file=new File("D:\\图片temp"); if(!file.exists()){ file.mkdir(); } //获取文件夹的绝对路径+分隔符【不同平台下会自动对应】+文件名。通过MultipartFile对象上传数据 uploadFile.transferTo(new File(file.getAbsolutePath()+File.separator+originalFilename)); //跳转页面 return "index"; }
全局异常处理器
xml配置
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--用来指定出现异常后,跳转的视图-->
<prop key="java.lang.Exception">tip</prop>
</props>
</property>
<!--将异常信息存储到request域,value属性用来指定存储时的key。-->
<property name="exceptionAttribute" value="e"/>
</bean>注解配置
@RestControllerAdvice
@Slf4j
public class HandleException {
//全局异常处理
@ExceptionHandler(Exception.class)
public Result GlobalHandle(Exception exception){
log.error(String.valueOf(exception));
return Result.error().message("您的网络存在异常,请稍后再试");
}
}拦截器
拦截器与过滤器
Spring MVC的拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理。
- 过滤器是基于函数回调的,拦截器 则是基于Java的动态代理实现(当前请求处理器对象(controller)和对应拦截器(interceptors)组成HandlerAdapter对象)
- 过滤器实现的是 javax.servlet.Filter 接口,基于Servlet容器(如Tomcat等),导致它只能在web程序中使用。拦截器它是一个Spring组件,并由Spring容器管理,是可以单独使用的。
- 先经过过滤器——>DispatchServlet——>拦截器——>Controller

拦截器创建
实现
org.springframework.web.servlet.HandlerInterceptor接口preHandle:处理器方法调用之前执行
- 只有该方法有返回值,返回值是布尔类型,true放行,false拦截。
postHandle:处理器方法调用之后执行
afterCompletion:渲染完成后执行
在SpringMvc中进行配置
xml<bean class="com.atguigu.interceptor.FirstInterceptor"></bean> <ref bean="firstInterceptor"></ref> <!--需要先使用component注解装配该容器,Spring的内容在SpringMVC中可以使用--> <!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/test"/> 模糊匹配路径 <!--<mvc:mapping path="/**/auth/**"/>--> 排除包含的路径 <!--<mvc:exclude-mapping path="/testRequestEntity"/>--> <bean class="com.atguigu.interceptor.LoginInterceptor" /> 引用已有的bean: <!--<ref bean="httpSession"></ref>--> </mvc:interceptor> </mvc:interceptors> <!-- 以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求 -->拦截器的执行顺序:

- preHandle()会按照SpringMVC配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行
- 只要有一个拦截器
preHandle返回false,任何postHandle都不执行。但返回false的拦截器的前面的拦截器按照逆序执行afterCompletion。
javapublic class HandlerExecutionChain { // 顺序执行 preHandle 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)) { // 如果其中一个拦截器preHandle返回false // 将该拦截器前面的拦截器按照逆序执行所有的afterCompletion triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } return true; } // 逆序执行 postHanle void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { for (int i = this.interceptorList.size() - 1; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); interceptor.postHandle(request, response, this.handler, mv); } } // 逆序执行 afterCompletion void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } } }
SpringMVC全流程

SpringMVC组件:
DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求HandlerMapping:处理器映射器,不需要工程师开发,由框架提供
作用:根据请求的url、method等信息查找Handler,即控制器方法(@RequestMapping("empInfor")),可以获HandleExecutionChain对象HandleExecutionChain:请求处理器执行链对象
作用:通过其可以获得HandlerAdapter对象。有当前请求处理器对象(controller)和对应拦截器(interceptors)组成Handler:处理器,需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行ViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:将逻辑视图名转换为物理视图名,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView。编写类实现ViewResolver接口,实现resolveViewName方法,在该方法中完成逻辑视图名转换为物理视图名,并返回View对象。View:视图
作用:负责将模型数据Model渲染为视图格式(HTML代码),并最终将生成的视图(HTML代码)输出到客户端。(它负责将模板语言转换成HTML代码)。编写类实现View接口,实现render方法,在该方法中将模板语言转换成HTML代码,并将HTML代码响应到浏览器。ViewResolverRegistry:视图解析器注册器
负责在web容器(Tomcat)启动的时候,完成视图解析器的注册。如果有多个视图解析器,会将视图解析器对象按照order的配置放入List集合。
SpringMVC的执行流程:
用户向服务器发送请求,携带URL,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射,从而加载Controller:
Controller类中不存在对应URI
- 判断是否配置了mvc:default-servlet-handler
- 没配置,则控制台报映射查找不到,客户端展示404错误
- 有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误,且URL不可用
- 判断是否配置了mvc:default-servlet-handler
Controller类中存在对应URI
访问到目标资源后:根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象
此时将开始执行拦截器的postHandle(...)方法【逆向】。
根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
将渲染结果返回给客户端
javapublic class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // 根据请求对象request获取 // 这个对象是在每次发送请求时都创建一个,是请求级别的 // 该对象中描述了本次请求应该执行的拦截器是哪些,顺序是怎样的,要执行的处理器是哪个 HandlerExecutionChain mappedHandler = getHandler(processedRequest); // 根据处理器获取处理器适配器。(底层使用了适配器模式) // HandlerAdapter在web服务器启动的时候就创建好了。(启动时创建多个HandlerAdapter放在List集合中) // HandlerAdapter有多种类型: // RequestMappingHandlerAdapter:用于适配使用注解 @RequestMapping 标记的控制器方法 // SimpleControllerHandlerAdapter:用于适配实现了 Controller 接口的控制器 // 注意:此时还没有进行数据绑定(也就是说,表单提交的数据,此时还没有转换为pojo对象。) HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 执行请求对应的所有拦截器中的 preHandle 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 通过处理器适配器调用处理器方法 // 在调用处理器方法之前会进行数据绑定,将表单提交的数据绑定到处理器方法上。(底层是通过WebDataBinder完成的) // 在数据绑定的过程中会使用到消息转换器:HttpMessageConverter // 结束后返回ModelAndView对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 执行请求对应的所有拦截器中的 postHandle 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); // 处理分发结果(在这个方法中完成了响应) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } // 根据每一次的请求对象来获取处理器执行链对象 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { // HandlerMapping在服务器启动的时候就创建好了,放到了List集合中。HandlerMapping也有多种类型 // RequestMappingHandlerMapping:将 URL 映射到使用注解 @RequestMapping 标记的控制器方法的处理器。 // SimpleUrlHandlerMapping:将 URL 映射到处理器中指定的 URL 或 URL 模式的处理器。 for (HandlerMapping mapping : this.handlerMappings) { // 重点:这是一次请求的开始,实际上是通过处理器映射器来获取的处理器执行链对象 // 底层实际上会通过 HandlerMapping 对象获取 HandlerMethod对象,将HandlerMethod 对象传递给 HandlerExecutionChain对象。 // 注意:HandlerMapping对象和HandlerMethod对象都是在服务器启动阶段创建的。 // RequestMappingHandlerMapping对象中有多个HandlerMethod对象。 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; } private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { // 渲染 render(mv, request, response); // 渲染完毕后,调用该请求对应的所有拦截器的 afterCompletion方法。 mappedHandler.triggerAfterCompletion(request, response, null); } protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // 通过视图解析器返回视图对象 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); // 真正的渲染视图 view.render(mv.getModelInternal(), request, response); } protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { // 通过视图解析器返回视图对象 View view = viewResolver.resolveViewName(viewName, locale); } }