Detailed explanation of SpringMVC knowledge, including key source code analysis! ! !

This article is a study note for SpringMVC2021 in Silicon Valley at station b , and I hope it will be helpful to everyone's study! ! !

Article Directory

1. Introduction to Spring MVC

1. What is MVC

MVC is a software architecture idea that divides software into models, views, and controllers.

M: Model, the model layer, refers to the JavaBean in the project, the role is to process data

JavaBean is divided into two categories:

  • One class is called Entity Bean: specifically stores business data, such as Student, User, etc.
  • One class is called business processing Bean: it refers to Service or Dao object, which is specially used to handle business logic and data access.

V: View, view layer, refers to pages such as html or jsp in the project, which is used to interact with users and display data

C: Controller, control layer, refers to the servlet in the project, the role is to receive requests and respond to browsers

The workflow of MVC:
the user sends a request to the server through the view layer, and the request is received by the Controller in the server. The Controller calls the corresponding Model layer to process the request. After processing, the result is returned to the Controller. The Controller then finds the corresponding View view according to the result of the request processing, and finally responds to the browser after rendering the data.

2. What is Spring MVC

SpringMVC is a follow-up product of Spring and a sub-project of Spring

SpringMVC is a complete set of solutions provided by Spring for the development of presentation layer. After the presentation layer framework has undergone successive changes in many products such as Strut, WebWork, and Strut2, the industry generally chooses SpringMVC as the preferred solution for the development of the presentation layer of Java EE projects .

Note: The three-tier architecture is divided into presentation layer (or presentation layer), business logic layer, and data access layer. The presentation layer represents the foreground page and background servlet

3. The characteristics of Spring MVC

  • Native products of the Spring family , seamlessly interface with infrastructure such as IOC containers
  • Based on the native Servlet , through the powerful front controller DispatcherServlet , the request and response are processed uniformly
  • The problems that need to be solved in each segment of the presentation layer are covered in an all-round way , and comprehensive solutions are provided
  • The code is fresh and concise , greatly improving development efficiency
  • The degree of internal componentization is high, and the pluggable components can be plugged and played . You can configure the corresponding components for any function you want.
  • Excellent performance , especially suitable for the requirements of modern large-scale and ultra-large Internet projects

Two, Hello World

(1) Create a maven project:

image-20210809114519055

(2) The pom.xml file sets the packaging method to war package, and introduces the required dependencies

<groupId>org.oymn</groupId>
<artifactId>SpringMVC-demo1</artifactId>
<version>1.0-SNAPSHOT</version>
<!--设置打包方式:war-->
<packaging>war</packaging>

<dependencies>
    <!-- SpringMVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- 日志 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>

    <!-- ServletAPI -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <!-- Spring5和Thymeleaf整合包 -->
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.12.RELEASE</version>
    </dependency>
</dependencies>

(3) Create a webapp directory under the main directory, and add a web.xml configuration file under the webapp directory

Create the webapp directory:

image-20210809115645185

Add the web.xml file:

image-20210809115748867

Modify the directory manually:

image-20210809120121036

result:

image-20210809120808007

(4) Configure web.xml :

Register SpringMVC's front controller DispatcherServlet

a> Default configuration mode:

Under this configuration, the configuration file of SpringMVC is located under WEB-INF by default, and the default name is <servlet-name>-servlet.xml. For example, the configuration file of SpringMVC corresponding to the following configuration is located under WEB-INF, and the file name is springMVC-servlet.xml

<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <!--
        设置springMVC的核心控制器所能处理的请求的请求路径
        /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
        但是/不能匹配.jsp请求路径的请求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

b>Extended configuration method (generally use this) :

<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<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>

(5) Create a SpringMVC configuration file :

<!-- 自动扫描包 -->
<context:component-scan base-package="com.oymn"/>

<!-- 配置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>

(6) Create a request controller

Although the front controller uniformly processes the requests sent by the browser, different requests require different processing procedures, so a request controller needs to be created, and each request processing method in the request controller is called a controller method.

The request controller needs to be identified as a control layer component through the @Controller annotation and handed over to Spring's IOC container for management, so that SpringMVC can recognize the existence of the controller.

@Controller   //请求控制器
public class HelloController {
    
    

    @RequestMapping("/")     //将路径“/”和index.html页面联系起来
    public String index(){
    
    
        //返回视图名称
        return "index";
    }

    @RequestMapping("/target")  //将路径“/target”和target.html页面联系起来
    public String toTarget() {
    
    
        return "target";
    }
}

(7) Create the templates directory under the WEB-INF directory, and then create the pages index.html and target.html under it

index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        <h1>你好</h1>
        <!--用th修饰href属性,然后就可以使用@{} 来自动填充绝对路径了-->
        <a th:href="@{/target}">跳转到target页面</a>
    </body>
</html>

target.html jump page:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        HelloWorld
    </body>
</html>

(8) Summary :

The browser sends a request, and if the request address matches the url-pattern of the front controller, the request will be processed by the front controller DispatcherServlet. The front controller will read the configuration file of SpringMVC, find the controller by scanning the components, and match the request address with the value of the @RequestMapping annotation in the controller. If the match is successful, the controller method is the method to process the request. The method of processing the request needs to return a view name of string type. This view name will be parsed by the view resolver, plus the prefix and suffix to form a complete path, and the view will be rendered by Thymeleaf, and finally **forwarded (instead of redirected)** to the corresponding page.

3. @RequestMapping annotation

1 Introduction

  • Associate the request with the controller method that processes the request to establish a mapping relationship.

  • The location of the @RequestMapping annotation:

    • Modified a class: represents the initial information of the mapped request path.

    • Modify a method: Indicates the specific information of the mapped request path.

    • @Controller
      @RequestMapping("/test")
      public class RequestMappingController {
              
              
      
      	//此时请求映射所映射的请求的请求路径为:/test/testRequestMapping
          @RequestMapping("/testRequestMapping")
          public String testRequestMapping(){
              
              
              return "success";
          }
      
      }
      
  • Idea shortcut key alt+7 to view all properties of RequestMapping

    29

2. value attribute

  • The value attribute matches the request mapping by the request address .

  • The value attribute of @RequestMapping is a string array , which can match requests corresponding to multiple request addresses at the same time.

<a th:href="@{/testRequestMapping}">测试@RequestMapping的value属性-->/testRequestMapping</a><br>
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
@RequestMapping(
        value = {
    
    "/testRequestMapping", "/test"}
)
public String testRequestMapping(){
    
    
    return "success";
}

In the above example, whether it is a /test request or a /testRequestMapping request, you can jump to the success.html page.

