Java SSM remastered version (2) SpringMvc

SpringMVC basics

Before entering: "Spring Core Content" "JavaWeb" "JDK9-17 New Features"

After learning the Spring framework technology earlier, there will be two groups of people: one group is confused and still doesn’t understand what this thing does; the other group almost understands the core ideas, but doesn’t know these How something should function. But it doesn’t matter. In the SpringMVC stage, you will definitely be able to gradually appreciate the convenience that the Spring framework brings to us.

At this stage, we will return to Tomcat's Web application development again to experience the great convenience that the Spring framework brings to us.

MVC theoretical basis

Previously, we explained to you the three-tier architecture, including:

img

Each layer has its own responsibilities, the most critical of which is the presentation layer, because it is equivalent to a layer that directly interacts with the user's browser, and all requests will be parsed through it, and then the business layer will be notified. Processing, the return and data filling of any page are all completed by the presentation layer, so it is actually the most critical layer in the entire three-tier architecture. In the previous actual development, we wrote a large number of Servlets (that is, Presentation layer implementation) to handle various requests from the browser, but we found that for just a few small functions and a few very basic pages, we have to write nearly ten Servlets. If it is a larger website Systems, such as Taobao and Bilibili, may contain dozens or even hundreds of functions in just one page. Think about how terrifying it would be to write that.

Therefore, SpringMVC was born to solve this problem. It is a very excellent presentation layer framework, designed and implemented using MVC ideas.

MVC is explained in detail as follows:

  • M refers to the business model (Model): in layman's terms, it is the entity class we used to encapsulate data transfer.
  • V refers to user interface (View): generally refers to the front-end page.
  • C is the controller: the controller is equivalent to the basic function of the Servlet, processing requests and returning responses.

img

SpringMVC hopes to decouple the three, achieve their own tasks, and divide the corresponding responsibilities more finely. Finally, render the View and Model to get the final page and return it to the front end (just like using Thymeleaf before, give both the entity data object and the front-end page to Thymeleaf, and then it will integrate and render them to get the final data) page, and this tutorial will also use Thymeleaf as the view parser to explain)


Configure the environment and build the project

Here we continue to use the previous Tomcat10 server. After Spring 6, Tomcat10 or higher is required. As before, we directly create a new JakartaEE project.

image-20230219162053172

The relevant files will be automatically generated after creation, but please check whether the URL and application context name in the running configuration are consistent.

Traditional XML configuration form

The SpringMvc project still supports multiple configuration forms. Here we first explain the most traditional XML configuration form.

First we need to add Mvc related dependencies:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.0.10</version>
</dependency>

Then we need to configure web.xml and replace the DispatcherServlet with the Servlet that comes with Tomcat. Here the url-pattern needs to be written as /, and the replacement can be completed:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Then you need to configure a Spring context environment (that is, container) for the entire web application. Because SpringMVC is developed based on Spring, it directly uses the container provided by Spring to implement various functions. So the first step is still the same as before, you need to write A configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

Next we need to configure some initialization parameters for DispatcherServlet to specify the configuration file we just created:

<servlet>
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      	<!--     指定我们刚刚创建在类路径下的XML配置文件       -->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application.xml</param-value>
    </init-param>
</servlet>

In this way, we have completed the basic configuration. Now we can test whether the configuration is correct. We delete the Servlet class that comes with the project and create a Controller class used in Mvc. It doesn’t matter if you haven’t learned it yet, just follow it. Here we Just to test this:

@Controller
public class HelloController {
    
    
    @ResponseBody
    @RequestMapping("/")
    public String hello(){
    
    
        return "HelloWorld!";
    }
}

Then we need to register this class as a Bean to use it normally. Let's write the Spring configuration file. Here we directly configure package scanning. Package scanning under XML needs to be turned on like this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
  	<!-- 需要先引入context命名空间,然后直接配置base-package属性就可以了 -->
    <context:component-scan base-package="com.example"/>
</beans>

If HelloWorld can successfully appear in the browser, the configuration is successful:

image-20230219170637540

Smart friends may have discovered that the Controller we wrote above is actually responsible for the basic functions of the Servlet. For example, what we return here is the HelloWorld string, so when we access this address, we will get the characters returned here. String, you can see that the writing method is very concise. As for how this is done and how to use it, we will introduce it in detail in this chapter.

Full annotation configuration form

If you want to completely discard the configuration file and use pure annotation development, you can add a class directly. Tomcat will look for a class that implements the ServletContainerInitializer interface in the class path. If found, use it to configure the Servlet container. Spring provides this The implementation class of the interface, SpringServletContainerInitializer, is set through @HandlesTypes(WebApplicationInitializer.class). This class in turn will find the class that implements WebApplicationInitializer and assign the configuration task to them, so just implement the interface directly:

public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    @Override
    protected Class<?>[] getRootConfigClasses() {
    
    
        return new Class[]{
    
    WebConfiguration.class};   //基本的Spring配置类,一般用于业务层配置
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
    
    
        return new Class[0];  //配置DispatcherServlet的配置类、主要用于Controller等配置,这里为了教学简单,就不分这么详细了,只使用上面的基本配置类
    }

    @Override
    protected String[] getServletMappings() {
    
    
        return new String[]{
    
    "/"};    //匹配路径,与上面一致
    }
}

Then we need to add some necessary annotations to the configuration class:

@Configuration
@EnableWebMvc   //快速配置SpringMvc注解,如果不添加此注解会导致后续无法通过实现WebMvcConfigurer接口进行自定义配置
@ComponentScan("com.example.controller")
public class WebConfiguration {
    
    
}

In this way we can also access normally:

image-20230219170637540

From now on, for convenience, we will uniformly write in full annotation form.

