Skip to content

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作用

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

mvc搭建

pom文件

xml
<!--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
<?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

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标签,在只需要进行简单跳转时可以使用

  • 步骤

    1. 添加<mvc:view-controller>标签:为指定URL映射html页面
    2. 添加<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目录下受保护的资源。

java
//通过Servlet的原生api
request.getRequestDispatcher("/index").forward(request, response);

//SpringMvc使用forward进行转发
@RequestMapping("/a")
public String toA(){
    return "forward:/b";
}

重定向

由服务器通知浏览器重新发送一次请求,浏览器一共发送了两次请求,可以完成内部资源的跳转,也可以完成跨域跳转,但无法访问WEB-INF目录下受保护的资源的。

java
//通过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文件中添加如下的配置:

xml
<!-- 开启注解驱动 -->
<mvc:annotation-driven />

<!-- 配置静态资源处理,表示凡是请求路径是"/static/"开始的,都会去"/static/"目录下找该资源。 -->
<mvc:resources mapping="/static/**" location="/static/" />

请求参数注解

@RequestMapping详解

为指定的类或方法设置相应URL

注解位置

  • 书写在类上面:为当前类设置映射URL,不能单独使用,需要与方法上的@RequestMapping配合使用。最终方法的URL=类的URL+方法的URL

  • 书写在方法上面:为当前方法设置映射URL,可以单独使用

java
@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

java
@RequestMapping(
        value = {"/testRequestMapping", "/test"}//两个地址都能访问
)
public String testRequestMapping(){
    return "success";//跳转到success页面
}
path属性

String[]类型,与value属性作用一致。

method属性

RequestMethod[]类型,为当前URL【类或方法】设置请求方式。默认情况下所有请求方式均支持,如请求方式不支持,会报如下错误:405 Request method 'GET' not supported

java
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:

  1. "param":要求请求映射所匹配的请求必须携带param请求参数
  2. "!param":要求请求映射所匹配的请求必须不能携带param请求参数
  3. "param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value
  4. "param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value
java
@RequestMapping(
        value = {"/testRequestMapping", "/test"}
        ,method = {RequestMethod.GET, RequestMethod.POST}
        ,params = {"username","password!=123456"}
)
public String testRequestMapping(){
    return "success";
}
headers

String[]类型,获取当前URL的指定请求头信息。若URL中未携带指定请求头信息,会报: 404:请求资源未找到

java
"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

获取请求路径中的参数

java
@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

作用:将请求参数与方法上的形参映射。

  1. value:String类型,用于设置需要入参的参数名

  2. name:String类型,与value属性作用一致

  3. required:Boolean类型,设置当前参数,是否必须入参

    • true【默认值】:表示当前参数必须入参,如未入参会报如下错误

      400 Required String parameter 'sName' is not present

      • false:表示当前参数不必须入参,如未入参,装配null值
  4. defaultValue:String类型,当装配数值为null时,指定当前defaultValue默认值

java
@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

该注解的作用是:将请求头信息映射到方法的形参上

  1. value:String类型,用于设置需要获取请求头名称

  2. name:String类型,与value属性作用一致

  3. required:Boolean类型,设置当前当前请求头,是否必须入参

    • true:默认值,设置当前请求头为必须入参,如未入参会报如下错误

      400 Required String parameter 'sName' is not present

      • false:表示当前参数不必须入参,如未入参,装配null值
  4. defaultValue:String类型,当装配数值为null时,指定当前defaultValue默认值

java
@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数据映射到方法形参

  1. value:String类型,用于设置需要获取Cookie名称

  2. name:String类型,与value属性作用一致

  3. required:boolean类型,设置当前Cookie是否为必须入参

    • true:设置当前Cookie为必须入参,如未入参会报如下错误

      400 Required String parameter 'sName' is not present

    • false:表示当前Cookie不必须入参,如未入参,装配null值

  4. defaultValue:String类型,当装配数值为null时,指定当前defaultValue默认值

java
/**
* 获取Cookie
* @return
*/
@RequestMapping("/getCookie")
public String getCookie(@CookieValue("JSESSIONID")String cookieValue){
    System.out.println("cookieValue = " + cookieValue);
    return SUCCESS;
}

