1.4W words! Let me take you to understand the world of springmvc!

Table of contents

1. Prerequisite understanding

1. What is the relationship between tomcat and servlet?

2. What are the conditions that springmvc must meet to achieve web development?

2. What is SpringMVC

3. Create a web project based on SpringMVC

① Create a project and select dependencies

 ②Set up hot deployment (some code changes can take effect without manually re-running)

4. Understand the development process of front-end and back-end separation

Five. SpringMVC realizes web development

1. Explain the response returned by the client in detail

@Controller

@ResponseBody

redirection and forwarding

Forward

redirect

The difference between forwarding and redirection: (M)

custom return type

@RestController

@RequestMapping

 2. Explain in detail how the server receives the client's request

2.1 About the parameters in the request path and request header

@Pathvirable

Use postman to analyze the test results:

@RequestHeader

@CookieValue

@SessionAttribute

2.2 About request parameters

Unannotated request parameters

@RequestParam

@RequestPart

@RequestBody

Set programmatic configuration schemes through configuration classes

Set the unified prefix of the backend path

Set interceptor

Unified exception handling

Unified result handling:

About the role of responseBodyAdvice


1. Prerequisite understanding

Before we really talk about springmvc, let's clarify the following two questions:

1. What is the relationship between tomcat and servlet?

We all know that servlet is a technology to realize dynamic pages, servlet is a specification for web development, and tomcat is a servlet container, which provides a web server and port number on the basis of conforming to the servlet specification, and realizes unified management of servlet; At the same time, it can receive the user's request, send the request to the servlet, and send the response to the client after the servlet gives the response

2. What are the conditions that springmvc must meet to achieve web development?

Through the above discussion, we can clearly understand that tomcat meets the servlet specification and develops web on this basis. If springmvc wants to realize web development, it must also meet the servlet specification.

2. What is SpringMVC

SpringMVC is a framework for web development. It also relies on the springboot framework (springboot includes springmvc). Benefiting from the principle that the agreement is greater than the configuration in the springboot framework, the springmvc file path is configured by default in springboot, and many defaults are made to springmvc configuration (no need for manual configuration by the user), springmvc has a built-in web server (equivalent to a customized server) that meets the servlet specification (further encapsulation of the servlet), and if we want to use springmvc for web development, only It needs to meet the usage specification of springmvc.

The purpose of using springmvc for web development is also very clear: to make web development more convenient and improve the efficiency of web development

3. Create a web project based on SpringMVC

① Create a project and select dependencies

 ②Set up hot deployment (some code changes can take effect without manually re-running)

 

③ Disable JMX: Because if it is not excluded, an error will be reported when the project starts. Although this error does not affect the implementation of our project, for the sake of standardization, we still add

 ④ Disable tomcat and replace it with undertow (not a mandatory option, because undertow is slightly more efficient than tomcat)

⑤ Modify the code set

4. Understand the development process of front-end and back-end separation

The MVC of springMVC is composed of the following three parts: ①Model (model)②View (view) ③Controller (controller) 

We combine the process of web request and response to understand the development process and the three components of springmc:

The user sends an http request, analyzes the request information through the controller layer combined with the model layer and gives a response, returns the response data to the view layer, and finally gives an http response through the view layer.

Combined with the specific release method to analyze this:

In fact, the professional logic should be like this:
 

Processing flow
The processing flow of DispatcherServlet can be divided into the following steps:

Receiving Client Requests
        When a client sends a request, the DispatcherServlet receives and processes the request. The way to receive requests depends on the configuration of DispatcherServlet. Usually, it will map the request to a URL, and then listen to the request of the URL.

Creating a request object
        DispatcherServlet will create a request object according to the client request, which contains all the information requested by the client, such as request method, request header, request parameters, etc.

Processing request mapping
DispatcherServlet will map the request to the corresponding controller for processing. Request mapping is done through HandlerMapping, which is responsible for mapping requests to one or more controllers, so that the most appropriate controller can be selected for processing.