If Log Technology reports an error and cannot display Mvc related logs, please add the following dependencies:

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.33</version>
</dependency>
<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.7.33</version>
</dependency>

After adding it, you can print the log normally:

image-20230630162821105

ControllerController

With SpringMVC, we no longer have to create a Servlet for each request address as before. It uses the DispatcherServletdefault static resource Servlet instead of the default static resource Servlet provided by Tomcat. That is to say, all requests now (except jsp, because Tomcat also provides A jsp Servlet) will DispatcherServletbe processed.

So DispatcherServletwhat will it help us do?

img

According to the picture, we can understand that after our request reaches the Tomcat server, it will be handed over to the current Web application for processing, and SpringMVC is used to process DispatcherServletall requests, which means that it is used as a unified access point, and all requests are processed It does the scheduling.

When a request passes DispatcherServlet, it will go first HandlerMapping. It will map the request to HandlerExecutionChain, and pass through HandlerInterceptorthe filters in turn, which is similar to the filters we learned before. However, in SpringMVC we use interceptors, and then hand them over HandlerAdapteraccording to the path of the request. Select the appropriate controller for processing. After the controller processing is completed, an ModelAndViewobject will be returned, including the data model and the view. In layman's terms, it is the data in the page and the page itself (only the view name is sufficient).

After returning ModelAndView, it will be handed over to ViewResolverthe (view parser) for processing. The view parser will parse the entire view page. SpringMVC comes with some view parsers, but they only apply to JSP pages. We can also use Thymeleaf as before. View parser, so that we can directly read the page written in HTML and parse it into a real View based on the given view name.

After the parsing is completed, all the data in the page needs to be rendered into the View, and finally returned to DispatcherServleta formed page containing all the data, and then responded to the browser to complete the entire process.

Therefore, in fact, in the entire process, we only need to write the Controller corresponding to the request path and configure the ViewResolver we need. We can continue to add interceptors later, and SpringMVC has helped us complete the other processes.

Configure view resolvers and controllers

First we need to implement the most basic page parsing and return. The first step is to configure the view parser. Here we use the view parser provided by Thymeleaf and import the required dependencies:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring6</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>

Configuring the view resolver is very simple. We only need to register the corresponding ViewResolverone as a Bean. Here we write it directly in the configuration class:

@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfiguration {
    
    
    //我们需要使用ThymeleafViewResolver作为视图解析器,并解析我们的HTML页面
    @Bean
    public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine springTemplateEngine){
    
    
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setOrder(1);   //可以存在多个视图解析器,并且可以为他们设定解析顺序
        resolver.setCharacterEncoding("UTF-8");   //编码格式是重中之重
        resolver.setTemplateEngine(springTemplateEngine);   //和之前JavaWeb阶段一样,需要使用模板引擎进行解析,所以这里也需要设定一下模板引擎
        return resolver;
    }

    //配置模板解析器
    @Bean
    public SpringResourceTemplateResolver templateResolver(){
    
    
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setSuffix(".html");   //需要解析的后缀名称
        resolver.setPrefix("/");   //需要解析的HTML页面文件存放的位置,默认是webapp目录下,如果是类路径下需要添加classpath:前缀
        return resolver;
    }

    //配置模板引擎Bean
    @Bean
    public SpringTemplateEngine springTemplateEngine(ITemplateResolver resolver){
    
    
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(resolver);   //模板解析器,默认即可
        return engine;
    }
}

Now that we have completed the configuration of the view resolver, we then create a Controller. Creating a Controller is also very simple. Just add an annotation to a class. It @Controllerwill be scanned by Spring and automatically registered as a Bean of the Controller type. Then we only need to write a method in the class to handle the request for the corresponding address:

@Controller   //直接添加注解即可
public class HelloController {
    
    

    @RequestMapping("/index")   //直接填写访问路径
    public ModelAndView index(){
    
    
        return new ModelAndView("index");  //返回ModelAndView对象,这里填入了视图的名称
      	//返回后会经过视图解析器进行处理
    }
}

Then we create a simple html file in the root directory of the classpath:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>
    <p>欢迎来到GayHub全球最大同性交友网站</p>
</body>
</html>

We will find that after opening the browser, we can directly access our HTML page:

image-20230220150905300

Before, when we used Thymeleaf to parse some data from the backend, we needed to pass it through the Context. However, after using SpringMvc, we can provide the data directly to the Model model layer:

@RequestMapping(value = "/index")
public ModelAndView index(){
    
    
    ModelAndView modelAndView = new ModelAndView("index");
    modelAndView.getModel().put("name", "啊这");   //将name传递给Model
    return modelAndView;
}

In this way, Thymeleaf can receive the data we pass and parse it:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="static/test.js"></script>
</head>
<body>
    HelloWorld!
    <div th:text="${name}"></div>
</body>
</html>

Of course, for simplicity, we can directly return the View name, and SpringMVC will automatically wrap it into a ModelAndView object:

@RequestMapping(value = "/index")
public String index(){
    
    
    return "index";
}

We can also add a Model separately as a formal parameter to set, and SpringMVC will automatically help us pass the instance object through dependency injection:

@RequestMapping(value = "/index")
public String index(Model model){
    
      //这里不仅仅可以是Model,还可以是Map、ModelMap
    model.addAttribute("name", "yyds");
    return "index";
}

With the blessing of the Spring framework, compared to the web application we wrote before, it is simply a level more convenient. Just say whether you like it or not, whether you like it or not.

Note that you must ensure that a horizontal line appears under the view name and hold down Ctrl to jump. The configuration is correct (the latest version of IDEA)