3. method attribute

  • The method attribute matches the request mapping by the request method (get or post).
  • The method attribute of @RequestMapping is an array of RequestMethod type, which can match requests of multiple request methods at the same time.
  • If the current request address satisfies the value attribute of the request mapping, but the request method does not satisfy the method attribute, the browser will report 405 , for example: Request Method 'POST' not supported
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
<form th:action="@{/test}" method="post">
    <input type="submit">
</form>
@RequestMapping(
        value = {
    
    "/testRequestMapping", "/test"},
        method = {
    
    RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
    
    
    return "success";
}

Note:

  1. For controller methods that handle specific requests, SpringMVC provides @RequestMapping derived annotations:

    Annotation for processing get requests: @GetMapping

    Annotations for processing post requests: @PostMapping

    Annotation for processing put requests: @PutMapping

    Annotation for processing delete requests: @DeleteMapping

  2. Commonly used request methods are get, post, put, delete

    However, the current browsers only support the two request methods of get and post. If other request methods (put or delete) are set in the form submission, the default request method of get will be used for processing.

    To send a put or delete request, you need to pass the filter HiddenHttpMethodFilter provided by Spring, which will be mentioned in the RESTful section.

4. params attribute

  • The params attribute matches request mappings by request parameters .
  • The params attribute is an array of string type, and the matching relationship between request parameters and request mapping can be set through four expressions
    • "param": It is required that the matched request must carry the param request parameter
    • "!param": It is required that the matched request must not carry the param request parameter
    • "param=value": It is required that the matched request must carry the param request parameter, and the value is value
    • "param!=value": It is required that the matched request must carry the param request parameter, but the value cannot be value
<a th:href="@{/test(username='admin',password=123456)">测试@RequestMapping的params属性-->/test</a><br>
@RequestMapping(
        value = {
    
    "/testRequestMapping", "/test"}
        ,method = {
    
    RequestMethod.GET, RequestMethod.POST}
        ,params = {
    
    "username","password!=123456"}
)
public String testRequestMapping(){
    
    
    return "success";
}

Note: If the current request satisfies the value attribute and method attribute of @RequestMapping, but does not satisfy the params attribute, the page will report an error 400 : Parameter conditions “username, password!=123456” not met for actual request parameters: username={admin}, password={123456}

5. headers attribute

  • The headers attribute matches the request mapping by the request header information of the request.
  • The headers attribute is an array of string types, and four expressions can be used to set the matching relationship between request header information and request mapping.
    • "header": The request matched by the request mapping must carry the header request header information
    • "!header": The request matched by the request mapping must not carry the header request header information
    • "header=value": The request matched by the request mapping must carry header request header information and header=value
    • "header!=value": The request matched by the request mapping must carry header request header information and header!=value
  • If the current request satisfies the value attribute and method attribute of @RequestMapping, but does not satisfy the headers attribute, the page displays a 404 error , that is, the resource is not found.
@RequestMapping(
    value="/test1",
    headers={
    
    "Host=localhost:8081"}      //若使用默认端口8080访问,则会报错404
)
public String test1(){
    
    
    return "success";
}

6. SpringMVC supports ant-style paths

(1)? : represents any single character

@RequestMapping("/a?a")    //请求路径中?的位置可以替代成任意单个字符(但不能省略),例如"/aba","/aga","/a:a"等都是可以的
public String test2(){
    
    
    return "success";
}

(2) *: Indicates any 0 or more characters

@RequestMapping("/a*a")  //请求路径中*的位置可以替代成0个或多个字符,例如"/aba","/aa","/aaaaa"等都是可以的
public String test2(){
    
    
    return "success";
}

(3) **: Indicates any one or more layers of directories

@RequestMapping("/**/a")   //请求路径中**位置可以替代成0层或多层目录,例如"/a/a","/b/b/b/a","/a"等都是可以的
public String test2(){
    
    
    return "success";
}

7. Support path placeholders in SpringMVC

  • Original method: /deleteUser?id=1

  • Rest method: /deleteUser/1

The placeholders in the SpringMVC path are often used in the RESTful style. When the parameters are transmitted to the server through the request path, the parameters represented by the placeholders can be assigned to the formal parameters of the controller method through the placeholder {xxx} in the value attribute of the @RequestMapping annotation of the corresponding controller method, and then through the **@ PathVariable annotation ** .

<a th:href="@{/test/1/admin}">测试路径中的占位符</a><br/>
@RequestMapping("/test/{id}/{username}")
public String test(@PathVariable("id") Integer id, @PathVariable("username") String username){
    
    
    System.out.println("id="+id+",username="+username);
    return "success";
}

Fourth, SpringMVC obtains request parameters

1. Obtain via Servlet API

HttpServletRequest is used as the formal parameter of the controller method . At this time, the parameter of type HttpServletRequest represents the object that encapsulates the request message of the current request.

<a th:href="@{/test01(username='oymn',password='000000')}">测试通过HttpServletRequest获取请求参数</a><br/>
@RequestMapping("/test01")
public String test1(HttpServletRequest request){
    
            //传入HttpServletRequest对象
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    System.out.println("username="+username+",password="+password);
    return "success";
}

2. Automatically obtain request parameters through formal parameters with the same name

In the formal parameter position of the controller method, set the formal parameter with the same name as the request parameter . When the browser sends a request and matches the request mapping, DispatcherServlet will automatically assign the request parameter to the corresponding formal parameter.

<a th:href="@{/test02(username='oymn',password='000000')}">测试同名形参获取请求参数</a><br/>
@RequestMapping("/test02")
public String test2(String username, String password){
    
           //设置同名形参,自动赋值
    System.out.println("username="+username+",password="+password);
    return "success";
}

When there are multiple request parameters with the same name in the request , such as a check box in a form, you can set a string array or a string type parameter to accept the request parameter.

If an array of strings is used , the array contains each request parameter.

If the string type is used , it is a string concatenated with commas for the request parameters.

3. @RequestParam

Through the above study, we know that the request parameters can be automatically obtained through the formal parameters of the same name, but when the formal parameters and the request parameters are inconsistent, or when you want to explicitly specify the mapping relationship between the formal parameters and the request parameters, you can use the @RequestParam annotation.

@RequestParam has three attributes:

  • value : Specify the name of the request parameter assigned as the formal parameter.
  • required : Set whether the request parameter must be transmitted, the default is true .
  • defaultValue : No matter required is true or false, when the request parameter specified by value is not transmitted or the transmitted value is an empty string, the default value defaultValue is used to assign a value to the formal parameter.

Note: If required is set to true, but the request parameter is not included in the request, and the defaultValue attribute is not set, a 400 error will be reported: Required String parameter 'xxx' is not present; if it is set to false, the request parameter is not required, and if it is not transmitted, the formal parameter value identified by the annotation is null.

@RequestMapping("/test04")
//value的值为name,对应html文件中请求参数的名字;required设置为false表示该参数不是必须项;defaultValue设置默认值为oymn
public String test4(@RequestParam(value="name",required=false,defaultValue="oymn")String username){
    
    
    System.out.println(username);
    return "success";
}
<a th:href="@{/test04(name='oymn')}">测试RequestParam注解</a><br/>

4. @RequestHeader

@RequestHeader creates a mapping relationship between the request header information and the formal parameters of the controller method

The @RequestHeader annotation has three attributes: value, required, defaultValue, the usage is the same as @RequestParam

5. @CookieValue

@CookieValue is to create a mapping relationship between cookie data and controller method parameters

The @CookieValue annotation has three attributes: value, required, defaultValue, the usage is the same as @RequestParam

@RequestMapping("/test05")
public String test5(@CookieValue("JSESSIONID") String JSESSIONID){
    
       //获取session的cookie
    System.out.println(JSESSIONID);
    return "success";
}

6. Get request parameters through POJO

The formal parameter of the controller method is set to an entity class object. At this time, if the parameter name of the request sent by the browser corresponds to the attribute name of the entity class object , then the attribute of the entity class will be automatically assigned a value.

<form th:action="@{/testpojo}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    性别:<input type="radio" name="sex" value=""><input type="radio" name="sex" value=""><br>
    年龄:<input type="text" name="age"><br>
    邮箱:<input type="text" name="email"><br>
    <input type="submit">
</form>
@RequestMapping("/testpojo")
public String testPojo(User user){
    
    
    System.out.println(user.toString());
    return "success";
}

In the above example, if the form is submitted by post , the gender will be garbled when the user object is output in the controller method testPojo() (because it is in Chinese), and the garbled problem needs to be solved; if the form is submitted by get, the garbled problem will not occur (if it does, it should be a tomcat problem).

7. Solve the garbled problem of getting request parameters

To solve the garbled problem, you need to process it during the filter stage , because sending a request will pass the listener, the filter, and then serve. If you set it through the request.SetCharacterenCoding () method like JavaWeb, because this method needs to be executed before obtaining the request parameter, and the srpingmvc is through S through s Before Ervletapi obtains the request request object, the request parameters have been automatically obtained internally, so the setting code is useless, so the task needs to be advanced to the filter stage in advance.

SpringMVC provides the encoding filter CharacterEncodingFilter , which needs to be registered in web.xml.

<!--配置SpringMVC的编码过滤器-->
<filter>
    <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>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Part of the source code of CharacterEncodingFilter :

public class CharacterEncodingFilter extends OncePerRequestFilter {
    
    
    @Nullable
    private String encoding;     //这里就是设置编码的属性
    private boolean forceRequestEncoding;  //强制设置请求编码,默认为false
    private boolean forceResponseEncoding; //强制设置响应编码,默认为false

    //无参构造函数
    public CharacterEncodingFilter() {
    
    
        this.forceRequestEncoding = false;
        this.forceResponseEncoding = false;
    }
    
    //这个就是过滤器的执行方法dofilter
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        
        String encoding = this.getEncoding();   //如果没有设置默认为null
        
        if (encoding != null) {
    
       //如果设置了encoding
            
            //request.getCharacterEncoding()==null这个条件表明了只要之前没有设置过编码,请求编码就可以设置成功了
            if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
    
    
                request.setCharacterEncoding(encoding);
            }
            
			//想要设置响应编码,还需要将forceResponseEncoding设置为true
            if (this.isForceResponseEncoding()) {
    
    
                response.setCharacterEncoding(encoding);
            }
        }
		
        filterChain.doFilter(request, response);
    }
    
}