Calling the controller
        DispatcherServlet will call the corresponding controller for processing, and the controller will perform corresponding processing according to the request parameters and business logic, and return a ModelAndView object.

Rendering the view
        DispatcherServlet will pass the ModelAndView object to the view resolver (ViewResolver), and the view resolver will resolve the corresponding view object according to the view name in the ModelAndView. The DispatcherServlet then passes the model data to the view object in order to render the view. Finally, the view object will generate the corresponding response result and return it to the client.

 

Five. SpringMVC realizes web development

1. Explain the response returned by the client in detail

@Controller

@controller indicates that the class modified by this annotation is a bean (generally only used to modify the class), and this bean is responsible for processing web requests and responses

@ResponseBody

@ResponseBody indicates the response body, which specifies the type of the return value (it can be used to modify both classes and methods. The modified class means that all methods set the return value type, and the modified method means that only this method specifies the return value type), @ ResponseBody stipulates that the return value of the method modified by it is returned in a specific data format, and the default return form is json

redirection and forwarding

If @ResponseBody is not used to specify the format of the return value type, the default return value type is String and represents a path : this path is generally divided into two application scenarios: forwarding and redirection

Forward

Syntax format: forward:/+path

Capture packet view features:

①The resource with only one request and response response is an html page

 

redirect

Grammar format: "redirect:/+path"

Capture packet view features:

First request: 

 First response:

Second request:

Second response:

The difference between forwarding and redirection: (M)

1. Redirect access to the server twice, forwarding only access to the server once.

2. The URL of the forwarded page will not change, but the redirected address will change

3. Forwarding can only be forwarded to your own web application, and redirection can be redefined to any resource path.

4. Forwarding is equivalent to server jump, which is equivalent to method call. During the execution of the current file, it turns to execute the target file. The two files (current file and target file) belong to the same request, and the front and back pages share a request. You can use this To pass some data or session information, request.setAttribute() and request.getAttribute(). Redirection will generate a new request, and the request domain information and request parameters cannot be shared

5. Since the forwarding is equivalent to the internal method call of the server, the code behind the forwarding will still be executed (remember to return after the forwarding); after the redirection code is executed, the redirection operation is performed after the method execution is completed, that is, access to the second request, if If it is the last line of the method to redirect, it will be redirected immediately (redirection also requires return).
 

custom return type

In addition to using @ResponseBody to set the return value type and perform forwarding and redirection, you can also implement a custom return value type, as follows:

Let's take an example to illustrate: we transmit a .doc object to the client (network resource download)

1. Create a file path object

2. Read the byte array in the path

3. Set a custom return object

@Controller
public class SelfController {
    @GetMapping("/object1")
    public ResponseEntity test() throws IOException {
        //创建返回值
        //传输字节码文件
        Path p=new File("D:\\大物实验报告\\42109211014_20221018142451.doc").toPath();
        byte[]bytes= Files.readAllBytes(p);
      return   ResponseEntity.ok().header("content-type", "application/msword").body(bytes);
    }
}

We use the path to access

@RestController

For the time being, we can simply understand its function as: the combined annotation of @Controller and @Responsebody

How to use: before loading the class

@RequestMapping

@RequestMapping represents the request path. This annotation can be added to both the class and the method. If this annotation is added to both the class and the method, the request path is expressed as: class path + method path

Instructions:

Some attributes:

●value: defines the mapping address of the request request

●method: Define the method of the request address, including [GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.] By default, the get request is accepted. If the request method is different from the defined method, the request will not succeed.

●params: Define the parameter values ​​that must be included in the request request.

 2. Explain in detail how the server receives the client's request

2.1 About the parameters in the request path and request header

@Pathvirable

Usage: used to modify formal parameters

@Pathvirable Function: Identify the dynamic parameters in the request path (read the variable with the same name as the method parameter variable modified by @Pathvirable from the request path using @RequestMapping, and read it to the formal parameter variable with the same variable name in the method middle.)

Code demo:

@RestController
public class PathTest {
    @RequestMapping("/test/{id}")
    public Object test(@PathVariable Long id){
        //创建容器
        HashMap<String, Long> stringLongHashMap = new HashMap<>();
        stringLongHashMap.put("id", id);
        return  stringLongHashMap;
    }

Use postman to analyze the test results:

 The variable type modified by @pathvirable can verify the variable from the request path. If the parameter in the request path does not match the type of the parameter, a 400 error will be reported

@RequestHeader

Function: Obtain the field information of the request header by binding the field name in the request header

Example usage:

   @RequestMapping("/header")
    public Object getHeader(@RequestHeader("user-agent") String headers){
        HashMap<String, String> stringLongHashMap = new HashMap<>();
        stringLongHashMap.put("user-agent", headers);
        return  stringLongHashMap;
    }

@CookieValue

Function: Obtain the information in the cookie by binding the key name in the cookie

How to use:

    @RequestMapping("/cookie")
    public Object getCookie(@CookieValue("JSESSIONID") String cookie){
        HashMap<String, String> stringLongHashMap = new HashMap<>();
        stringLongHashMap.put("my-cookie", cookie);
        return  stringLongHashMap;

@SessionAttribute

Before explaining this, let's briefly talk about the relationship between cookie and session: session is stored on the server side, and the server uses it to save user information. We can understand session as a key-value pair, and the key is a randomly generated sessionId. The value is the session object of the current user; the cookie is saved to the client, which is also used to save user information. When verifying user information, match the corresponding sessoin object on the server according to the sessionId for identity verification

@SessionAttribute is used to obtain the session information in the request parameter (obtain the session object on the server according to the sessionId, and then verify the user information)

Example of use:
 

 @RequestMapping("/login")
    public Object info(HttpServletRequest request){
        //通过session存储session信息
        HttpSession session = request.getSession(true);
        //存储session信息
        session.setAttribute("user", "zhangsan");
        //创建容器并返回
        HashMap<String, String> map = new HashMap<>();
        map.put("user", "zhangsan");
        return map;
    }
    @RequestMapping("/check")
    public Object checkLogin(@SessionAttribute("user") String name){
        HashMap<String, String> map = new HashMap<>();
        map.put("user", name);
        return map;
    }

Generally, the general process of login verification is as follows:

When logging in for the first time, check whether the session object exists (if it does not exist, create a new session object), then store it according to the user information from the front end, return a response, and then perform other operations (need to be based on user login) You need to obtain the session object from the server according to the sessionId for verification

Result analysis:

2.2 About request parameters

Unannotated request parameters

 The front-end request parameters are usually divided into four data formats for transmission: query-string, form type data, json format data and form-data type data, and the java object that our backend receives the request, we divide it into Simple data types (basic data types + wrapper classes + String) and complex data types (collection framework and custom types), we will analyze these two data types below; we will give a conclusion first: whether it is a simple data
type It is still a complex data type. In the absence of annotations, only query-string can be parsed at the back end. Form type data, json format data, json type data need to use annotations ( @RequestBody ), we draw a The table explains this:

We analyze these front-end data types separately:
 

@RequestParam

@RequestParam (can modify other types except json, and can also modify collections such as map and list)

Parameters: vlaue/name: the name used to identify the request parameter (the name of the request parameter in the request path), this parameter can also not be passed, as long as the parameters in the method and the request path parameters are completely consistent

required: Whether this parameter must be provided in the request parameter: FALSE means that it is not required, and TRUE means that it must be provided

If the parameter type does not match or the required parameter is not provided, a 400 error will be reported

Role: Convert request parameters into formal parameters of controller methods

Example of use:
 

@RequestMapping("/requestB")
    public Object res(@RequestParam String name ,@RequestParam() Integer id){
        HashMap<String, String> map = new HashMap<>();
        map.put("name", name);
        map.put("id", id+"");
        return map;
    }

@RequestPart

@RequestPart is generally used to multipart/form-datamap type data to parameters of controller processing methods

Annotation analysis

  ① value:

    Binding parameter name, parameter value is String type.

  ② name:

    Binding parameter name, parameter value is String type. name and value can be used at the same time, but the values ​​of the two must be consistent, otherwise an error will occur. (400)
  ③ required:

    Whether the specified value must be included in the request header, the default value is true.

    When required is true, an exception will be thrown if the specified value is missing from the request header.

    When required is false, null will be returned if the specified value is missing from the request header.

Use case:

    @RequestMapping("/upload")
    //上传文件
    public Object upload(@RequestPart MultipartFile head, User user) throws IOException {
       head.transferTo(new File("D:/上传的"+head.getOriginalFilename()));
        HashMap<String, Object> map = new HashMap<>();
        map.put("file", head);
        return map;

    }

Possible errors in file upload: The system cannot find the specified file
java.io.FileNotFoundException: C:\Users\86131\AppData\Local\Temp\tomcat.8080.6906634590984434583\work\Tomcat\localhost\ROOT\upload_135c4883_3414_49af_b54e_39dbe063b0de _00000002.tmp (the system finds The specified file was not found.)

Let's go to the directory specified by the system to check whether the file is uploaded successfully:

It was found that although an error was reported, the file was uploaded successfully:

Before analyzing the cause of the error, we first analyze the logic of file upload: when the client transmits a file to the server, the file first reports an error to the system network card and then saves part of the file information to the server memory, and the relevant file content will be saved in a local temporary directory, if we call transferTo(), the default saved system files will be moved to the file directory we specified, in this case the files in the original temporary directory will be gone, so when we call the relevant file information later The method is to first search in the memory of the server. If there is no such information in the memory of the server, you need to search in the temporary directory. If there is no such file in the temporary directory, the above error will be reported.

How to solve this problem?

The solution is also relatively clear: when calling file-related information, just call it before the transferTo() method is executed.

@RequestBody

As we mentioned earlier, whether it is an unannotated request parameter or using the @RequestParam annotation, it does not support the parsing of json data, while @RequestBody can realize the parsing of json data , but does not support the parsing of other data types.

Example of use:

  @RequestMapping("/json")

    public Object jsonSend( @RequestBody User user){
        HashMap<String, String> map = new HashMap<>();
        map.put("name", user.getName());
        map.put("id", user.getId()+"");
        return map;
    }

Set programmatic configuration schemes through configuration classes

We know that the springboot project can be set by setting the format of the configuration file, but it is cumbersome to set through the configuration file or we can set the configuration through programming (setting the configuration class) that cannot be achieved through the configuration file. This function is realized by @Configuration: the class modified by @Configuration will load the configuration when the springboot project starts. We use the following two examples to load the description:

Set the unified prefix of the backend path

Function: Add a unified prefix for all backend controllers (named: api)

Application scenario: When accessing the back-end request path, it is necessary to add some unified verification to the processing logic of some of its paths. In order to distinguish the front-end path from the back-end path, we uniformly add a prefix to all controllers.

We give code examples:

Because we are doing configuration related to web development, our configuration should inherit the interface of WebMvcConfigurer

@Configuration
public class AppConfig implements WebMvcConfigurer {
    //调用路径匹配的api,为所有的后端逻辑添加前缀
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
      configurer.addPathPrefix("api", c->{
          //通过循环遍历所有的后端controller判断是否要加前缀(暂时设置所有的路径都为前缀)
          //如果需要在此处使某些控制器不加前缀,在此处应该加一些别的逻辑经即可
         return true;
      });
    }

We do not have an api request path in the actual path, but we must access it through the "api" path when we visit, otherwise we will report 404 directly

 

Set interceptor

Let's first understand the three major components of web development: servlet (connector), listener (listener) and filter (filter). We need to be clear: spring does not provide interceptors officially, and interceptors are provided by springmvc. But its implementation principle is similar to that of filters.

The processing logic of the interceptor is as follows:

 Before the client sends back the request, it will be processed by the interceptor preHander related method. The preHander returns a Boolean value, TRUE will continue to execute downward, and FALSE will return directly; after the controller layer returns the response, it will also pass through the interceptor. At this time, Call the postHander() method, and finally return the response to the client. Our custom interceptor usually overrides the following three methods:

1. The preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) method is called before the request is processed. This method is executed first in the Interceptor class, and is used to perform some pre-initialization operations or preprocess the current request, and also make some judgments to determine whether the request should continue. The return of this method is of Boolean type. When it returns false, it means that the request is over, and the subsequent Interceptor and Controller will not be executed; when it returns true, it will continue to call the preHandle method of the next Interceptor, if it is the last When an Interceptor is called, the Controller method of the current request will be called.

2. The postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) method is executed after the current request is processed, that is, after the Controller method is called, but it will be called before the DispatcherServlet returns the view to render, so we can use this In the method, the ModelAndView object processed by the Controller is operated.