Our page may also contain some static resources, such as js and css, so here we also need to configure it so that the static resources can be parsed through the default Servlet provided by Tomcat. We need to let the configuration class implement the interface so that in the web WebMvcConfigurerapplication When the program starts, further configuration will be performed based on the content in our overridden method:

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    
    
    configurer.enable();   //开启默认的Servlet
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    
    registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    //配置静态资源的访问路径
}

Let’s write the front-end content:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <!-- 引用静态资源,这里使用Thymeleaf的网址链接表达式,Thymeleaf会自动添加web应用程序的名称到链接前面 -->
    <script th:src="@{/static/test.js}"></script>
</head>
<body>
    <p>欢迎来到GayHub全球最大同性交友网站</p>
</body>
</html>

Create test.jsand write the following content:

window.alert("欢迎来到GayHub全球最大同性交友网站")

Finally, visit the page. A pop-up window will be displayed when the page is loaded. In this way, we have completed the most basic page configuration. Compared with the previous method, this is much simpler, directly avoiding the need to write a large number of Servlets to handle requests.

@RequestMapping detailed explanation

We have already learned how to create a controller to handle our requests. Then we only need to add a method to the controller to handle the corresponding requests. Before, we needed to completely write a Servlet to implement it, but now we only need to You only need to add one @RequestMappingto achieve it. In fact, we can also know from its name that this annotation establishes a mapping relationship between requests and request processing methods. When a request is received, the corresponding request processing method can be called according to the mapping relationship. So let’s talk about it first @RequestMapping. The annotation is defined as follows:

@Mapping
public @interface RequestMapping {
    
    
    String name() default "";

    @AliasFor("path")
    String[] value() default {
    
    };

    @AliasFor("value")
    String[] path() default {
    
    };

    RequestMethod[] method() default {
    
    };

    String[] params() default {
    
    };

    String[] headers() default {
    
    };

    String[] consumes() default {
    
    };

    String[] produces() default {
    
    };
}

The most critical one is the path attribute (equivalent to value), which determines the request path processed by the current method. Note that the path must be globally unique. Any path can only be processed by one method. It is an array, which means that this method Not only can it be used to process a certain request path, we can use this method to process multiple request paths:

@RequestMapping({
    
    "/index", "/test"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Now when we access /index or /test, it will be processed through this method.

We can also @RequestMappingadd it directly to the class name to add a path prefix to all request mappings in this class, such as:

@Controller
@RequestMapping("/yyds")
public class MainController {
    
    

    @RequestMapping({
    
    "/index", "/test"})
    public ModelAndView index(){
    
    
        return new ModelAndView("index");
    }
}

So now we need to access /yyds/indexor /yyds/testto get this page. We can directly view all request mappings defined by the current web application in the endpoint section below IDEA, and can directly access a certain path through the built-in web client provided by IDEA.

Paths also support wildcard matching:

  • ?: Represents any character, such as @RequestMapping("/index/x?")/index/xa, /index/xb, etc. can be matched.
  • *: Indicates any 0-n characters, such as @RequestMapping("/index/*")/index/lbwnb, /index/yyds, etc. can be matched.
  • **: Indicates the current directory or a multi-level directory based on the current directory, for example, @RequestMapping("/index/**")it can match /index, /index/xxx, etc.

Let's look at the next method attribute. As the name suggests, it is the method type of the request. We can limit the request method, such as:

@RequestMapping(value = "/index", method = RequestMethod.POST)
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Now if we directly use the browser to access this page, it will show 405 method not supported, because the browser defaults to directly using the GET method to obtain the page, and here we specify the POST method to access this address, so the access fails, we now go to the endpoint Use POST method to access, and successfully get the page.

image-20230220152559862

We can also use derived annotations to directly set request mappings for specified types:

@PostMapping(value = "/index")
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

The request mapping that is directly specified as the POST request type is used here @PostMapping. Similarly, there is also @GetMappingthe GET request method that can be directly specified, which will not be listed here.

We can use paramsattributes to specify which request parameters the request must carry, such as:

@RequestMapping(value = "/index", params = {
    
    "username", "password"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

For example, here we require that the request must carry usernameand passwordattributes, otherwise it cannot be accessed. It also supports expressions, for example we can write like this:

@RequestMapping(value = "/index", params = {
    
    "!username", "password"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Adding an exclamation point before username indicates that the request is not allowed to carry this parameter, otherwise it will not be accessible. We can even set a fixed value directly:

@RequestMapping(value = "/index", params = {
    
    "username!=test", "password=123"})
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

In this way, the request parameter username is not allowed to be test, and the password must be 123, otherwise access will not be possible.

headerThe usage of the attribute is paramsthe same as that of the attribute, but it requires what content needs to be carried in the request header, such as:

@RequestMapping(value = "/index", headers = "!Connection")
public ModelAndView index(){
    
    
    return new ModelAndView("index");
}

Then, if the request header carries Connectionattributes, it will not be accessible. Two other properties:

  • consumes: Specify the submission content type (Content-Type) for processing requests, such as application/json, text/html;
  • produces: specifies the content type to be returned. It will be returned only if the (Accept) type in the request header contains the specified type;

Detailed explanation of @RequestParam and @RequestHeader

Let's take a look at how to get the parameters in the request.

We only need to add a formal parameter to the method and add @RequestParaman annotation in front of the formal parameter:

@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam("username") String username){
    
    
    System.out.println("接受到请求参数:"+username);
    return new ModelAndView("index");
}

We need to @RequestParamfill in the parameter name in and the value of the parameter will be automatically passed to the formal parameter. We can use it directly in the method. Note that if the parameter name is the same as the formal parameter name, the parameter value can be obtained even without adding it @RequestParam.

Once added @RequestParam, the request must carry the specified parameters. We can also set the require attribute to false to make the attribute non-required:

@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "username", required = false) String username){
    
    
    System.out.println("接受到请求参数:"+username);
    return new ModelAndView("index");
}

We can also directly set a default value. When the request parameter is missing, the default value can be used directly:

@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "username", required = false, defaultValue = "伞兵一号") String username){
    
    
    System.out.println("接受到请求参数:"+username);
    return new ModelAndView("index");
}

If you need to use some of the original Servlet classes, such as:

@RequestMapping(value = "/index")
public ModelAndView index(HttpServletRequest request){
    
    
    System.out.println("接受到请求参数:"+request.getParameterMap().keySet());
    return new ModelAndView("index");
}

Just add it directly HttpServletRequestas a formal parameter. SpringMVC will automatically pass the original HttpServletRequestobject of the request. Similarly, we can also add it HttpServletResponseas a formal parameter, or even directly pass HttpSession as a parameter:

@RequestMapping(value = "/index")
public ModelAndView index(HttpSession session){
    
    
    System.out.println(session.getAttribute("test"));
    session.setAttribute("test", "鸡你太美");
    return new ModelAndView("index");
}

We can also pass request parameters directly to an entity class:

@Data
public class User {
    
    
    String username;
    String password;
}

Note that all parameters must be included in the set method or constructor. The request parameters will be automatically matched according to the field names in the class:

@RequestMapping(value = "/index")
public ModelAndView index(User user){
    
    
    System.out.println("获取到cookie值为:"+user);
    return new ModelAndView("index");
}

@RequestHeaderThe usage is @RequestParamconsistent, but it is used to obtain request header parameters and will not be demonstrated here.

@CookieValue和@SessionAttrbutie

By using @CookieValueannotations, we can also quickly obtain the cookie information carried by the request:

@RequestMapping(value = "/index")
public ModelAndView index(HttpServletResponse response,
                          @CookieValue(value = "test", required = false) String test){
    
    
    System.out.println("获取到cookie值为:"+test);
    response.addCookie(new Cookie("test", "lbwnb"));
    return new ModelAndView("index");
}

Similarly, Session can also be quickly obtained using annotations:

@RequestMapping(value = "/index")
public ModelAndView index(@SessionAttribute(value = "test", required = false) String test,
                          HttpSession session){
    
    
    session.setAttribute("test", "xxxx");
    System.out.println(test);
    return new ModelAndView("index");
}

It can be found that by using the SpringMVC framework, the development of the entire web application becomes very simple. Most functions only require one annotation. It is thanks to the Spring framework that SpringMVC can show its talents.

Redirects and request forwarding

Redirection and request forwarding are also very simple. We only need to add a prefix in front of the view name, such as redirection:

@RequestMapping("/index")
public String index(){
    
    
    return "redirect:home";
}

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

By adding redirect:a prefix, redirection can be easily implemented. As for request forwarding, it is actually the same. Use the forward:prefix to indicate forwarding to other request mappings:

@RequestMapping("/index")
public String index(){
    
    
    return "forward:home";
}

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

Using SpringMVC, only one prefix is ​​needed to implement redirection and request forwarding, which is very convenient.

Web scope of beans

When learning Spring, we explained the scope of Bean, including singletonand prototype. Bean will be created in single instance and multiple instance modes respectively. In SpringMVC, its scope is continued to be subdivided:

  • request: For each HTTP request, the Bean defined using the request scope will generate a new instance, and the Bean will disappear after the request is completed.
  • Session: For each session, a Bean defined using session scope will generate a new instance, and the Bean will disappear after the session expires.
  • global session: not commonly used, no explanation will be given.

Here we create a test class to try it out:

public class TestBean {
    
    

}

Then register it as a Bean. Note that you need to add @RequestScopeor @SessionScopeindicate the Web scope of this Bean:

@Bean
@RequestScope
public TestBean testBean(){
    
    
    return new TestBean();
}

Then we automatically inject it into the Controller:

@Controller
public class MainController {
    
    

    @Resource
    TestBean bean;

    @RequestMapping(value = "/index")
    public ModelAndView index(){
    
    
        System.out.println(bean);
        return new ModelAndView("index");
    }
}

We found that the Bean instance obtained each time was different, and then we changed its scope to so @SessionScopethat the scope was raised to Session. As long as the browser's cookies are cleared, it will be considered the same session, as long as it is the same session, the Bean instance remains unchanged.

In fact, it is also implemented through a proxy. The methods we call in the Bean will be forwarded to the real Bean object for execution.


RestFul style

The Chinese definition is "Presentation Layer State Transition" (the name is quite fancy). It is not a standard, but a design style. Its main function is to fully and correctly utilize the characteristics of the HTTP protocol and standardize the URI path for resource acquisition. In layman's terms, RESTful style design allows parameters to be passed to the server through URL splicing. The purpose is to make the URL look more concise and practical, and we can make full use of a variety of HTTP request methods (POST/GET/PUT/DELETE) to Perform different types of operations on the same request address.

Therefore, with this style of connection, we can read parameters directly from the request path, such as:

http://localhost:8080/mvc/index/123456

We can directly process the next-level path of the index as a request parameter, which means that the current request parameters are included in the request path:

@RequestMapping("/index/{str}")
public String index(@PathVariable String str) {
    
    
    System.out.println(str);
    return "index";
}

Note that we can manually add placeholder-like information to the request path, so that everything in the placeholder position will be used as request parameters, and the formal parameter list of the method must include a parameter with the same name as the placeholder and annotations added @PathVariable. Parameters, or @PathVariablespecified as placeholder names by annotations:

@RequestMapping("/index/{str}")
public String index(@PathVariable("str") String text){
    
    
    System.out.println(text);
    return "index";
}

If it is not configured correctly, a yellow line will appear on the method name.

We can divide it according to different functions:

  • POST http://localhost:8080/mvc/index - add user information and carry form data
  • GET http://localhost:8080/mvc/index/{id} - Get user information, the id is placed directly in the request path
  • PUT http://localhost:8080/mvc/index - Modify user information and carry form data
  • DELETE http://localhost:8080/mvc/index/{id} - delete user information, id is placed directly in the request path

We write four request mappings respectively:

@Controller
public class MainController {
    
    

    @RequestMapping(value = "/index/{id}", method = RequestMethod.GET)
    public String get(@PathVariable("id") String text){
    
    
        System.out.println("获取用户:"+text);
        return "index";
    }

    @RequestMapping(value = "/index", method = RequestMethod.POST)
    public String post(String username){
    
    
        System.out.println("添加用户:"+username);
        return "index";
    }

    @RequestMapping(value = "/index/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable("id") String text){
    
    
        System.out.println("删除用户:"+text);
        return "index";
    }

    @RequestMapping(value = "/index", method = RequestMethod.PUT)
    public String put(String username){
    
    
        System.out.println("修改用户:"+username);
        return "index";
    }
}

This is just a design style, just understand it.


Interceptor interceptor

Interceptors are an important part of the entire SpringMVC. Interceptors are similar to filters and are used to intercept some illegal requests. However, the filters we explained before act on Servlets and can only be successfully reached after passing through layers of filters. Servlet, and the interceptor is not before the Servlet, it is between the Servlet and the RequestMapping, which is equivalent to the DispatcherServlet intercepting the request before handing it over to the method in the corresponding Controller. It will only intercept all requests corresponding to the request mapping defined in the Controller. request (static resources will not be intercepted), the difference between the two must be distinguished here.

image-20230630194651686

Create interceptor

To create an interceptor we need to implement an HandlerInterceptorinterface:

public class MainInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        System.out.println("我是处理之前!");
        return true;   //只有返回true才会继续,否则直接结束
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("我是处理之后!");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
      	//在DispatcherServlet完全处理完请求后被调用
        System.out.println("我是完成之后!");
    }
}