Through the above source code analysis, if you want to set the response encoding, you need to set the encoding and forceResponseEncoding to true . Therefore, a more complete encoding filter is as follows :

<!--配置SpringMVC的编码过滤器-->
<filter>
    <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>
    <!--这个参数是为了设置响应的编码-->
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Note: SpringMVC's encoding filter needs to be configured before other filters, otherwise it will be invalid.

5. Domain objects share data

1. Use ServletAPI to share data with request domain objects

@RequestMapping("/testscope")
public String testScope(HttpServletRequest request){
    
    
    request.setAttribute("hello","testScopeByServletAPI");   //调用setAttribute方法
    return "success";
}
<p th:text="${hello}"></p>   

2. Use ModelAndView to share data with request domain objects

ModelAndView has the functions of Model and View:

  • Model: used to share data to the request domain
  • View: used to set the view and realize page jump
@RequestMapping("/testscope2")
public ModelAndView testScope2(){
    
    
    
    //创建ModelAndView对象
    ModelAndView mav = new ModelAndView();
    
    //向请求域中共享数据
    mav.addObject("hello","hello,testScopeByModelAndView");
    
    //设置视图,实现页面跳转
    mav.setViewName("success");
    
    return mav;
}

3. Use Model to share data with request domain objects

@RequestMapping("/testscope3")
public String testScope3(Model model){
    
    
    model.addAttribute("hello","testScopeByModel");
    return "success";
}

4. Use Map to share data with request domain objects

@RequestMapping("/testscope4")
public String testScope4(Map<String, Object> map){
    
    
    map.put("hello", "testScopeByMap");
    return "success";
}

5. Use ModelMap to share data with request domain objects

@RequestMapping("/testscope5")
public String testScope5(ModelMap modelMap){
    
    
    modelMap.addAttribute("hello","testScopeByModelMap");
    return "success";
}

6. Relationship between Model, ModelMap, and Map

System.out.println(model.getClass().getName());

System.out.println(modelMap.getClass().getName());

System.out.println(map.getClass().getName());

Output the full class name of the implementation class of these three parameters in the controller method, and the output results are: org.springframework.validation.support.BindingAwareModelMap, indicating that the parameters of the Model, ModelMap, and Map types are essentially BindingAwareModelMap objects.

//ModelMap是ExtendedModelMap的父类,ExtendedModelMap是BindingAwareModelMap的父类
//相当于ModelMap是BindingAwareModelMap的父类
public class ExtendedModelMap extends ModelMap implements Model {
    
    }   
public class BindingAwareModelMap extends ExtendedModelMap {
    
    }  

//LinkedHashMap是ModelMap的父类,而LinkedHashMap实现了Map,相当于ModelMap实现了Map
//而BindingAwareModelMap是ModelMap的子类,相当于BindingAwareModelMap实现了Map
public class ModelMap extends LinkedHashMap<String, Object> {
    
    }
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
    
    }

//ExtendedModelMap实现了Model,相当于BindingAwareModelMap实现了Model
public interface Model {
    
    }
public class ExtendedModelMap extends ModelMap implements Model {
    
    } 

The relationship diagram is as follows:

image-20210812182433880

7. Share data to the Session domain

@RequestMapping("/testscope6")
public String testScope6(HttpSession session){
    
    
    session.setAttribute("hello","testSession");
    return "success";	
}
<p th:text="${session.hello}"></p>

8. Share data to the Application domain

@RequestMapping("/testscope7")
public String testScope7(HttpSession session){
    
    
    ServletContext application = session.getServletContext();    //先获取ServletContext对象
    application.setAttribute("hello","testApplication");
    return "success";
}
<p th:text="${application.hello}"></p>

6. View of SpringMVC

The view in SpringMVC is the View interface. The function of the view is to render data and display the data in the model Model to the user.

There are many kinds of views in SpringMVC, and there are forwarding views and redirecting views by default .

  • When the jstl dependency is introduced into the project , the forwarding view will be automatically converted to jstlView .

  • When the view technology used is Thymeleaf , that is, the Thymeleaf view resolver is configured in the SpringMVC configuration file, and the ThymeleafView is obtained after the view resolver resolves .

1. ThymeleafView

When the view name set in the controller method does not have any prefix , the view name at this time will be parsed by the view resolver in the SpringMVC configuration file , and the final path obtained by splicing the view name with the view prefix and view suffix will be redirected by forwarding .

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

Track the source code:

Break the point in the controller method, and then find the doDispatch method in the debug method stack. At this time, the doDispatch method executes to this line:

image-20210813093821919

Then execute the program to processDispatchReuslt and enter the method:

image-20210813093937115

In the processDispatchResult method, execute the render method and enter:

image-20210813094039791

After executing the resolveViewName method in the render method, you can see that the view is a ThymeleafView object at this time.

image-20210813094254713

2. Forward view InternalResourceView

The default forwarding view in SpringMVC is InternalResourceView .

The situation of creating a forwarding view in SpringMVC: When the view name set in the controller method is prefixed with "forward:" , the InternalResourceView view will be created first . At this time, the view name will not be parsed by the view parser configured by SpringMVC, but the prefix "forward:" will be removed , and the remaining part will be used as the final path to realize the jump through forwarding .

@RequestMapping("/testInternalResourceView")
public String testInternalResourceView(){
    
    
    return "forward:/testThymeleaf";
}
image-20210813220019115

3. Redirect view RedirectView

SpringMVC's default redirection view is RedirectView .

The situation of creating a redirect view in SpringMVC: When the view name set in the controller method is prefixed with "redirect:" , the RedirectView view will be created first . At this time, the view name will not be parsed by the view parser configured by SpringMVC, but the prefix "redirect:" will be removed , and the remaining part will be used as the final path to realize the jump through redirection.

@RequestMapping("/testRedirectView")
public String testRedirectView(){
    
    
    return "redirect:/testThymeleaf";
}
image-20210813215519424

Note: When the redirected view is parsed, the redirect: prefix will be removed first, and then it will be judged whether the remaining part starts with /, and if so, the context path will be automatically spliced

4. View controller view-controller

When the controller method is only used to realize page jumping , that is, when only the view name needs to be set, the view-controller tag can be used instead of the controller method.

<!--
	path:设置处理的请求地址
	view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>
<!--开启mvc注解驱动-->
<mvc:annotation-driven />

5. The view parser used by the jsp page

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/templates/"></property>     <!--视图前缀-->
    <property name="suffix" value=".jsp"></property>					<!--视图后缀-->
</bean>

Seven, RESTful

1. RESTful

  • REST: Re presentational State Transfer , resource state transfer at the presentation layer .

There are four verbs in the Http protocol: GET, POST, PUT, and DELETE , corresponding to four basic operations: GET is used to obtain resources, POST is used to create new resources, PUT is used to update resources, and DELETE is used to delete resources, that is, adding, deleting, modifying and checking.

The REST style advocates the use of a unified style design for URL addresses. Each word is separated by "/" from front to back. Instead of using question mark key-value pairs to carry request parameters, the request parameters to be sent to the server are used as part of the URL address to ensure the consistency of the overall style.

operate traditional way REST style
query operation getUserById?id=1 user/1–>get request method
save operation saveUser user–>post request method
delete operation deleteUser?id=1 user/1–>delete request method
update operation updateUser user–>put request method

2. HiddenHttpMethodFilter

Since the browser only supports sending get and post requests, how to send put and delete requests?

SpringMVC provides HiddenHttpMethodFilter to help us convert POST requests into DELETE or PUT requests

HiddenHttpMethodFilter handles the conditions for put and delete requests:

  • The request method of the current request must be post
  • The current request must transmit the request parameter _method

If the above conditions are met, the HiddenHttpMethodFilter filter will convert the request method of the current request into the value of the request parameter _method , so the value of the request parameter _method is the final request method.

Register the HiddenHttpMethodFilter filter in web.xml:

<!--配置HiddenHttpMethodFilter-->
<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>
<form th:action="@{/user}" method="post">                    <!--条件1:设置请求方式为post-->
    <input type="hidden" name="_method" value="put">         <!--条件2:通过隐藏域传输请求参数_method-->
    <input type="submit" >
</form>

Note:

So far, two filters are provided in SpringMVC: CharacterEncodingFilter and HiddenHttpMethodFilter

When registering in web.xml, you must first register CharacterEncodingFilter, and then register HiddenHttpMethodFilter

(CharacterEncodingFilter is written in front of HiddenHttpMethodFilter in the web.xml file)

reason:

  • In CharacterEncodingFilter, set the character set through the request.setCharacterEncoding(encoding) method

  • The request.setCharacterEncoding(encoding) method requires no previous operations to obtain request parameters

  • And HiddenHttpMethodFilter has exactly one operation to get the request method:

  • String paramValue = request.getParameter(this.methodParam);
    

Track the source code:

The most important thing about a filter is the method of executing the filter. First, start here: doFilter (doFilterInternal)

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
    
    HttpServletRequest requestToUse = request;
    
    //这个判断需要满足两个条件:request是一个post请求;
    //而request.getAttribute("javax.servlet.error.exception")为null 可以认为是恒成立的,这里不太清楚
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) 	 {
    
    
        
        //这里的methodParam是一个字符串,值为"_method"
        String paramValue = request.getParameter(this.methodParam);
        
        //如果_method的值有长度,也就是不为空
        if (StringUtils.hasLength(paramValue)) {
    
    
           
            //将_method的值转换为大写
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            
            //ALLOWED_METHODS是一个List集合,包含"PUT","DELETE","PATCH"
            if (ALLOWED_METHODS.contains(method)) {
    
    
                //HttpMethodRequestWrapper是HiddenHttpMethodFilter的一个内部类
                //这里就是根据_method的值也就是真正的请求方式重新创建了一个请求对象
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }
	//从这里也可以看出,过滤器在放行时用的是根据_method的值新创建的请求对象
    filterChain.doFilter((ServletRequest)requestToUse, response);
}

八、HttpMessageConverter

HttpMessageConverter, a message information converter, converts a request message into a java object, or converts a java object into a response message .

HttpMessageConverter provides two annotations and two types: @RequestBody, @ResponseBody, RequestEntity, ResponseEntity .

1、@RequestBody

@RequestBody can get the request body, you need to set a formal parameter in the controller method, use @RequestBody to identify, the request body of the current request will assign a value to the formal parameter identified by the current annotation

<form th:action="@{/testRequestBody}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit">
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
    
    
    System.out.println("requestBody:"+requestBody);
    return "success";
}