3. The afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) method will only be executed when the return value of the preHandle method of the corresponding Interceptor class is true. As the name implies, this method will be executed after the entire request ends, that is, after the DispatcherServlet renders the corresponding view. This method is mainly used for resource cleanup.

The process of setting up interceptors can be designed and analyzed from the following three aspects: ①Customizing interceptors ②Setting interception paths ③Setting the processing logic of interceptors

We give the following three codes in an interceptor:
①Path processing:

Configure the interception path to support fuzzy matching: the configuration method is generally handled by adding interception paths and excluding some paths that are not intercepted

/**: Add any path

/api/**: Add any path under api

/api/*: Add a layer of directory under api

Set exclusion path:

/api/login: exclude the login directory under api

/api/register: Exclude the register directory under api

②Custom interceptor:

Customize the interceptor and reference the HandlerInterceptor interface, and then rewrite the methods in the interface

③ Realize method logic

Implement the method logic in the overridden method

Full code:

@Configuration
public class AppConfig implements WebMvcConfigurer {
    //调用路径匹配的api,为所有的后端逻辑添加前缀
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
      configurer.addPathPrefix("api", c->{
          //通过循环遍历所有的后端controller判断是否要加前缀(暂时设置所有的路径都为前缀)
         return true;
      });
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       //使用注册器进行注册过滤器
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api/**").excludePathPatterns("/api/login")
                        .excludePathPatterns("/api/register");
    }
}
public class LoginInterceptor implements HandlerInterceptor {
    //设置请求逻辑:在请求前进行处理
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //获取session
        HttpSession session = request.getSession(false);
        if(session!=null){
            String user = (String)session.getAttribute("user");
            if(user!=null){
                //判断是否是用户
                if(user.equals("admin")){
                    return true;
                }
            }
        }
        response.setStatus(401);

        return false;
    }
}

Interceptor function
Logging: record the log of request information for information monitoring, information statistics, PV (Page View) calculation, etc.; authority check: such as
login detection, enter the processor to detect whether the user is logged in;
performance monitoring: through the interceptor when entering The processor records the start time before, and records the end time after processing, so as to obtain the processing time of the request. (Reverse proxy, such as Apache can also automatically record)
General behavior: Read Cookie to get user information and put the user object into the request, so as to facilitate the use of subsequent processes, as well as extract Locale, Theme information, etc., as long as there are multiple processors All that is needed can be implemented using interceptors.

Unified exception handling

If an error occurs when we make a request through the browser, it is very likely that all the error information of the backend will be displayed on the browser. destructive operation), on the other hand, it is not very friendly to the user experience.

Therefore, we need to carry out unified exception handling and unify back-end error reporting. We perform the following operations: use @ControllerAdvice annotation for controller enhancement, use @ExceptionHandle ( exception class) for unified exception handling, @ExceptionHandle (exception class) The processing logic is as follows: According to the exception class information in the brackets, when the error message in the exception class in the brackets occurs, use the following method to process the error report (the function is the catch of exception handling)

@ControllerAdvice
public class ExceptionHandle {
    //默认以json形式返回数据
    @ResponseBody
    //统一捕获异常类
    @ExceptionHandler(Exception.class)
    public Object handle(Exception e){
        HashMap<String, Object> map = new HashMap<>();
        map.put("code",500);
        map.put("message",e.getMessage());
        return map;
    }

It should be noted that: @ExceptionHandle (exception class) can only capture the exceptions in its brackets. Once it is out of the scope of the exception class in its brackets, it will directly return the error information to the client: the modified error message is as follows:

@ControllerAdvice
public class ExceptionHandle {
    //默认以json形式返回数据
//    @ResponseBody
//    //统一捕获异常类
//    @ExceptionHandler(Exception.class)
//    public Object handle(Exception e){
//        HashMap<String, Object> map = new HashMap<>();
//        map.put("code",500);
//        map.put("message",e.getMessage());
//        return map;
//    }
    @ResponseBody
    //统一捕获异常类
    @ExceptionHandler(IOException.class)
    public Object handleIo(Exception e){
        HashMap<String, Object> map = new HashMap<>();
        map.put("code",500);
        map.put("message",e.getMessage());
        return map;
    }

Unified result handling:

①Custom result class

@Data
public class Result {
    private Boolean ok;
    private Object data;
    private String error;
}

② Use @ControllerAdvice to process the results uniformly: set ok data (data is processed directly in the returned body) and error

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //进行赋值处理
        Result result = new Result();
        result.setOk(true);
        result.setData(body);
        result.setError(null);
        return result;
    }
}

We use this result class to access the following code:

import java.util.HashMap;

/**
 * @author tongchen
 * @create 2023-04-28 10:34
 */