Then we need to register in the configuration class:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    
    registry.addInterceptor(new MainInterceptor())
      .addPathPatterns("/**")    //添加拦截器的匹配路径,只要匹配一律拦截
      .excludePathPatterns("/home");   //拦截器不进行拦截的路径
}

Now we access the index page in the browser, and the interceptor has taken effect.

Get the execution order of the finishing interceptors:

我是处理之前!
我是处理!
我是处理之后!
我是完成之后!

In other words, before and after processing, the real request mapping processing is included, and the method is executed once after the entire process. In fact, the afterCompletionwhole process is similar to the Filter we knew before, but before processing, we only need Just return true or false to indicate whether it has been intercepted, instead of using FilterChain to pass it down.

So let's take a look at what will happen if false is returned before processing:

我是处理之前!

Through the results, we found that once false is returned, all subsequent processes will be cancelled. So what if an exception occurs during processing?

@RequestMapping("/index")
public String index(){
    
    
    System.out.println("我是处理!");
    if(true) throw new RuntimeException("");
    return "index";
}

The result is:

我是处理之前!
我是处理!
我是完成之后!

We found that if an exception is thrown during processing, the post-processing postHandlemethod will not be executed, but afterCompletionthe method will be executed. We can get the thrown exception in this method.

multi-stage interceptor

The previous article introduced the situation where there is only one interceptor. Let's look at how it will be executed if there are multiple interceptors. We create the second interceptor in the same way:

public class SubInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        System.out.println("二号拦截器:我是处理之前!");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("二号拦截器:我是处理之后!");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        System.out.println("二号拦截器:我是完成之后!");
    }
}

Register the second interceptor:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    
  	//一号拦截器
    registry.addInterceptor(new MainInterceptor()).addPathPatterns("/**").excludePathPatterns("/home");
  	//二号拦截器
    registry.addInterceptor(new SubInterceptor()).addPathPatterns("/**");
}

Note that the order of interception is the order of registration, so the interceptors will be executed sequentially according to the order of registration. We can open the browser and run it once:

一号拦截器:我是处理之前!
二号拦截器:我是处理之前!
我是处理!
二号拦截器:我是处理之后!
一号拦截器:我是处理之后!
二号拦截器:我是完成之后!
一号拦截器:我是完成之后!

postHandleSame as the multi-level Filter, before processing, interceptions are performed in order from front to back, but after the processing is completed, the post-processing methods are executed in reverse order, and after completion, they are executed in the same reverse order after all executions.

So what if the No. 1 interceptor returns false before processing?

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
    System.out.println("一号拦截器:我是处理之前!");
    return false;
}

The result is as follows:

一号拦截器:我是处理之前!

We found that, just like the case of a single interceptor, once the interceptor returns false, it will no longer continue regardless of whether there is an interceptor or not.

Exception handling

When an exception occurs in our request mapping method, it will be displayed directly on the front-end page. This is because SpringMVC provides us with a default exception handling page. When an exception occurs, our request will be directly transferred to a dedicated exception handling page. controller for processing.

We can customize an exception handling controller. Once a specified exception occurs, it will be transferred to this controller for execution:

@ControllerAdvice
public class ErrorController {
    
    

    @ExceptionHandler(Exception.class)
    public String error(Exception e, Model model){
    
      //可以直接添加形参来获取异常
        e.printStackTrace();
        model.addAttribute("e", e);
        return "500";
    }
}