Output result:

requestBody:username=admin&password=123456

2、RequestEntity

RequestEntity encapsulates a type of request message, which needs to be set in the formal parameter of the controller method, and the request message of the current request will be assigned to the formal parameter, and the request header information can be obtained through getHeaders(), and the request body information can be obtained through getBody()

@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
    
    
    System.out.println("requestHeader:"+requestEntity.getHeaders());
    System.out.println("requestBody:"+requestEntity.getBody());
    return "success";
}

输出结果:
requestHeader:[host:“localhost:8080”, connection:“keep-alive”, content-length:“27”, cache-control:“max-age=0”, sec-ch-ua:“” Not A;Brand";v=“99”, “Chromium”;v=“90”, “Google Chrome”;v=“90"”, sec-ch-ua-mobile:“?0”, upgrade-insecure-requests:“1”, origin:“http://localhost:8080”, user-agent:“Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36”]
requestBody:username=admin&password=123

3、@ResponseBody

@ResponseBody is used to identify a controller method, and the return value of the method can be directly responded to the browser as the response body of the response message

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

Result: The browser page displays success

4. Spring MVC processes json

Steps for @ResponseBody to process json:

a> import jackson dependencies

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

b> Turn on the annotation driver of mvc in the core configuration file of SpringMVC. At this time, a message converter will be automatically assembled in the HandlerAdaptor : MappingJackson2HttpMessageConverter, which can convert the Java object responding to the browser into a string in Json format

<mvc:annotation-driven />

c> Use the @ResponseBody annotation on the processor method to identify

d> Return the Java object directly as the return value of the controller method, and it will be automatically converted to a string in Json format

@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
    
    
    return new User(1001,"admin","123456",23,"男");
}

The results displayed in the browser's page:

{“id”:1001,“username”:“admin”,“password”:“123456”,“age”:23,“sex”:“男”}

5. Spring MVC handles ajax

a> request hyperlink:

<div id="app">
	<a th:href="@{/testAjax}" @click="testAjax">testAjax</a><br>
</div>

b> Handle click events through vue and axios:

<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript">
    var vue = new Vue({
      
      
        el:"#app",
        methods:{
      
      
            testAjax:function (event) {
      
      
                axios({
      
      
                    method:"post",
                    url:event.target.href,
                    params:{
      
      
                        username:"admin",
                        password:"123456"
                    }
                }).then(function (response) {
      
      
                    alert(response.data);
                });
                event.preventDefault();
            }
        }
    });
</script>

c>Controller method:

@RequestMapping("/testAjax")
@ResponseBody
public String testAjax(String username, String password){
    
    
    System.out.println("username:"+username+",password:"+password);
    return "hello,ajax";
}

6. @RestController annotation

The @RestController annotation is a composite annotation provided by springMVC. It is marked on the controller class, which is equivalent to adding the @Controller annotation to the class, and adding the @ResponseBody annotation to each method in it.

7、ResponseEntity

ResponseEntity is used for the return value type of the controller method , and the return value of the controller method is the response message to the browser

9. File upload and download

1. File download

  • Use ResponseEntity to implement file download.

Create a new directory static under the webapp directory, then create a new img directory, and store 1.jpg in the img directory

image-20210816181120777
@RequestMapping("/fileDownload")
public ResponseEntity<byte[]> fileDownload(HttpSession session) throws IOException {
    
    
    //获取ServletContext对象
    ServletContext servletContext = session.getServletContext();
    //获取服务器中文件的真实路径
    String realPath = servletContext.getRealPath("/static/img/1.jpg");
    //创建输入流
    InputStream is = new FileInputStream(realPath);
    //创建字节数组
    byte[] bytes = new byte[is.available()];
    //将流读到字节数组中
    is.read(bytes);
    //创建HttpHeaders对象设置响应头信息
    MultiValueMap<String, String> headers = new HttpHeaders();
    //设置要下载方式以及下载文件的名字
    headers.add("Content-Disposition", "attachment;filename=1.jpg");
    //设置响应状态码
    HttpStatus statusCode = HttpStatus.OK;
    //创建ResponseEntity对象
    ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes, headers, statusCode);
    //关闭输入流
    is.close();
    return responseEntity;
}
<a th:href="@{/fileDownload}">文件下载 1.jpg</a>

2. File upload

  • File upload requires the request method of the form form to be post , and add the attribute enctype="multipart/form-data"

  • SpringMVC encapsulates the uploaded file into a MultipartFile object, through which file information can be obtained.

step:

  1. Add dependencies for file uploads:

    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    
  2. Configure the file parser in SpringMVC to convert the file into a MultipartFile object

    <!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    
  3. Controller method:

    @RequestMapping("/fileUpload")
    //注意:这里MultipartFile的对象名photo需要和html中表单设置的名字一致,才能实现将文件转化为MultipartFile对象
    public String fileUpload(MultipartFile photo, HttpSession session) throws IOException {
          
          
        //获取上传的文件的文件名
        String fileName = photo.getOriginalFilename();
        //处理文件重名问题
        String hzName = fileName.substring(fileName.lastIndexOf("."));
        fileName = UUID.randomUUID().toString() + hzName;
        //获取服务器中photo目录的路径
        ServletContext servletContext = session.getServletContext();
        String photoPath = servletContext.getRealPath("/img");
        File file = new File(photoPath);
        if(!file.exists()){
          
          
            file.mkdir();
        }
        String finalPath = photoPath + File.separator + fileName;
        //实现上传功能
        photo.transferTo(new File(finalPath));
        return "success";
    }
    
  4. html page: set to post request, enctype is "multipart/form-data" , and note that the name attribute in the <input> tag and the name of the MultipartFile object of the formal parameter of the controller method must be consistent .

    <form th:action="@{/fileUpload}" enctype="multipart/form-data" method="post">
        <input type="file" name="photo">
        <input type="submit">
    </form>
    

10. Interceptor

1. Interceptor configuration

  • SpringMVC interceptors are used to intercept the execution of controller methods.