Ant 风格路径(了解):

  • 常用通配符

    a) ?:匹配一个字符

    b) *:匹配任意字符

    c) **:匹配多层路径

java
@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请求区别

  1. get没有请求体,请求发送数据的时候,数据拼接在URI的后面,以?分割URL和传输数据,参数之间以&相连。POST请求有请求体,数据可以放到请求体(也可以放到URL中)。

  2. get请求只能发送普通的字符串。并且发送的字符串长度有限制;post请求可以发送任何类型的数据,理论上没有长度限制。

  3. 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源码

      java
      public 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)

  1. 注册CharacterEncodingFilter,其必须是第一Filter位置
  2. 为CharacterEncodingFilter中属性encoding赋值
  3. 为CharacterEncodingFilter中属性forceRequestEncoding赋值
xml
<!--必须是第一过滤器位置,该配置在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源码:

java
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

处理请求和响应接口

JAVA
 //  获取请求对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

//  强制转换
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;

//  请求对象
HttpServletRequest request = servletRequestAttributes.getRequest();

//获取响应对象
HttpServletResponse response = servletRequestAttributes.getResponse();

HttpServletRequest

该接口是ServletRequest接口的子接口,封装了HTTP请求的相关信息(请求报文中的所有信息都被封装到request对象中)

java
获得请求头信息
    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

java
通过输出流将响应数据输出给客户端
    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方式
java
//在创建方法时写参数HttpServletRequest,会自动传入
@Controller
public class RequestScopeTestController {

    @RequestMapping("/testServletAPI")
    public String testServletAPI(HttpServletRequest request){
        // 向request域中存储数据
        request.setAttribute("testRequestScope", "在SpringMVC中使用原生Servlet API实现request域数据共享");
        return "view";
    }
}
使用Model接口
java
@RequestMapping("/testModel")
public String testModel(Model model){
    // 向request域中存储数据
    model.addAttribute("testRequestScope", "在SpringMVC中使用Model接口实现request域数据共享");
    return "view";
}
使用Map接口
java
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
    // 向request域中存储数据
    map.put("testRequestScope", "在SpringMVC中使用Map接口实现request域数据共享");
    return "view";
}
使用ModelMap类
java
@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域数据共享。

java
@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失效,会话结束。

使用会话域的业务场景:

  1. 在A资源中通过重定向的方式跳转到B资源,因为是重定向,因此从A到B是两次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到session域中。
  2. 登录成功后保存用户的登录状态。
使用原生Servlet API
java
//接受参数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域中。

java
@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
java
@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对象转换为响应协议的。

  • MappingJackson2HttpMessageConverterjava对象转换为json格式字符串

@RequestBody

获取请求体(注意get方法无请求体),主要用于将Ajax异步传入的JOSN数据进行转化为对应类型。

  • 在使用了axios发送异步请求之后,浏览器发送到服务器的请求参数有两种格式:
    • key=value&key=value…,此时的请求参数可以通过request.getParameter()获取
    • {key:value,key:value…},此时无法通过request.getParameter()获取,通过@RequestBody进行转换为java对象
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

用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器,而不是进行跳转页面。也可标注类上,标明该类所有方法返回值均响应到浏览器。

java
//结果:浏览器页面显示success

@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
    return "success";
}

@RestController

@RestController =@Controller+@ResponseBody。标注在类上即可,被它标注的Controller中所有的方法上都会自动标注 @ResponseBody

@ModelAttribute

使用表单提交数据时,可以将其转化为对象

RequestEntity

继承了HttpEntity,是封装请求报文的一种类型,封装了整个请求协议。可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息

java
@RequestMapping("/testRequestEntity")
//填写后会自动传入RequestEntity参数
public String testRequestEntity(RequestEntity<String> requestEntity){
    System.out.println("requestHeader:"+requestEntity.getHeaders());
    System.out.println("requestBody:"+requestEntity.getBody());
    return "success";
}

ResponseEntity

封装响应协议,包括:状态行、响应头、响应体,可用于文件下载等。

java
@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

xml
<!--导入依赖-->
<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[]>

java
@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配置

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>

注解配置

java
@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
    java
    public 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不可用
      • 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(…)方法【逆向】。

    • 将渲染结果返回给客户端

    java
    public 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);
    	}
    }