Then we write a page specifically to display exceptions:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  500 - 服务器出现了一个内部错误QAQ
  <div th:text="${e}"></div>
</body>
</html>

Then modify:

@RequestMapping("/index")
public String index(){
    
    
    System.out.println("我是处理!");
    if(true) throw new RuntimeException("您的氪金力度不足,无法访问!");
    return "index";
}

After accessing, we found that the console will output exception information, and the page is also a customized page.

JSON data format and Axios requests

JSON (JavaScript Object Notation, JS Object Notation) is a lightweight data exchange format.

What we now advocate is the development model that separates the front and back ends, instead of handing all the content to the back end for rendering and then sending it to the browser. In other words, the content of the entire Web page is written at the beginning, and the data in it is The front-end executes the JS code to dynamically obtain it from the server, and then renders (fills) the front-end. This can greatly reduce the pressure on the back-end, and the back-end only needs to transmit key data (in the upcoming SpringBoot stage, we will completely Adopting a development model with front-end and back-end separation)

JSON data format

Since we want to realize the separation of front-end and back-end, we must agree on a more efficient data transmission mode to transmit the data provided by the back-end to the front-end page. Therefore, JSON was born. It is very easy to understand and has excellent compatibility with the front end. Therefore, the more mainstream data transmission method now is carried through JSON format.

A piece of data in JSON format looks like this, taking the student object as an example:

{
    
    "name": "杰哥", "age": 18}

Multiple students can be represented in the form of an array:

[{
    
    "name": "杰哥", "age": 18}, {
    
    "name": "阿伟", "age": 18}]

The nested relationship can be expressed as:

{
    
    "studentList": [{
    
    "name": "杰哥", "age": 18}, {
    
    "name": "阿伟", "age": 18}], "count": 2}

It directly includes the name and value of the attribute, which is very similar to the object of JavaScript. After it reaches the front end, it can be directly converted into an object, and the operation and content are read in the form of an object, which is equivalent to expressing it in the form of a string. A JS object that we can test directly in the console window:

let obj = JSON.parse('{"studentList": [{"name": "杰哥", "age": 18}, {"name": "阿伟", "age": 18}], "count": 2}')
//将JSON格式字符串转换为JS对象
obj.studentList[0].name   //直接访问第一个学生的名称

We can also convert JS objects to JSON strings:

JSON.stringify(obj)

Our backend can return data to the frontend in the form of a JSON string, so that after the frontend gets the data, it can quickly obtain it, which is very convenient.

So how does the backend quickly create data in JSON format? We first need to import the following dependencies:

<dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>2.0.34</version>
</dependency>

There are many JSON parsing frameworks, the more commonly used ones are Jackson and FastJSON. Here we use Alibaba's FastJSON for parsing, which is currently known as the fastest JSON parsing framework, and FastJSON 2 version has now been launched.

The first thing to introduce is JSONObject, which is used in the same way as Map and is ordered (implementing the LinkedHashMap interface). For example, we store several data in it:

@RequestMapping(value = "/index")
public String index(){
    
    
    JSONObject object = new JSONObject();
    object.put("name", "杰哥");
    object.put("age", 18);
    System.out.println(object.toJSONString());   //以JSON格式输出JSONObject字符串
    return "index";
}

The final result we get is:

{
    
    "name": "杰哥", "age": 18}

In fact, JSONObject is an object representation of JSON data. There is also JSONArray, which represents an array and is used in the same way as List. Other JSONObjects or JSONArrays can be nested in the array:

@RequestMapping(value = "/index")
public String index(){
    
    
    JSONObject object = new JSONObject();
    object.put("name", "杰哥");
    object.put("age", 18);
    JSONArray array = new JSONArray();
    array.add(object);
    System.out.println(array.toJSONString());
    return "index";
}

The result is:

[{
    
    "name": "杰哥", "age": 18}]

When a circular reference occurs, it will be parsed according to the following syntax:

img

We can also directly create an entity class and convert the entity class into JSON format data:

@RequestMapping(value = "/index", produces = "application/json")
@ResponseBody
public String data(){
    
    
    Student student = new Student();
    student.setName("杰哥");
    student.setAge(18);
    return JSON.toJSONString(student);
}

Here we modified producesthe value and set the returned content type to , indicating that the server returned a JSON format data (of course, it is okay not to set it, and it can also be displayed, this is for the sake of standardization) and then we add a representation application/jsonto the method The result @ResponseBodyreturned by the method (you can also add it to the class to @RestControllerindicate that this Controller returns string data by default) is not the view name but directly needs to return a string as the page data. In this way, what is returned to the browser is the character we return directly. string content.

Then we use the JSON tool class to convert it into a JSON format string, open the browser, and get the JSON format data.

SpringMVC is very smart, we can directly return an object type, which will be automatically converted to JSON string format:

@RequestMapping(value = "/data", produces = "application/json")
@ResponseBody
public Student data(){
    
    
    Student student = new Student();
    student.setName("杰哥");
    student.setAge(18);
    return student;
}

Note that you need to add a FastJSON converter to the configuration class. Here you need to add a dependency first:

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2-extension-spring6</artifactId>
    <version>2.0.34</version>
</dependency>

Then write the configuration:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
    converters.add(new FastJsonHttpMessageConverter());
}

Try again and the content will be automatically converted into JSON format and sent to the client.

Axios asynchronous request

Earlier we explained how to send data in JSON format to the browser, so now let's take a look at how to request data from the server.

img

In this part, we are going back to the introduction of front-end related content. Of course, we are still just understanding it and do not need to learn front-end project development knowledge in detail.

Why does the front end need to use asynchronous requests? This is because our web pages are dynamic (dynamic here does not mean animation effects, but the ability to update content in real time). For example, when we click a button, new content will pop up, or jump to New pages, updating data in the page, etc. all need to be implemented through JS to complete asynchronous requests.