@RestController
public class UserController {
    @RequestMapping("/user/{id}/{name}")
    public Object user(@PathVariable String name,@PathVariable Integer id ){
        HashMap<String, Object> map = new HashMap<>();
        map.put("user", name);
        return map;
    }
}

The result is as follows:

But we found a problem. In this code, we directly write the returned information except data . We think about a question: In real business scenarios, how should we uniformly process the returned results? (abnormal result processing and unified result processing)

A: Modify the return class, cancel the unified setting of the return method, and return the result in a specific business scenario, and return the result separately for the exception class

The specific code and results are as follows:

@Data
public class Result<T> {
    private Integer ok;//1代表成功 0代表失败
    private T data;
    private String msg;//正确或异常的信息
    public static <T>Result<T> success(T data){
        Result<T> result = new Result<>();
        result.setData(data);
        result.setOk(1);
        return result;
    }
    public static  <T>Result<T>error(String msg){
        Result<T> result = new Result<>();
        result.setOk(0);
       result.setMsg(msg);
        return result;
    }
}

 Normal result class:

@RestController
public class UserController {
    @RequestMapping("/user/{id}/{name}")
    public Object user(@PathVariable String name,@PathVariable Integer id ){
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("name",name);
        return Result.success(map);
    }
}

Exception result class:

public class MyException extends Exception{
    public MyException(String message) {
        super(message);
    }
}
@RestController
public class ExceptionController {
    @RequestMapping("/test")
    public void test() throws MyException {
        throw new MyException("HAHAHH");
    }
}

Exception result class handling:
 

@ControllerAdvice
public class ExceptionHandle {
    //默认以json形式返回数据
//    @ResponseBody
//    //统一捕获异常类
//    @ExceptionHandler(Exception.class)
//    public Object handle(Exception e){
//        HashMap<String, Object> map = new HashMap<>();
//        map.put("code",500);
//        map.put("message",e.getMessage());
//        return map;
//    }
    @ResponseBody
    //统一捕获异常类
    @ExceptionHandler(MyException.class)
    public Object handleIo(Exception e){
        HashMap<String, Object> map = new HashMap<>();
      return Result.error("服务器错误");
    }


}

Normal result:

 Exception result:

About the role of responseBodyAdvice

 

 The ResponseBodyAdvice interface is to perform some processing on the response after the Controller executes the return and before the response is returned to the client, which can realize some unified encapsulation or encryption of the response data.

  This interface has two methods:

(1) supports——judging whether to execute the beforeBodyWrite method, true to execute, false not to execute——through the supports method, we can choose which classes or methods to process the response, and the rest will not be processed.

(2) beforeBodyWrite - the specific execution method for response processing.

Guess you like

Origin blog.csdn.net/m0_65431718/article/details/130361545