step:

  1. Implement the HandlerInterceptor interface and rewrite the three methods of preHandle(), postHandle(), and afterCompletion().

    public class TestInterceptor implements HandlerInterceptor {
          
          
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          
          
            System.out.println("preHandle");
            return true;
        }
    
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          
          
            System.out.println("postHandle");
        }
    
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
          
          
            System.out.println("afterCompletion");
        }
    }
    
  2. Configure in the configuration file of SpringMVC

    There are three ways to write:

    The first:

    <mvc:interceptors>
    	<bean class="com.oymn.interceptor.TestInterceptor"></bean>    <!--设置拦截器-->
    </mvc:interceptors>
    

    The second type:

    <mvc:interceptors>
    	<ref bean="testInterceptor"></ref>      <!--设置拦截器-->
    </mvc:interceptors>
    

    The third type:

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>                   <!--/**表示拦截所有请求-->
            <mvc:exclude-mapping path="/"/>            <!--表示不拦截/请求-->
            <ref bean="testInterceptor"></ref>         <!--设置拦截器-->
        </mvc:interceptor>
    </mvc:interceptors>
    

    The first and second are to intercept all requests processed by DispatcherServlet .

    The third is to set the interceptor through the ref or bean tag , set the request to be intercepted through mvc:mapping , and set the request to be excluded through mvc:exclude-mapping, that is, the request that does not need to be intercepted .

2. Three abstract methods of interceptors

Interceptors in SpringMVC have three abstract methods:

  • preHandle : Execute preHandle() before the execution of the controller method. Its return value of boolean type indicates whether to intercept or release , return true to indicate release, that is, call the controller method; return false to indicate interception, that is, not to call the controller method .
  • postHandle : Execute postHandle() after the controller method is executed .
  • afterCompletion : After processing the view and model data, execute afterCompletion() after rendering the view .
image-20210816220758732

source code:

image-20210816221251251

Enter the processDispatchResult method:

image-20210816221610173

3. Execution order of multiple interceptors

(1) If prehandle() of each interceptor returns true :

At this time, the execution order of multiple interceptors is related to the configuration order of the SpringMVC configuration file.

preHandle() will be executed in the order of configuration, while postHandle() and afterComplation() will be executed in reverse order of configuration .

(2) If the prehandle() of an interceptor returns false :

The interceptor that returns false from preHandle() and the preHandle() of the interceptor before it will be executed, postHandle() will not be executed, and the afterComplation() of the interceptor before the interceptor that returns false will be executed, and it will be executed in reverse order.

Source code analysis:

Enter the applyPreHandle() method:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
    
    //正向遍历,同时interceptorIndex记录着最后一个返回true的拦截器的下标
    for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
    
    
        
        //获取拦截器
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i); 
        
        //执行preHandle方法,然后判断是否返回false
        if (!interceptor.preHandle(request, response, this.handler)) {
    
    
            
            //当preHandle方法返回false时,会执行afterComplation()方法
            this.triggerAfterCompletion(request, response, (Exception)null);
            return false;
        }
        
    }

    return true;
}

Enter the applyPostHandle() method:

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    
    
    //反序遍历
    for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
    
    
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
        //执行postHandle方法
        interceptor.postHandle(request, response, this.handler, mv);
    }

}

Enter the triggerAfterCompletion method:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    
    
    //反序遍历,并且是只遍历interceptorIndex,也就是只遍历返回false的拦截器之前的拦截器
    for(int i = this.interceptorIndex; i >= 0; --i) {
    
    
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

        try {
    
    
            //执行afterCompletion方法
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable var7) {
    
    
            logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
        }
    }

}

It can be seen from here that when the applyPreHandle method returns false , that is, when there is an interceptor whose preHandle returns false in the interceptor list, the subsequent applyPostHandle method will not be executed .

image-20210817104722037

Eleven, exception handler

1. Configuration-based exception handler

SpringMVC provides an interface to handle exceptions that occur during the execution of controller methods : HandlerExceptionResolver

Implementation classes of HandlerExceptionResolver: DefaultHandlerExceptionResolver and SimpleMappingExceptionResolver

image-20210817105947919

Among them, SimpleMappingExceptionResolver is a custom exception handler provided by SpringMVC .

<!--注册异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <!--key表示控制器方法执行过程中可能出现的异常:ArigthmeticException算术异常-->
            <!--值error是指出现异常后跳转到指定页面error.html,该视图同样被thymeleaf解析,补充前后缀-->
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
    <!--为exceptionAttribute设置一个值,作为异常信息的属性名,将出现的异常信息在请求域中进行共享-->
    <property name="exceptionAttribute" value="exception"></property>
</bean>

Controller method:

@RequestMapping("/testException")
public String testException(){
    
    
    int i = 1/0;    //出现异常
    return "success";
}

error.html page:

出现了错误<br/>
错误信息是:<p th:text="${exception}"></p>     <!-这里的exception就是配置文件中exceptionAttribute的value值--->

Effect:

image-20210817114758956

2. Annotation-based exception handler

@ControllerAdvice      //@ControllerAdvice将当前类表示为处理异常的组件
public class ExceptionController {
    
    
	//@ExceptionHandler设置所处理的异常
    @ExceptionHandler(value = {
    
    ArithmeticException.class,NullPointerException.class})
    public String testException1(Exception ex, Model model){
    
       //通过形参Exception类型对象接收异常信息
        model.addAttribute("ex",ex);
        return "error";
    }
}

error.html page:

出现了错误<br/>
错误信息是:<p th:text="${ex}"></p>     <!--这里的exception就是配置文件中exceptionAttribute的value值-->

12. Annotation configuration SpringMVC

  • Use configuration classes and annotations to replace the functions of web.xml and SpringMVC configuration files

1. Create an initialization class to replace web.xml

In Spring 3.0 and later environments, the container will search for a class that implements the javax.servlet.ServletContainerInitializer interface in the class path , and if found, use it to configure the Servlet container, that is, the Tomcat server.

And Spring provides an implementation of this interface, called SpringServletContainerInitializer , which in turn will look for classes that implement WebApplicationInitializer and hand over the configuration tasks to them.

And Spring3.2 introduced the implementation class of WebApplicationInitializer, called AbstractAnnotationConfigDispatcherServletInitializer , when our class inherits this implementation class and deploys it to the Servlet3.0 container, the container will automatically find it and use it to configure the Servlet context.

public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    //指定Spring的配置类
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    SpringConfig.class};
    }

    //指定SpringMVC的配置类
    protected Class<?>[] getServletConfigClasses() {
    
    
        return new Class[]{
    
    WebConfig.class};
    }

    //指定DispatcherServlet的映射规则,即url-pattern
    protected String[] getServletMappings() {
    
    
        return new String[]{
    
    "/"};
    }

    //添加过滤器
    @Override
    protected Filter[] getServletFilters() {
    
    
        //编码过滤器
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceResponseEncoding(true);

        //处理put、delete请求的过滤器
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{
    
    encodingFilter,hiddenHttpMethodFilter};
    }
}

2. Create a SpringConfig configuration class to replace the Spring configuration file

@Configuration
public class SpringConfig {
    
    
	//ssm整合之后,spring的配置信息写在此类中
}

3. Create a WebConfig configuration class to replace the SpringMVC configuration file

//  1.组件扫描     2.视图解析器     3.mvc注解驱动      4.视图控制view-controller
//  5.文件上传解析器  6.defaultServletHandler  7.拦截器  8.异常处理器

@Configuration   //标识配置类
@ComponentScan("com.oymn")   //1.开启组件扫描
@EnableWebMvc    //3.开启mvc注解驱动
public class WebConfig implements WebMvcConfigurer {
    
         //实现WebMvcConfigurer接口

    //8.异常处理器
    @Bean
    public SimpleMappingExceptionResolver exceptionResolver(){
    
    
        //创建SimpleMappingExceptionResolver
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

        Properties prop = new Properties();
        //key表示可能出现的算数异常,value表示出现异常后跳转的error.html页面
        prop.setProperty("java.lang.ArithmeticException","error");
        //设置异常映射
        exceptionResolver.setExceptionMappings(prop);
        //设置共享信息的键
        exceptionResolver.setExceptionAttribute("ex");

        return exceptionResolver;
    }

    //7.配置拦截器
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        //自定义的拦截器类FirstInterceptor
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        //设置拦截规则:addPathPatterns的/**表示拦截所有请求,excludePathPatterns的/表示/请求不拦截
        registry.addInterceptor(firstInterceptor).addPathPatterns("/**").excludePathPatterns("/");
    }

    //6.默认Servlet处理静态资源
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    
    
        configurer.enable();
    }

    //5.配置文件上传解析器
    @Bean
    public CommonsMultipartResolver multipartResolver(){
    
    
        return new CommonsMultipartResolver();
    }

    //4.视图控制view-controller
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("index");
    }

    //2.配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
    
    
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
                webApplicationContext.getServletContext());
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    //生成模板引擎并为模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
    
    
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }

    //生成视图解析器并未解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
    
    
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }
}

The exception handler above, in addition to returning the SimpleMappingExceptionResolver object through the @Bean tag, can also be rewritten

The WebMvcConfigurer interface provides the configureHandlerExceptionResolvers method, and the rewritten content is basically the same as above.

//8.异常处理器
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    
    
    SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
    Properties prop = new Properties();
    prop.setProperty("java.lang.ArithmeticException", "error");
    //设置异常映射
    exceptionResolver.setExceptionMappings(prop);
    //设置共享异常信息的键
    exceptionResolver.setExceptionAttribute("ex");
    resolvers.add(exceptionResolver);
}

Thirteen, SpringMVC execution process

1. Common components of SpringMVC

  • DispatcherServlet: front-end controller , does not require engineers to develop, provided by the framework

Role: Unified processing of requests and responses, the center of the entire process control, which calls other components to process user requests

  • HandlerMapping: processor mapper , does not require engineers to develop, provided by the framework

Function: Find the Handler according to the requested url, method and other information, that is, the controller method

  • Handler: Processor , which needs to be developed by engineers

Function: Handler processes specific user requests under the control of DispatcherServlet

  • HandlerAdapter: Processor adapter , no need for engineer development, provided by the framework

Function: Execute the processor (controller method) through HandlerAdapter

  • ViewResolver: view resolver , no need for engineer development, provided by the framework

Function: Perform view analysis to obtain corresponding views, for example: ThymeleafView, InternalResourceView, RedirectView

  • View: view

Role: display the model data to the user through the page

2. DispatcherServlet initialization process

  • DispatcherServlet is essentially a Servlet

    image-20210818113300628

The above is a simplified diagram of the DispatcherServlet initialization call process:

Next, analyze some source code:

  1. Initialize the WebApplicationContext:
protected WebApplicationContext initWebApplicationContext() {
    
    
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
    
		//webApplicationContext为null,if语句不会执行
        if (this.webApplicationContext != null) {
    
    
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
    
    
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if (!cwac.isActive()) {
    
    
                    if (cwac.getParent() == null) {
    
    
                        cwac.setParent(rootContext);
                    }

                    this.configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }

        if (wac == null) {
    
    
            wac = this.findWebApplicationContext();
        }

        if (wac == null) {
    
    
            //创建WebApplicationContext
            wac = this.createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
    
    
            synchronized(this.onRefreshMonitor) {
    
    
                //刷新WebApplicationContext
                this.onRefresh(wac);
            }
        }

        if (this.publishContext) {
    
    
            String attrName = this.getServletContextAttributeName();
            //将IOC容器在应用域中共享
            this.getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }
  1. Create a WebApplicationContext
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    
    
        Class<?> contextClass = this.getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    
    
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
    
    
            
            //通过反射创建IOC容器对象
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            //设置环境
            wac.setEnvironment(this.getEnvironment());
            //设置父容器(也就是Spring)
            wac.setParent(parent);
            String configLocation = this.getContextConfigLocation();
            if (configLocation != null) {
    
    
                wac.setConfigLocation(configLocation);
            }

            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }
  1. DispatcherServlet initialization strategy

    After FrameworkServlet creates WebApplicationContext, it refreshes the container and calls onFresh(wac). This method is rewritten in DispatcherServlet, and the initStategies(context) method is called to initialize the strategy, that is, to initialize each component of DispatcherServlet.

    protected void initStrategies(ApplicationContext context) {
          
          
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }
    

3. DispatcherServlet calls the component to process the request

HttpServlet中**service(HttpServletRequest request, HttpServletResponse response)**方法:

Call the corresponding doXXX method by request

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
    
    
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
    
    
            this.doGet(req, resp);
        } else {
    
    
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified) {
    
    
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
    
    
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
    
    
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
    
    
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
    
    
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
    
    
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
    
    
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
    
    
        this.doTrace(req, resp);
    } else {
    
    
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{
    
    method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

FrameworkServlet rewrites the service (HttpServletRequest request, HttpServletResponse response) method and doXXX() method in HttpServlet , and these methods call processRequest(request, response).

//重写HttpServlet的service方法
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
    
    
        
        //调用父类的service方法,但从上面的源码可以知道,实质上是调用了对应的doxxx方法
        //而FrameworkServlet又重写了doXXX方法,如下,可以知道doXXX方法中又调用了processRequest方法,所以和下面else一样
        super.service(request, response);     
        
    } else {
    
    
        //调用processRequest方法
        this.processRequest(request, response);
    }

}

//重写HttpServlet的doGet方法
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

//重写HttpServlet的doPost方法
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

//重写HttpServlet的doPut方法
protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

//重写HttpServlet的doDelete方法
protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

The processRequest(request, response) method is as follows:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = this.buildLocaleContext(request);
    
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
    
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
    
    this.initContextHolders(request, localeContext, requestAttributes);

    try {
    
    
        //执行服务,doService是一个抽象方法,在DispatcherServlet中进行了重写
        this.doService(request, response);
    } catch (IOException | ServletException var16) {
    
    
        failureCause = var16;
        throw var16;
    } catch (Throwable var17) {
    
    
        failureCause = var17;
        throw new NestedServletException("Request processing failed", var17);
    } finally {
    
    
        this.resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
    
    
            requestAttributes.requestCompleted();
        }

        this.logResult(request, response, (Throwable)failureCause, asyncManager);
        this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
    }

}

The doService method in DispatcherServlet:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
    this.logRequest(request);
    
    Map<String, Object> attributesSnapshot = null;
    
    if (WebUtils.isIncludeRequest(request)) {
    
    
        attributesSnapshot = new HashMap();
        Enumeration attrNames = request.getAttributeNames();

        label120:
        while(true) {
    
    
            String attrName;
            do {
    
    
                if (!attrNames.hasMoreElements()) {
    
    
                    break label120;
                }

                attrName = (String)attrNames.nextElement();
            } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

            attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
    }

    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
    if (this.flashMapManager != null) {
    
    
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
    
    
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath requestPath = null;
    if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
    
    
        requestPath = ServletRequestPathUtils.parseAndCache(request);
    }

    try {
    
    
        //处理请求和响应
        this.doDispatch(request, response);
    } finally {
    
    
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
    
    
            this.restoreAttributesAfterInclude(request, attributesSnapshot);
        }

        if (requestPath != null) {
    
    
            ServletRequestPathUtils.clearParsedRequestPath(request);
        }

    }

}

doDispatch method:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    

    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
    
    
        try {
    
    
            ModelAndView mv = null;
            Object dispatchException = null;

            try {
    
    
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

             //执行链,包含handler,interceptorList,interceporIndex
             //handler:浏览器发送的请求所匹配的控制器方法
             //interceptorList:处理控制器方法的所有拦截器集合
             //interceporIndex:拦截器索引,preHanler方法返回false的最后一个拦截器的下标,控制afterCompletion方法的执行
                mappedHandler = this.getHandler(processedRequest);

                if (mappedHandler == null) {
    
    
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                //处理器适配器,用于调用请求所对应的控制器方法
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                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;
                }

                //由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
    
    
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);

                //调用拦截器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception var20) {
    
    
                dispatchException = var20;
            } 
            catch (Throwable var21) {
    
    
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

			//后续处理:处理模型数据和渲染视图
            processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } 
        catch (Exception var22) {
    
    
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } 
        catch (Throwable var23) {
    
    
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
    
    
        if (asyncManager.isConcurrentHandlingStarted()) {
    
    
            if (mappedHandler != null) {
    
    
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
    
    
            this.cleanupMultipart(processedRequest);
        }

    }
}

processDispatchResult method:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    
    
    
    boolean errorView = false;
    if (exception != null) {
    
    
        if (exception instanceof ModelAndViewDefiningException) {
    
    
            this.logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException)exception).getModelAndView();
        } else {
    
    
            Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
            mv = this.processHandlerException(request, response, handler, exception);
            errorView = mv != null;
        }
    }

    if (mv != null && !mv.wasCleared()) {
    
    
        //处理模型数据和渲染视图
        this.render(mv, request, response);
        if (errorView) {
    
    
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else if (this.logger.isTraceEnabled()) {
    
    
        this.logger.trace("No view rendering, null ModelAndView returned.");
    }

    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    
    
        if (mappedHandler != null) {
    
    
            //调用拦截器的afterCompletion方法
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }

    }
}

4. The execution process of SpringMVC

  1. The user sends a request to the server, and the request is captured by the SpringMVC front controller DispatcherServlet.

  2. DispatcherServlet parses the request URL, obtains the request resource identifier (URI), and determines the mapping corresponding to the request URI:

a) does not exist

i. Then judge whether mvc:default-servlet-handler is configured

ii. If not configured, the console will report that the mapping cannot be found, and the client will display a 404 error

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-SaH6iGR1-1672844074895) (D:\File\Study Materials\SpringMVC\Notes\img\img006.png)]

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-63YAc6XD-1672844074896) (D:\File\Study Materials\SpringMVC\Notes\img\img007.png)]

iii. If there is a configuration, access the target resource (usually a static resource, such as: JS, CSS, HTML), and if the client cannot be found, a 404 error will be displayed

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-P9ZPpkcL-1672844074897) (D:\File\Study Materials\SpringMVC\Notes\img\img008.png)]

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-8jFa567Q-1672844074898) (D:\File\Study Materials\SpringMVC\Notes\img\img009.png)]

b) If it exists, execute the following process

  1. According to the URI, call HandlerMapping to obtain all related objects configured by the Handler (including the Handler object and the interceptor corresponding to the Handler object), and finally return it in the form of a HandlerExecutionChain execution chain object.

  2. DispatcherServlet selects an appropriate HandlerAdapter according to the obtained Handler.

  3. If the HandlerAdapter is successfully obtained, the interceptor's preHandler(...) method will be executed at this time [Forward]

  4. Extract the model data in the Request, fill in the Handler input parameters, start executing the Handler (Controller) method, and process the request. In the process of filling Handler's input parameters, according to your configuration, Spring will help you do some extra work:

a) HttpMessageConveter: Convert the request message (such as Json, xml, etc.) into an object, and convert the object into the specified response information

b) Data conversion: perform data conversion on the request message. Such as converting String to Integer, Double, etc.

c) Data formatting: format the data of the request message. Such as converting a string to a formatted number or formatted date, etc.

d) Data verification: Verify the validity of the data (length, format, etc.), and store the verification results in BindingResult or Error

  1. After the Handler is executed, it returns a ModelAndView object to the DispatcherServlet.

  2. At this point, the interceptor's postHandle(...) method [reverse] will be executed.

  3. According to the returned ModelAndView (at this time, it will judge whether there is an exception: if there is an exception, execute HandlerExceptionResolver for exception handling) select a suitable ViewResolver for view resolution, and render the view according to the Model and View.

  4. After rendering the view, execute the afterCompletion(…) method of the interceptor [reverse].

  5. Return the rendered result to the client.

Guess you like

Origin blog.csdn.net/OYMNCHR/article/details/120076465