Front-end asynchronous requests refer to sending requests to the server or other resources in the front-end without blocking the user interface or other operations. In a traditional synchronous request, when a request is sent, the browser waits for the server to respond, during which the user cannot perform other operations. Asynchronous requests, on the other hand, allow the user to continue other operations while waiting for a response by sending the request to the background. This mechanism improves user experience and allows pages to be updated in real time. Common front-end asynchronous request methods include using the XMLHttpRequest object, Fetch API, and using the AJAX method in the jQuery library, as well as the currently most commonly used Axios framework.

Suppose we have data in the backend that needs to be refreshed in real time (changes over time) and now needs to be updated and displayed in real time on the frontend. Here we take the simple use of the axios framework as an example to show you how to initiate an asynchronous request and update our page. The data.

The first is the front-end page, just copy the homework:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
</head>
<body>
  <p>欢迎来到GayHub全球最大同性交友网站</p>
  <p>用户名: <span id="username"></span></p>
  <p>密码: <span id="password"></span></p>
</body>
</html>

Then we use the axios framework to directly request JSON data from the backend:

<script>
    function getInfo() {
      
      
        axios.get('/mvc/test').then(({
       
       data}) => {
      
      
            document.getElementById('username').innerText = data.username
            document.getElementById('password').innerText = data.password
        })
    }
</script>

In this way, we have achieved data acquisition from the server and updated it to the page. Front-end developers use JS to initiate asynchronous requests, which can achieve various effects, while our back-end developers only need to care about the interface returning correct data. However, this already has the prototype of separate front-end and back-end development (in fact, we have demonstrated it before using XHR requests in the JavaWeb stage, but at that time it was pure data)

Then let’s look at how to send a JS object data to the server and parse it. Here we take a simple login as an example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
</head>
<body>
  <p>欢迎来到GayHub全球最大同性交友网站</p>
  <button onclick="login()">立即登录</button>
</body>
</html>

Here we still use axios to send POST request:

<script>
    function login() {
      
      
        axios.post('/mvc/test', {
      
      
            username: 'test',
            password: '123456'
        }, {
      
      
            headers: {
      
      
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).then(({
       
       data}) => {
      
      
            if(data.success) {
      
      
                alert('登录成功')
            } else {
      
      
                alert('登录失败')
            }
        })
    }
</script>

The server only needs to add an object to receive in the request parameter position (the same as before, because here is also the submitted form data):

@ResponseBody
@PostMapping(value = "/test", produces = "application/json")
public String hello(String username, String password){
    
    
    boolean success = "test".equals(user.getUsername()) && "123456".equals(user.getPassword());
    JSONObject object = new JSONObject();
    object.put("success", success);
    return object.toString();
}

We can also convert js objects into JSON strings for transmission. Here we need to use the ajax method to process:

<script>
    function login() {
      
      
        axios.post('/mvc/test', {
      
      
            username: 'test',
            password: '123456'
        }).then(({
       
       data}) => {
      
      
            if(data.success) {
      
      
                alert('登录成功')
            } else {
      
      
                alert('登录失败')
            }
        })
    }
</script>

If we need to read the JSON format data sent to us by the front end, then we need to add @RequestBodyannotations at this time:

@ResponseBody
@PostMapping(value = "/test", produces = "application/json")
public String hello(@RequestBody User user){
    
    
    boolean success = "test".equals(user.getUsername()) && "123456".equals(user.getPassword());
    JSONObject object = new JSONObject();
    object.put("success", success);
    return object.toString();
}

In this way, we have achieved communication between the front and back ends using JSON strings.

Implement file upload and download

Using SpringMVC, we can easily implement file upload and download. We need to add a new method in MainInitializer:

public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    

    ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
    
    
      	// 直接通过registration配置Multipart相关配置,必须配置临时上传路径,建议选择方便打开的
        // 同样可以设置其他属性:maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/Users/nagocoler/Download"));
    }
}

Then we can write the Controller directly:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public String upload(@RequestParam MultipartFile file) throws IOException {
    
    
    File fileObj = new File("test.png");
    file.transferTo(fileObj);
    System.out.println("用户上传的文件已保存到:"+fileObj.getAbsolutePath());
    return "文件上传成功!";
}

Finally, add a file upload point on the front end:

<div>
    <form action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <input type="submit">
    </form>
</div>

In this way, after clicking submit, the file will be uploaded to the server.

Downloading is actually roughly the same as what we wrote before, just use HttpServletResponse directly and transfer data to the output stream.

@RequestMapping(value = "/download", method = RequestMethod.GET)
@ResponseBody
public void download(HttpServletResponse response){
    
    
    response.setContentType("multipart/form-data");
    try(OutputStream stream = response.getOutputStream();
        InputStream inputStream = new FileInputStream("test.png")){
    
    
        IOUtils.copy(inputStream, stream);
    }catch (IOException e){
    
    
        e.printStackTrace();
    }
}

Add a download point to the front-end page:

<a href="download" download="test.png">下载最新资源</a>

Interpret DispatcherServlet source code

**Note:** This part is optional!

So far, we have learned almost everything about SpringMVC, but in the end we still need to have an in-depth understanding of how the underlying DispatcherServlet is scheduled, so we will explain it from the source code perspective.

First we need to find DispatcherServletthe top level HttpServletBean, which is directly inherited here HttpServlet, so let's first take a look at what it does in the initialization method:

public final void init() throws ServletException {
    
    
  	//读取配置参数,并进行配置
    PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
    
    
        try {
    
    
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
            this.initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        } catch (BeansException var4) {
    
    
            if (this.logger.isErrorEnabled()) {
    
    
                this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
            }

            throw var4;
        }
    }
		//此初始化阶段由子类实现,
    this.initServletBean();
}

Let's next look at initServletBean()how the method is implemented, which is FrameworkServletdefined in the subclass:

protected final void initServletBean() throws ServletException {
    
    
    this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
    
    
        this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
    }

    long startTime = System.currentTimeMillis();

    try {
    
    
      	//注意:我们在一开始说了SpringMVC有两个容器,一个是Web容器一个是根容器
      	//Web容器只负责Controller等表现层内容
      	//根容器就是Spring容器,它负责Service、Dao等,并且它是Web容器的父容器。
      	//初始化WebApplicationContext,这个阶段会为根容器和Web容器进行父子关系建立
        this.webApplicationContext = this.initWebApplicationContext();
        this.initFrameworkServlet();
    } catch (RuntimeException | ServletException var4) {
    
    
      //...以下内容全是打印日志
}

img

Let's take a look at initWebApplicationContexthow it is initialized:

protected WebApplicationContext initWebApplicationContext() {
    
    
  	//这里获取的是根容器,一般用于配置Service、数据源等
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
    
    
      	//如果webApplicationContext在之前已经存在,则直接给到wac
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
    
    
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
            if (!cwac.isActive()) {
    
    
                if (cwac.getParent() == null) {
    
    
                  	//设定根容器为Web容器的父容器
                    cwac.setParent(rootContext);
                }

                this.configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }

    if (wac == null) {
    
    
      	//如果webApplicationContext是空,那么就从ServletContext找一下有没有初始化上下文
        wac = this.findWebApplicationContext();
    }

    if (wac == null) {
    
    
      	//如果还是找不到,直接创个新的,并直接将根容器作为父容器
        wac = this.createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
    
    
        synchronized(this.onRefreshMonitor) {
    
    
          	//此方法由DispatcherServlet实现
            this.onRefresh(wac);
        }
    }

    if (this.publishContext) {
    
    
        String attrName = this.getServletContextAttributeName();
      	//把Web容器丢进ServletContext
        this.getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

Let's next look at the methods implemented in DispatcherServlet onRefresh():

@Override
protected void onRefresh(ApplicationContext context) {
    
    
    initStrategies(context);
}
    
protected void initStrategies(ApplicationContext context) {
    
    
  	//初始化各种解析器
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
  	//在容器中查找所有的HandlerMapping,放入集合中
  	//HandlerMapping保存了所有的请求映射信息(Controller中定义的),它可以根据请求找到处理器Handler,但并不是简单的返回处理器,而是将处理器和拦截器封装,形成一个处理器执行链(类似于之前的Filter)
    initHandlerMappings(context);
  	//在容器中查找所有的HandlerAdapter,它用于处理请求并返回ModelAndView对象
  	//默认有三种实现HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter
  	//当HandlerMapping找到处理请求的Controller之后,会选择一个合适的HandlerAdapter处理请求
  	//比如我们之前使用的是注解方式配置Controller,现在有一个请求携带了一个参数,那么HandlerAdapter会对请求的数据进行解析,并传入方法作为实参,最后根据方法的返回值将其封装为ModelAndView对象
    initHandlerAdapters(context);
  	//其他的内容
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

We have already understood the initialization process of DispatcherServlet, so let's take a look at how DispatcherServlet is scheduled. First, our request will definitely pass through, and then HttpServletit will be handed over to the corresponding doGet, doPost and other methods for processing. In FrameworkServletthe Rewrite it and use it processRequestfor processing:

protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
    this.processRequest(request, response);
}

Let's see what processRequest's done :

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 {
    
    
      	//重点在这里,这里进行了Service的执行,不过是在DispatcherServlet中定义的
        this.doService(request, response);
    } catch (IOException | ServletException var16) {
    
    
        //...
}

Please be patient. The bottom layers of these large frameworks are usually layers of matryoshka dolls, because the hierarchy will be clearer when written this way. So let’s take a DispatcherServletlook at

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
   //...
    try {
    
    
      	//重点在这里,这才是整个处理过程中最核心的部分
        this.doDispatch(request, response);
    } finally {
    
    
        //...
}

Finally found the core part:

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;
              	//在HandlerMapping集合中寻找可以处理当前请求的HandlerMapping
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
    
    
                    this.noHandlerFound(processedRequest, response);
                  	//找不到HandlerMapping则无法进行处理
                    return;
                }

              	//根据HandlerMapping提供的信息,找到可以处理的HandlerAdapter
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
    
    
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
    
    
                        return;
                    }
                }

              	//执行所有拦截器的preHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    
    
                    return;
                }

              	//使用HandlerAdapter进行处理(我们编写的请求映射方法在这个位置才真正地执行了)
              	//HandlerAdapter会帮助我们将请求的数据进行处理,再来调用我们编写的请求映射方法
              	//最后HandlerAdapter会将结果封装为ModelAndView返回给mv
                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);
            }

          	//最后处理结果,对视图进行渲染等,如果抛出异常会出现错误页面
            this.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);
        }

    }
}

Therefore, based on the above source code analysis, the final flow chart is obtained:

img

Although after completing this chapter, we have basically been able to rewrite a more advanced library management system based on Spring, but the complex problem of login verification has still not been solved. If we still write login verification in the previous way, it is obviously too simple. It is just a login, but without any permission division or encryption processing. We need a more advanced permission verification framework to help us implement the login operation. In the next chapter, we will explain in detail how to use the more advanced SpringSecurity framework. ASD.

Guess you like

Origin blog.csdn.net/qq_25928447/article/details/131562205