Spring MVC

How to understand MVC architecture and three-tier architecture

Disadvantages and solutions of the three-tier architecture

Disadvantages:

  1. Each function has to declare a corresponding Servlet, and there are too many servlets. Even if a Servlet includes more than one method, it cannot fundamentally solve the problem of the Servlet including more than one method, nor can it fundamentally solve the problem of the number of servlets.
  2. Getting the request data in a Servlet is tricky: a lot of requestt.getParameter(” name “) is required.
  3. Manual conversion: for example int age = integer.parseint (request.getParameter(” age “))
  4. The code that responds only wants to declare the corresponding response data. Such as: request. GetRequestDispatcher (path). The forward (request, response); The most critical is path.

Solution: MVC frameworks like SpringMVC.

Spring’s solution?

  1. The project declares only one Servlet, which serves as the public entry point for the project request. In this Servlet, the corresponding control code is invoked to process the request based on the request address.

    Declaring all the control methods (previously Servlet calls the business layer, indicates jump paths, and so on) in the Servlet makes the code architecturally unclear.

  2. Declare control methods separately into the control class (the Controller class).

    Then the Servlet dynamically calls the control method in the corresponding control class to process the request.

Known for SpringMVC

SpringMVC (full name Spring Web MVC) is a framework design provided by Spring for Web applications, which is actually part of Spring. It is a servlet-based technology that provides the core controller and associated components, with a loose structure tailored to suit a variety of flexible needs.

SpringMVC has become one of the most mainstream MVC frameworks. Since the release of Spring 2.5, ease of use has improved dramatically with support for annotation configuration. With the release of Spring3.0, it has comprehensively surpassed Struts2 and become the best MVC framework. Currently, the industry generally chooses SpringMVC as the preferred solution for Java EE project presentation layer development.

  1. Native to the Spring family, seamlessly integrated with infrastructure such as the IOC container

  2. The problems that need to be solved in each subdivision of the expression layer are covered comprehensively and comprehensive solutions are provided

  3. The code is fresh and concise, greatly improving the development efficiency

  4. Outstanding performance, especially suitable for modern large, super large Internet project requirements

  5. Support RESTful programming style requests.

  6. Adaptable, non-intrusive: You can select the appropriate controller subclass (Simple, Command, FROM, Wizard, multi-Action, or Custom) instead of a single controller (such as Action/ActionForm), depending on the application scenario.

  7. Because the Model data is not stored in a specific API, but in a Model (Map data structure implementation, therefore easy to use by other frameworks)

  8. Simpler, more powerful exception handling

In short, it’s easy to use, performs well, and has the highest market share of any technology in its class.

Creating a Web project

Create a Maven Java project

2. Add Web support [JBLJavaToWeb plugin into Web project]

3 Add Web resources

4. Deploy and access

Html web pages: Accessible

Images: also accessible

Thymeleaf Web pages: You cannot access them directly, they must be parsed by the Thymeleaf parser before jumping to the page, so in a project you usually put Thymeleaf Web pages in web-INF /templates

Basic implementation of Hello SpringMVC

1. Add dependencies

<dependencies>
    <! -- ServletAPI -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <! -- SpringMVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</version>
    </dependency>
    <! -- Spring5 and Thymeleaf integration package
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.12. RELEASE</version>
    </dependency>
</dependencies>

Copy the code

2.web.xml

<! -- Configure SpringMVC master controller, unique Servlet -->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <! -- Specify the name and location of the SpringMVC configuration file, with default location -->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <! Load the master controller when you start the server
    <load-on-startup>1</load-on-startup>
</servlet>
<! -- Configure the master controller access path -->
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <! - that can be * do *. ABC, simulation Struct2, Struct, require all the access path points controller methods must be with the ending of the action In this configuration, HTML, CSS js PNG JPG without total controller -- -- >
    <url-pattern>*.action</url-pattern>
</servlet-mapping>


Copy the code

3. Configure the spring-mvC.xml file

The SpringMVC configuration file is the Same as the Spring configuration file. If the project is large, you can split it into multiple configuration files.

<! -- Configure annotation scan base path: @controller@servicer@repository @component -->
<context:component-scan base-package="com.dyy.controller"></context:component-scan>

<! -- Thymeleaf view parser -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
            <property name="templateResolver">
                <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                    <! -- View prefix -->
                    <property name="prefix" value="/WEB-INF/templates/"/>
                    <! -- View suffix -->
                    <property name="suffix" value=".html"/>
                    <property name="templateMode" value="HTML5"/>
                    <property name="characterEncoding" value="UTF-8" />
                </bean>
            </property>
        </bean>
    </property>
</bean>

Copy the code

4. Develop the view layer page

<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a th:href="@{/hello/login.action}">Enter the</a>
</body>
</html>


Copy the code
<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>The results page</h3>
    <p th:text="${msg}"> </p>
</body>
</html>

Copy the code

5. Develop sub-controllers

Sub-controllers do not need to inherit or implement other classes or interfaces.

The return value of the method is String, which corresponds to the logical view of the forwarded page. The master controller is responsible for adding the prefix and suffix to form the physical view, parsing and rendering, and returning it to the client.

The @requestMapping method is used to specify the access path for the method. Because the current access path for the master Controller is *. Action, all Controller method access paths must end with action.

@Controller
public class HelloController {//Handler

    @RequestMapping("/hello/toIndex2.action")
    public String toIndex2(a){
        System.out.println("------HelloController toIndex2-------");
        //return "/WEB/INF/templates/index2.html";
        return "index2";
    }
    @RequestMapping("/hello/login.action")
    public String login(Model model){
        System.out.println("-------HelloController login--------");
        model.addAttribute("msg"."login success!!");
        return "result"; }}Copy the code

6. Deploy access

Note: You can set the request path of a sub-controller to /, which is automatically accessed after the project starts. The premise is:

  1. Get rid of welcome pages such as index.html
  2. Will the total controller<url-pattern>Set to /, but add a comment to set to / :<mvc:annotation-driven></mvc:anntotation-driven>To allow access to the sub-controller, but static resources cannot be accessed, add a comment:<mvc:default-servlet-handler></mvc:default-servlet-handler>Static resources can also be accessed normally.

Hello SpringMVC improvements

1. Add logs

  1. Add the dependent

    <! - log - >
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>
    Copy the code
  2. Add properties file [log-back.xml]

    
            
    <configuration debug="true">
        <! -- Specify log output location -->
        <appender name="STDOUT"
                  class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <! -- Log output format -->
                <! Time, log level, thread name, class to print logs, log body content, newline -->
                <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
            </encoder>
        </appender>
    
        <! -- Set the global log level. The log levels are DEBUG, INFO, WARN, and ERROR -->.
        <! -- Specifies that only logs at the current and later levels are printed at any log level. -->
        <root level="INFO">
            <! -- Specify the appender to print logs. Here we reference the previously configured appender with "STDOUT".
            <appender-ref ref="STDOUT" />
        </root>
    
        <! -- Specify the local log level according to special requirements -->
        <logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" />
    
        <! -- Specify log level of package -->
        <logger name="com.atguigu.controller" level="DEBUG" />
    </configuration>
    
    Copy the code
  3. Add logs to sub controllers

    @Controller
    public class UserController {
    
        private Logger logger = LoggerFactory.getLogger(UserController.class);
    
        @RequestMapping("/")
        public String toLogin123(a){
            //return "/WEB-INF/templates/login.html";
            logger.debug("------UserController toLogin123 ----");
            // Call the business layer if necessary
            return "login";
        }
        @RequestMapping("hello/login.action")
        public String login(Model model){
            // Invoke the business layer to complete the login function
            logger.debug("-----UserController login ---------");
            model.addAttribute("msg"."Login successful");
            return "result"; }}Copy the code
  4. Use simple Slf4j logging (Lombok’s logging annotations) @slf4j

    @Controller
    @Slf4j
    public class UserController {
        //private Logger logger = LoggerFactory.getLogger(UserController.class);
        @RequestMapping("/")
        public String toLogin123(a){
            //return "/WEB-INF/templates/login.html";
            log.debug("------UserController toLogin123!! -");
            // Call the business layer if necessary
            return "login";
        }
        @RequestMapping("hello/login.action")
        public String login(Model model){
            // Invoke the business layer to complete the login function
            log.debug("-----UserController login!! -- -- -- -- -- -- -- -- --");
            model.addAttribute("msg"."Login successful");
            return "result"; }}Copy the code

2. Access path of the controller

Resources in a Web project can be divided into:

  1. Static data
  2. JSP resources [now used less]
  3. Access to sub-controllers

Access path of the master controller:

  1. *. Action access to static and JSP resources without going through the master controller was once the most popular way. Disadvantages: Does not support Restful style.
  2. /* All requests pass through, but JSPS and static resources should not.
  3. / This is the only place you can write it. Access to sub-controllers and static resources passes through, but not access to JSPS. The advantage is that Restful style is supported. This is currently recommended. The action suffix is not important, but the suffix is not recommended in Restful style. The disadvantage is that static resources will also pass through, which is not necessary and can cause problems.

How to let go of static resource requests?

Solution:

<! -- Enable annotation driver, do a lot of things behind,
<mvc:annotation-driven></mvc:annotation-driven>
<! Static resources also pass through the master controller when the master controller is set to /. In the master controller, static resources are treated in a special way, instead of finding controllers by path.
<mvc:default-servlet-handler></mvc:default-servlet-handler>

Copy the code

RequestMapping

1. The role

As you can see from the name of the annotation, the @requestMapping annotation is used to establish a mapping between the requested URL and how the request is processed.

Position 2.

Write on class @requestMapping (” /hello “)

Write on method @rquesetMapping (” /login “)

The request path for the final method is the connection on class and method: /hello/login

Use 3.

① Exact matching

<a th:href="@{/hello/login.action}">Submit the login</a><br>
<a th:href="@{/hello/to/spring/mvc}">HelloWorld</a><br/>

Copy the code
@RequestMapping("hello/login.action")
public String login(Model model){
    // Invoke the business layer to complete the login function
    log.debug("-----UserController login!! -- -- -- -- -- -- -- -- --");
    model.addAttribute("msg"."Login successful");
    return "result";
}
@RequestMapping("/hello/to/spring/mvc")
public String login2(Model model){
    model.addAttribute("msg"."/hello/to/spring/mvc!!");
    log.debug("-----UserController login2 /hello/to/spring/mvc------");
    return "result";
}
Copy the code

② Fuzzy matching

<a th:href="@{/hello/fruit/apple}">@requestMapping Fuzzy matching [apple]</a><br/>
<a th:href="@{/hello/fruit/orange}">@requestMapping Fuzzy matching [orange]</a><br/>
<a th:href="@{/hello/fruit/banana}">@requestMapping Fuzzy matching [banana]</a><br/>

Copy the code
@RequestMapping("/hello/fruit/*")
public String login3(Model model){
    model.addAttribute("msg"."/hello/fruit/*");
    log.debug("-----UserController login3 /hello/fruit/*------");
    return "result";
}

Copy the code

Other writing

@RequestMapping(value = "/to/spring/mvc",method = RequestMethod.GET )
Copy the code
@GetMapping("/to/spring/mvc")
Copy the code
@PostMapping("/to/spring/mvc")
Copy the code

4. View controller

If a separate controller method only serves to jump to a page, use < MVC :view-controller> instead.

The following two functions are equivalent.

@RequestMapping("/")
public String toLogin123(a){
    //return "/WEB-INF/templates/login.html";
    log.debug("------UserController toLogin123?? -");
    // Call the business layer if necessary
    return "login";
}
Copy the code
<! Configure the view controller -->
<mvc:view-controller path="/" view-name="login"></mvc:view-controller>

Copy the code

Get request parameters

1. One person, one value

  1. Receive directly with variables of the same name
  2. Or use @requestParam to declare which variable to correspond to, with the name of the control in parentheses
<h3>Gets the request parameters - one name, one value</h3>
<a th:href="@{/hello/login1(username=zhangsan,password=123456)}">Log in 1</a><br>
<a href="/springmvc/hello/login2? username=zhangsan&password=123456">To log in 2</a><br>
<a th:href="@ {/ hello/register (username = zhangsan, the age = 12, salary = 600.0)}">registered</a><br>
<a th:href="@{/hello/register(username=zhangsan,age=12)}">Registered 2</a><br>
Copy the code
@Controller
@Slf4j
@RequestMapping("/hello")
public class UserController { //UserHandler
    @RequestMapping("/login1")
    public String login(String username,String password){
        log.debug("login:"+username+""+password);
        return "result";
    }
    @RequestMapping("/login2")
    public String login2(@RequestParam("username") String uname, @RequestParam("password") String pwd){
        log.debug("login:"+uname+""+pwd);
        return "result";
    }
    @RequestMapping("/register")
    public String register(String username,Integer age,double salary){
        log.debug("login:"+username+""+age+""+salary);
        return "result";
    }
    @RequestMapping("/register2")
    public String register2(String username,Integer age,
         @RequestParam(value = "salary",required = false,defaultValue = "800") double salary){
        log.debug("login:"+username+""+age+""+salary);
        return "result"; }}Copy the code

2. Multiple value

It needs to be received with a List, and the generic type needs the corresponding type. Use the @requestParam declaration.

<h3>Gets the request parameter - one - many-value</h3>
<form th:action="@{/hello/getTeams}" method="post">Please choose your favorite team:<input type="checkbox" name="team" value="Brazil"/>Brazil<input type="checkbox" name="team" value="German"/>Germany<input type="checkbox" name="team" value="French"/>The French<input type="checkbox" name="team" value="Holland"/>In the Netherlands,<input type="checkbox" name="team" value="Italian"/>Italy<input type="checkbox" name="team" value="China"/>China<br/>
    <input type="submit" value="Save"/>
</form>

Copy the code
@RequestMapping("/getTeams")
public String getTeam(@RequestParam("team") List<String> teamList){
    log.debug("teamList:"+teamList);
    return "result";
}

Copy the code

3. The entity class

Receive directly with the entity class

<h3>Gets the request parameter - entity class</h3>
<form th:action="@{/hello/addEmp}" method="post">Name:<input type="text" name="empName"/><br/>Age:<input type="text" name="empAge"/><br/>Salary:<input type="text" name="empSalary"/><br/>
    <input type="submit" value="Save"/>
</form>
Copy the code
@RequestMapping("/addEmp")
public String addEmp(Employee emp123){
    log.debug("emp:"+emp123);
    return "result";
}

Copy the code

Note 1: Write entity class attributes directly on the page, such as empName, not emp123.empname

Note 2: the sub-controller accepts the parameters of the entity class directly, regardless of the variable name. The underlying call getter and setter methods for the corresponding property

Note 3: Solve the POST Chinese garble problem

<! -- Filter to resolve Chinese garbled characters in Post requests -->
<filter>
   <filter-name>CharacterEncodingFilter</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
   <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
   </init-param>
   <init-param>
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
   </init-param>
   <init-param>
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
   </init-param>
</filter>
<filter-mapping>
   <filter-name>CharacterEncodingFilter</filter-name>
   <url-pattern>/ *</url-pattern>
</filter-mapping>

Copy the code

4. Entity classes have cascading properties

Receive directly using the class.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String stuName;
    private School school;
    private List<Subject> subjectList;//List
    //private Subject[] subjectArray; //Array
    //private Set
      
        teacherSet; //Set ?? Not recommended
      
    private Map<String, Double> scores;//Map
}

Copy the code
<h3>Get request parameters - The entity class contains cascading properties</h3>
<form th:action="@{/hello/addStu}" method="post">Name:<input type="text" name="stuName"/><br/>School No. :<input type="text" name="school.schoolNo"/><br/>School Name:<input type="text" name="school.schoolName"/><br/>Course 1 No. :<input type="text" name="subjectList[0].subjectId"/><br/>Course 1 Name:<input type="text" name="subjectList[0].subjectName"/><br/>Course 2 No. :<input type="text" name="subjectList[1].subjectId"/><br/>Name of Course 2:<input type="text" name="subjectList[1].subjectName"/><br/>MySQL Course Scores:<input type="text" name="scores['mysql']"/><br/>Java Course Scores:<input type="text" name="scores['java']"/><br/>
    <input type="submit" value="Save"/>
</form>

Copy the code
@RequestMapping("/addStu")
public String addStu(Student stu){
    log.debug("stu:"+stu);
    return "result";
}

Copy the code

⭐ @requestparam annotation omission

The @requestParam (“userName”) annotation can be omitted when the request parameter name matches the parameter name

However, the code becomes less readable and cannot be omitted in the future in SpringCloud, so it is recommended not to omit it

Get the request header

  • Use the @requestheader annotation to get the RequestHeader information
  • Name or value attribute: Specifies the request header name
  • DefaultValue property: sets the DefaultValue
@RequestMapping("/getHeader")
public String getHeader(@RequestHeader("User-Agent") String userAgent, @RequestHeader("Referer") String referer){
    log.debug("header User-Agent:"+userAgent);
    log.debug("header Referer:"+referer);
    return "result";
}
Copy the code

Get the specified Cookie

@CookieValue(value = “JSESSIONID”,defaultValue = “null”)

  • Get Cookie data with the specified name using the @cookievalue annotation
  • Name or value property: Specifies the Cookie name
  • DefaultValue property: sets the defaultValue
  • The HttpSession object is obtained by declaring a parameter of type HttpSession in the parameter location
@RequestMapping("/getCookie")
public String getCookie(@CookieValue(value = "JSESSIONID",defaultValue = "null") String sessionId, HttpSession session){
    log.debug("sessionId:"+sessionId);
    return "result";
}

Copy the code

To send data

① An extra layer of packaging

public class EmployeeParam {
    
    private List<Employee> employeeList;
    }
Copy the code

(2) the form

Send the List directly&lt;Employee&gt;:<br/>
<form th:action="@{/param/list/emp}" method="post">Employee No. 1 name:<input type="text" name="employeeList[0].empName" /><br/>Employee No. 1 Age:<input type="text" name="employeeList[0].empAge" /><br/>Salary of Employee No. 1:<input type="text" name="employeeList[0].empSalary" /><br/>Name of Employee No. 2:<input type="text" name="employeeList[1].empName" /><br/>Employee No. 2 Age:<input type="text" name="employeeList[1].empAge" /><br/>Salary of Employee No. 2:<input type="text" name="employeeList[1].empSalary" /><br/>
    <button type="submit">save</button>
</form>
Copy the code

(3) handler method

@RequestMapping("/param/list/emp")
public String saveEmpList(// SpringMVC calls the entity class setEmployeeList()EmployeeParam EmployeeParam){
    
    List<Employee> employeeList = employeeParam.getEmployeeList();
    
    for (Employee employee : employeeList) {
        logger.debug(employee.toString());
    }
    
    return "target";
}
Copy the code

Page jump control

Forwarding is a server jump, and the client is unaware of it.

A redirect is a client redirect that the client knows about.

If you want to retrieve the data stored in the request, you must use forwarding.

Forwarding can only be forward to the same project, at most other projects on the same server (special Settings), redirection can jump to any location on the Internet.

Forwarding can jump to the web-INF directory of the project.

Redirects cannot go to the web-INF directory of the project (redirects are client-side redirects).

@RequestMapping("/dispatcher1")
public String dispatcher1(a){
    //return "result";
    return "forward:/WEB-INF/templates/result.html";
}
@RequestMapping("/dispatcher2")
public String dispatcher2(a){
    return "forward:/outer.html";
}
@RequestMapping("/redirect1")
public String redirect1(a){
    return "redirect:/WEB-INF/templates/result.html"; / / can't
}
@RequestMapping("/redirect2")
public String redirect2(a){
    return "redirect:/outer.html"; // In Spring, the redirected/also represents the current project
}
@RequestMapping("/redirect3")
public String redirect3(a){
    return "redirect:http://www.baidu.com";
}
Copy the code

Get the native Servlet API object

@Autowired
private ServletContext servletContext;

@RequestMapping("/getAPI")
public String getAPI(HttpServletRequest request, HttpServletResponse response,HttpSession session){
    String requestURL = request.getRequestURL().toString();
    //HttpSession session1 = request.getSession();

    request.setAttribute("msg"."username is error"); // Only current requests from the current user
    session.setAttribute("username"."zhangsan");// Limited to multiple request cart users for the current user
    servletContext.setAttribute("count".10000);// All users are aware of all requests for website access
    response.addHeader("schoolName"."atguigu");
    return "result";
}


Copy the code
<span th:text="${msg}"></span><br>
 <hr>
<span th:text="${session.username}"></span><br>
<span th:text="${application.count}"></span><br>

Copy the code

Note: Use the native API if you pass data directly in the Web component and in the Session and Application domains. If you pass data in the Request domain, you generally do not use the native API, but use the following four methods [property domain].

Attribute domain

1. Use parameters of type Model

@RequestMapping("/getModel")
public String getModel(Model model){
    model.addAttribute("msg"."username is null(model)");
    return "result"; // The data in the request domain must be forwarded
}

Copy the code

2. Use parameters of the Map type

@RequestMapping("/getMap")
public String getMap(Map map){
    map.put("msg"."username is null(map)");
    return "result"; // The data in the request domain must be forwarded
}
Copy the code

3. Use parameters of type ModelMap

@RequestMapping("/getModelMap")
public String getModelMap(ModelMap modelMap){
    modelMap.addAttribute("msg"."username is null(modelmap)");
    return "result"; // The data in the request domain must be forwarded
}

Copy the code

4. Use the ModelAndView object

@RequestMapping("/getModelAndView")
public ModelAndView getModelAndView(a){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("msg"."username is null(modelandview)");
    modelAndView.setViewName("result");
    return modelAndView; // The data in the request domain must be forwarded
}

Copy the code

Note:

  1. Using Model, Map, and ModelMap to pass data, and the return value of the method is the String class, eventually the underlying SpringMVC will change to using ModelAndView to store data and views.

  2. The bottom line is to store the model into the request domain. Finally found the source location, in the class: org. Thymeleaf. Context. WebEngineContext

    ; SetVariable (); Effect of the statement is actually this. Request. SetAttribute (name, value).

  3. SpringMVC passes in Model, ModelMap, and Maptype parameters that are essentially BindingAwareModelMap.

Form data is displayed

1. Simple labels are displayed

Entity packaging

@Controller
public class TigerController {
    @RequestMapping("/tiger/findById")
    public String findById(Model model){
        // Access the database with the specified id
        Tiger tiger = new Tiger();
        tiger.setTigerId(5);
        tiger.setTigerName("TomCat tiger");
        tiger.setTigerSalary(666.66);
        // Stores information to the request domain
        model.addAttribute("tiger1",tiger);
        // Return (forward)
        return "updateTiger"; }}Copy the code
<h3>Modify the information</h3>
 <form>ID:<input type="text" name="tigerId" th:value="${tiger1.tigerId}"><br>Name:<input type="text" name="tigerName" th:value="${tiger1.tigerName}"><br>Salary:<input type="text" name="tigerSalary" th:value="${tiger1.tigerSalary}"><br>
     <input type="submit" value="Submit">
 </form>

Copy the code

2. The single display box and drop-down list box are displayed

ArrayList wrapper for entity class types

@RequestMapping("/tiger/findById2")
public String findById2(Model model){
    // Access the database with the specified id
    Tiger tiger = new Tiger();
    tiger.setTigerId(5);
    tiger.setTigerName("tomCat");
    tiger.setTigerSalary(666.66);
    tiger.setSeason(new Season("s3"."Authmn"));

    // Prepare four seasons
    List<Season> seasonList = new ArrayList<>();
    seasonList.add(new Season("s1"."Spring"));
    seasonList.add(new Season("s2"."Summer"));
    seasonList.add(new Season("s3"."Authmn"));
    seasonList.add(new Season("s4"."Winter"));
    // Stores information to the request domain
    model.addAttribute("tiger1",tiger);
    model.addAttribute("seasonList",seasonList);
    // Return (forward)
    return "updateTiger2";
}


Copy the code
Season:<input type="radio" name="season"
          th:each="season:${seasonList}"
          th:value="${season.seasonId}"
          th:text="${season.seasonName}"
          th:checked="${season.seasonId==tiger1.season.seasonId}"><br>


Copy the code
Season:<select name="season" >
    <option  th:each="season:${seasonList}"
             th:value="${season.seasonId}"
             th:text="${season.seasonName}"
             th:selected="${season.seasonId==tiger1.season.seasonId}"
    ></option>
</select>

Copy the code

3. The check box is displayed

ArrayList

@RequestMapping("/tiger/findById3")
public String findById3(Model model){
    // Access the database with the specified id
    Tiger tiger = new Tiger();
    tiger.setTigerId(5);
    tiger.setTigerName("tomCat");
    tiger.setTigerSalary(666.66);
    tiger.getFriendList().add(new Friend(1."monkey"));
    tiger.getFriendList().add(new Friend(3."turkey"));


    // Prepare four friends
    List<Friend> friendList = new ArrayList<>();
    friendList.add(new Friend(1."monkey"));
    friendList.add(new Friend(2."donkey"));
    friendList.add(new Friend(3."turkey"));
    friendList.add(new Friend(4."mickey"));
    // Stores information to the request domain
    model.addAttribute("tiger1",tiger);
    model.addAttribute("friendList",friendList);
    // Return (forward)
    return "updateTiger3";
}

Copy the code
<h3>Modify the information</h3>
 <form>ID:<input type="text" name="tigerId" th:value="${tiger1.tigerId}"><br>Name:<input type="text" name="tigerName" th:value="${tiger1.tigerName}"><br>Salary:<input type="text" name="tigerSalary" th:value="${tiger1.tigerSalary}"><br>Friend:<input type="checkbox" name="season"
               th:each="friend:${friendList}"
               th:value="${friend.friendId}"
               th:text="${friend.friendName}"
               th:checked="${#lists.contains(tiger1.friendList,friend)}"><br>
     <input type="submit" value="Submit">
 </form>

Copy the code

Case study: cinema system

Here HashMap is used to simulate the database, which does not involve the integration with MyBatis and Spring, but focuses on the use of SpringMVC: build the environment first, and perform functions through the operation of pages and sub-controllers.

1. Prepare the SpringMVC environment

1. Rely on

The Servlet rely on

For SpringMVC rely on

Integrated dependencies of Spring and Thymeleaf

Lombok

Log Logback

Spring Test

Junit5

2.web.xml

SpringMVC master controller

Resolve the problem of Chinese garbled characters in Post requests

3.Spring-mvc.xml

Enable MVC annotation driver

Resolve static resource access problems

Component (@controller@service) scanning

Thymeleaf parser

4. Log logback. XML

resources/logback.xml

5. Create a directory structure

2. Prepare entity classes

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Movie {

    private String movieId;
    private String movieName;
    private Double moviePrice;
}

Copy the code

3. Prepare business layer interface and implementation class code (provide simulated data)

public interface MovieService {    
    List<Movie> getAll(a);    
    Movie getMovieById(String movieId);    
    void saveMovie(Movie movie);    
    void updateMovie(Movie movie);    
    void removeMovieById(String movieId);    
}

Copy the code

4. Test the business layer code

@SpringJUnitConfig(locations = "classpath:spring-mvc.xml")
public class TestMovie {

    @Autowired
    private MovieService movieService;

    @Test
    public void testSelectAll(a){
        List<Movie> movieList = this.movieService.getAll();
        movieList.forEach(m-> System.out.println(m));
    }
    @Test
    public void testSaveMovie(a){
        Movie movie = new Movie("m17".Detective Chinatown 2.60.0);
        this.movieService.saveMovie(movie);
        List<Movie> movieList = this.movieService.getAll(); movieList.forEach(m-> System.out.println(m)); }}Copy the code

5. The home page is displayed

Configure the home page, using the XML configuration mapping here.

<! Configure the view controller -->
<mvc:view-controller path="/" view-name="portal"></mvc:view-controller>

Copy the code
<a th:href="@{/movie/getAll}">Enter the Theater System</a>
Copy the code

6. Display all movies

@Controller
@RequestMapping("/movie")
public class MovieController {
    @Autowired
    private MovieService movieService;

    @RequestMapping("/getAll")
    public String getAll(Model model){
        // Call the business layer to get the list of movies
        List<Movie> movieList = this.movieService.getAll();
        // Store the movie list data into the request field
        model.addAttribute("movieList",movieList);
        // Specify the view to return
        return "movieList"; }}Copy the code
<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        h3{
            text-align: center;
        }
        table#table1{
            width: 60%;
            margin: 10px auto;
            border-collapse: collapse;
        }
        table#table1 th.td{
            text-align: center;
            border:1px solid red;
        }
    </style>
</head>
<body>
    <h3>The movie list</h3>
    <table id="table1">
        <tr>
            <th>Film number</th>
            <th>The movie name</th>
            <th>Watch the price</th>
            <th>operation</th>
        </tr>
        <tbody>
            <tr th:each="movie:${movieList}">
                <td th:text="${movie.movieId}"></td>
                <td th:text="${movie.movieName}"></td>
                <td th:text="${movie.moviePrice}"></td>
                <td>Modify delete</td>
            </tr>
        </tbody>
    </table>
</body>
</html>


Copy the code

perfect

  • Interlaced discoloration
  • Distinguish between movies
<h3>The movie list</h3>
<table id="table1">
    <tr>
        <th>Film number</th>
        <th>The movie name</th>
        <th>Watch the price</th>
        <th>status.index</th>
        <th>status.count</th>
        <th>operation</th>
    </tr>
    <tbody th:if="${movieList==null || movieList.size()==0}">
        <tr>
            <td colspan="10">Not a single movie</td>
        </tr>
    </tbody>
    <tbody th:if="${not #lists.isEmpty(movieList)}">
        <tr th:each="movie,status:${movieList}" th:class="${status.index%2==0? 'white':'beige'}">
            <td th:text="${movie.movieId}"></td>
            <td th:text="${movie.movieName}"></td>
            <td th:text="${movie.moviePrice}"></td>
            <td th:text="${status.index}"></td>
            <td th:text="${status.count}"></td>
            <td>Modify delete</td>
        </tr>
    </tbody>
</table>

Copy the code

7. Add new movie features

1. Go to the Add page

<mvc:view-controller path="/movie/toAdd" view-name="movieAdd"></mvc:view-controller>
Copy the code
<h3>Add movie Information</h3>
<form th:action="@{/movie/addMovie}" method="post">Movie Number:<input type="text" name="movieId"><br>Movie title:<input type="text" name="movieName"><br>Movie price:<input type="number" step="0.01" name="moviePrice"><br>
    <input type="submit">
</form>

Copy the code

2. Add the function

@RequestMapping("/addMovie")
public String addMovie(Movie movie){
    this.movieService.saveMovie(movie);
    //return "forward:/movie/getAll"; / /??
    return "redirect:/movie/getAll";
}
Copy the code

Only redirection can be used here.

The forwarding address is the address before the jump, causing the form to be submitted repeatedly.

The redirected address is the address after the jump and does not cause the form to be submitted twice.

8. Modify the specified movie information

1. Query the specified movie information and the command output is displayed

<a th:href="@{/movie/getMovieById(movieId=${movie.movieId})}">Modify the</a>
Copy the code
@RequestMapping("/getMovieById")
public String getMovieById(String movieId,Model model){
    Movie movie = this.movieService.getMovieById(movieId);
    model.addAttribute("movie",movie);
    return "movieUpdate";
}
Copy the code
<h3>Modify movie information</h3>
<form th:action="@{/movie/updateMovie}" method="post">Movie Number:<input type="text" name="movieId" th:value="${movie.movieId}" readonly><br>Movie title:<input type="text" name="movieName" th:value="${movie.movieName}"><br>Movie price:<input type="number" step="0.01" name="moviePrice" th:value="${movie.moviePrice}"><br>
    <input type="submit">
</form>
Copy the code

2. Complete the modification function

@RequestMapping("/updateMovie")
public String updateMovie(Movie movie){
    this.movieService.updateMovie(movie);
    return "redirect:/movie/getAll";
}
Copy the code

9. To summarize

1. Develop the main steps

Setting up the project environment

Prepare the business layer code

Feature development (page Thymeleaf) and sub-controller (@Controller)

2. Front-end knowledge

1. Check whether the set is empty
<tbody th:if="${movieList==null || movieList.size()==0}">
Copy the code
<tbody th:if="${not #lists.isEmpty(movieList)}">
Copy the code
2. To traverse the List
<tr th:each="movie,status:${movieList}">
    <td th:text="${movie.movieId}"></td>
    <td th:text="${movie.movieName}"></td>
    <td th:text="${movie.moviePrice}"></td>
    <td th:text="${status.index}"></td>
    <td th:text="${status.count}"></td>
</tr>
Copy the code
3. Realize interlaced color change

Prepare two tr label styles in different colors for the same label

.white{
    background-color: white;
}
.beige{
    background-color: beige;
}

Copy the code

Set a variable increment while traversing, and then judge this variable.

<tr th:each="movie,status:${movieList}" th:class="${status.index%2==0? 'white':'beige'}">
</tr>
Copy the code
4. Modify and delete the hyperlink, pass the parameter way
  • Href: assign using (variable name =thymeleaf expression)

  • Event: Box the Thymeleaf expression with [[]]

<a th:href="@{/movie/getMovieById(movieId=${movie.movieId})}">Modify the</a>
<a th:href="@{/movie/removeMovieById(movieId=${movie.movieId})}">delete</a>
<a href="javascript:void(0)" th:onclick="confirmRemove([[${movie.movieId}]])">Delete the 2</a>
<a href="javascript:void(0)" th:onclick='confirmRemove([[${movie.movieId}]])'>Remove 3</a>
Copy the code

Note: If you want the href to be invalidated before deleting, use javascript:void(0).

5. The form data is displayed
<form th:action="@{/movie/updateMovie}" method="post">Movie Number:<input type="text" name="movieId" th:value="${movie.movieId}" readonly><br>Movie title:<input type="text" name="movieName" th:value="${movie.movieName}"><br>Movie price:<input type="number" step="0.01" name="moviePrice" th:value="${movie.moviePrice}"><br>
    <input type="submit">
</form>

Copy the code

3. Branch controller

  1. How to receive client parameters, pass basic parameters and entity classes.

    public String removeMovieById(String movieId){.. } public String addMovie(Movie movie){.. }Copy the code
  2. There are several ways to pass parameters to the next component, in this case Model is used and the underlying data is stored in the Request field.

    @RequestMapping("/getMovieById")
    public String getMovieById(String movieId,Model model){
        Movie movie = this.movieService.getMovieById(movieId);
        model.addAttribute("movie",movie);
        return "movieUpdate";
    }
    
    Copy the code
  3. How do I jump after DML operation?

    1. Can not jump to the page to display, should query in the display first
    2. After processing, you are advised to use the Redirect instead of the forward to avoid resubmitting the form.
    @RequestMapping("/updateMovie")
    public String updateMovie(Movie movie){
        this.movieService.updateMovie(movie);
        return "redirect:/movie/getAll";
    }
    
    Copy the code

Restful style

Restful architecture is a very popular Internet software architecture. It is clear structure, accord with the standard, easy to understand, easy to expand, is getting more and more sites to adopt.

REST, which stands for Representational State Transfer, can be translated as “Representational State Transfer,” or “State Transfer of presentation-layer resources.”

If an architecture conforms to REST principles, it is a RESTful architecture.

(1) resources

The subject is omitted from the name REST, “State transitions at the presentation layer.” The “presentation layer” actually refers to the “presentation layer” of “resources”.

The so-called “resource” is an entity on the network, or a specific information on the network. It can be a piece of text, a picture, a song, a service. You can point to it with a URI (uniform resource Locator), and each resource has a specific URI. To get this resource. Just access its URI, so the URI becomes the address or unique identifier for each resource.

② Representation

Resource is a kind of information entity, which can have many forms of external expression. The concrete form of “resource” is called its “presentation layer”.

For example, text can be represented in TXT format, HTML format, XML format, JSON format, and even binary format. Images can be rendered in JPG format or PNG format.

③ State transfer

Internet Communication protocol HTTP is a stateless protocol. In this dimension, all state is stored on the server side. Therefore, if the client wants to operate the server, it must somehow make a “state transition” occur on the server side. This transformation is based on the presentation layer, so it is the “presentation layer state transformation”.

The client can only use HTTP. Specifically, there are four verbs in the HTTP protocol that denote operations: GET, POST, PUT, DELETE. They correspond to four basic operations:

  • GET is used to obtain resources
  • POST is used to create resources
  • PUT is used to update resources
  • DELETE Deletes a resource
operation Traditional style REST style
save /CRUD/saveEmp URI address: /CRUD/ EMP Request mode: POST
delete /CRUD/removeEmp? empId=2 URI address: /CRUD/emp/2 Request mode: DELETE
update /CRUD/updateEmp URI address: /CRUD/ EMP Request mode: PUT
Query (form echo) /CRUD/findEmpById? empId=2 URI address: /CRUD/emp/2 Request mode: GET

At present, HTTP protocol is mainly used (designed by Roy Thomas Fielding, who proposed REST idea) to realize REST idea. Some people on the network summarize REST in a word from the point of view of events: URI locate resources and use HTTP verbs (GET,POST,PUT,DELETE) to describe operations.

Idempotent and security

HTTP operations Resource operation idempotence security
GET SELECT is is
POST INSERT/SAVE no no
The PUT UPDATE/EDIT is no
DELETE DELETE/REMOVE is no

Mapping in Restful request mode: GET

< h3 > Restful request mapping: Get < / h3 > < th: a href = "@ {/ emp}" > query all employees < / a > < br > < a th: href = "@ {/ emp / 1}" > query specifies the number of employees < / a > < br > < a Th :href="@{/emp/zh/300}"> </a><brCopy the code
@Controller @Slf4j public class EmployeeController { @GetMapping("/emp") public String findAll(){ log.debug("--------EmployeeController findAll ---------"); return "result"; } @GetMapping("/emp/{empId}") public String findById(@PathVariable("empId") Integer empId){ log.debug("-------EmployeeController findById :"+empId+"------"); return "result"; } @GetMapping("/emp/{empName}/{minSalary}") public String findEmp(@PathVariable("empName") String ename,@PathVariable("minSalary") Double minSal){ log.debug("-------EmployeeController findEmp :"+ename+","+minSal+"------"); return "result"; }}Copy the code

{} is used to indicate parameters in RequestMapping, and @pathVariable is used to specify mappings in method parameters

Restful Request mapping: POST

< H3 >< form th: Action ="@{/ EMp}" method=" POST "> Employee name :<input type="text" name="empName"><br> <input type="text" name="empSalary">Copy the code
@PostMapping("/emp") public String addEmp(Employee emp){ log.debug("-------EmployeeController addEmp:"+emp+" -- -- -- -- -- -- -- -- -- -- "); return "result"; }Copy the code

Mapping in Restful request mode: PUT

<! Convert a POST request to a PUT DELETE request (depending on the value of _method) Must be written after CharacterEncodingFilter --> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>Copy the code

HiddenHttpMethodFilter must be written after CharacterEncodingFilter

< H3 > PUT</ H3 > <form th: Action ="@{/ EMp}" method="POST"> <input type="hidden" name="_method" value=" PUT "> <input type="text" name="empId" value="7839"><br> <input type="text" name="empSalary" value="5000"><br>Copy the code

• Bullet point 1: The original request mode must be POST • Bullet point 2: The new request mode name is sent through the request parameter • Bullet point 3: the request parameter name must be _method • Bullet point 4: The request parameter value is the request mode to be changed

@PutMapping("/emp")
public String updateEmp(Employee emp, HttpServletRequest request){
    log.debug("-----EmployeeController updateEmp:"+request.getMethod()+"--------------");
    log.debug("-------EmployeeController updateEmp:"+emp+" ----------");
    return "result";
}

Copy the code

Mapping in Restful request mode: DELETE

<script type="text/javascript" th:src="@{/js/vue.js}"></script>
Copy the code
< H3 > <div ID ="app"> <a th:href="@{/emp/1}" @click="deleteEmp"> DELETE employee 1 </a> <a Th :href="@{/emp/2}" @click.prevent="confirmDeleteEmp">Copy the code

To provide a form that uses hidden, you must specify that name=”_method” value=”delete” is used to forward the request.

<form id="form1"  method="post">
    <input type="hidden" name="_method" value="delete">
</form>

Copy the code
script type="text/javascript"> new Vue({ el:"#app", data:{ } , Var formElem = document.getelementById ("form1"); // Modify the action of the generic form to the href formelem.action = event.target.href; // submit the form formelem.submit (); // prevent the default behavior of hyperlinks (request href resources) // event.preventdefault (); }, confirmDeleteEmp: function (event) {var flag = window. Confirm (" are you sure you want to delete the employee information? "); If (flag){var formElem = document.getelementById ("form1"); // Modify the action of the generic form to the href formelem.action = event.target.href; // submit the form formelem.submit (); // prevent the default behavior of hyperlinks (request href resources) // event.preventdefault (); }}}}); </script>Copy the code
@DeleteMapping("/emp/{empId}") public String deleteEmp(@PathVariable("empId") Integer empId,HttpServletRequest request){  log.debug("-----------EmployeeController deleteEmp:"+request.getMethod()+"--------------"); log.debug("-------EmployeeController deleteEmp:"+empId+" ----------"); return "result"; }Copy the code

conclusion

Refactoring projects in a Restful style:

  1. Modifying the Page Path
  2. Example Modify the RequestMapping of a sub-controller
  3. Specify the correspondence using @pathvariable
  4. The way to jump to a sub-controller is to notice the path.

Ajax passing parameters

Requirements:

Import axios and Vue JS files.

Basic type parameter

page

<script type="text/javascript" th:src="@{/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/js/axios.min.js}"></script>

Copy the code
<a href="javascript:void(0)" @click="AjaxDemo1">Copy the code
<script type="text/javascript"> new Vue({ el:"#app", Methods: {AjaxDemo1: function () {/ / sends an Ajax request axios ({method: "POST", / / url: "/ for springmvc/Ajax/AjaxDemo1", url:"[[@{/ajax/ajaxDemo1}]]", params:{ sid:1, name:"zhangsan", Then (function(result){//console.info(result); alert(result.data+" "+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) //console.debug(error1.data) }); }}}); </script>Copy the code

The background

@Controller
@Slf4j
public class AjaxController {
    @RequestMapping("/ajax/ajaxDemo1")
    @ResponseBody
    public String ajaxDemo1(Integer sid,String name,Double score){
        log.debug("ajaxDemo1:"+sid+","+name+","+score);
        int n = 10/0;
        return "ok"; //WEB-INF/templates/ok.html
    }
}

Copy the code

Entity class

page

<h3>Ajax request 2: Pass entity type parameter </h3> <a href="javascript:void(0)" @click="AjaxDemo2">Copy the code
New Vue({el:"#app", methods:{AjaxDemo2:function(){// Send an Ajax request axios({method:"POST", //url:"/springmvc/ajax/ajaxDemo1", url:"[[@{/ajax/ajaxDemo2}]]", params:{ empId:7839, empName:"King", Then (function(result){//console.info(result); alert(result.data+" "+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) console.debug(error1.response) console.debug(error1.response.data) alert(error1.response.data.innerHTML); //console.debug(error1.data) }); }}}); </script>Copy the code

The background

@RequestMapping("/ajax/ajaxDemo2")
public String ajaxDemo2(Employee emp){
    log.debug("ajaxDemo2:"+emp);
    int n = 10/0;
    return "ok"; //WEB-INF/templates/ok.html
}

Copy the code

Entity class with cascading attribute -1

< H3 >Ajax request 3-1: Pass entity type parameter with cascading attribute </h3> <a href="javascript:void(0)" @click="AjaxDemo31">Copy the code
AjaxDemo31: function () {/ / sends an Ajax request axios ({method: "POST", / / url: "/ for springmvc/Ajax/ajaxDemo1", [[@{/ajax/ajaxDemo31}]]", params:{empId:7839, empName:"King", empSalary:5000.0, hireDate :"1999-12-23", }}). Then (function(result){//console.info(result); alert(result.data+" "+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) console.debug(error1.response) console.debug(error1.response.data) }); }Copy the code

The background

@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer empId; private String empName; private double empSalary; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date hiredate; / /!!!!! private Dept dept; / /!!!!!! }Copy the code
@RequestMapping("/ajax/ajaxDemo31")
public String ajaxDemo31(Employee emp){
    log.debug("ajaxDemo31:"+emp);
    int n = 10/0;
    return "ok"; //WEB-INF/templates/ok.html
}

Copy the code

Note: Accept arguments of date type

@datetimeformat (pattern = “YYYY-MM-DD “)

The java.sql.Date format must be YYYY-MM-DD without @datetimeformat

Entity class with cascading properties -2

page

</h3> <a href="javascript:void(0)" @click="AjaxDemo32">Ajax request 3-2</a>Copy the code
AjaxDemo32: function () {/ / sends an Ajax request axios ({method: "POST", / / url: "/ for springmvc/Ajax/ajaxDemo1", [[@{/ajax/ajaxDemo32}]]", data:{empId:7839, empName:"King", empSalary:5000.0, hireDate :"1999-12-23", Dept :{deptNo:10, dname:" deptNo "}}). Then (function(result){//console.info(result); alert(result.data+" "+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) console.debug(error1.response) console.debug(error1.response.data) }); },Copy the code

The background

@RequestMapping("/ajax/ajaxDemo32")
public String ajaxDemo32(@RequestBody Employee emp){
    log.debug("ajaxDemo32:"+emp);
    //int n = 10/0;
    return "ok"; //WEB-INF/templates/ok.html
}


Copy the code

Rely on

<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> < version > 2.12.1 < / version > < / dependency > < the dependency > < groupId > com. Fasterxml. Jackson. Core < / groupId > < artifactId > Jackson - databind < / artifactId > < version > 2.12.1 < / version > < / dependency >Copy the code

Pay attention to

  1. The Jackson JSON component must be added
  2. You must use @requestBody to receive data in a sub-controller method
  3. Use Ajax to pass data in front pages using data properties instead of Params
  4. Data is passed in the front-end page using JSON format

Why @requestBody

When @requestbody receives a client request, it retrieves data from the RequestBody instead of the request header or URL

When @responsebody responds to a client, instead of a forward and redirect redirect, it puts data directly into the ResponseBody

When you send an Ajax request, you use data, put the data into the request body, and the server’s sub-controller takes the data from the request body

When sending Ajax requests, use Params to write the data to the end of the URL, such as Ajax /ajaxDemo31? empId=xx&ename=xx&… , whether it’s get or post

Entity class objects, collections

The previous four Ajax requests have different contents, but they all return simple strings. What if they return an entity class object or a collection object?

page

</h3> <a href="javascript:void(0)" @click="AjaxDemo4">Copy the code
AjaxDemo4: function () {/ / sends an Ajax request axios ({method: "POST", / / url: "/ for springmvc/Ajax/ajaxDemo1", url:"[[@{/ajax/ajaxDemo4}]]", }) .then(function(result){ //console.info(result); //alert(result.data+" "+result.status); console.info(result.data) }) .catch(function(error1){ //console.debug(error) console.debug(error1) }); },Copy the code

The background

@RequestMapping("/ajax/ajaxDemo4") public Employee ajaxDemo4(){ Employee emp = new Employee(); emp.setEmpId(7839); emp.setEmpName("King"); An emp. SetEmpSalary (5000.0); emp.setHiredate(java.sql.Date.valueOf("1999-12-22")); Dept Dept = new Dept(10," Dept "); emp.setDept(dept); return emp; }Copy the code
@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer empId; private String empName; private double empSalary; @dateTimeFormat (pattern = "YYYY-MM-DD ") @jsonFormat (pattern =" YYYY-MM-DD ") private Date hiredate; / /!!!!! private Dept dept; / /!!!!!! }Copy the code

Note:

Behind the scenes is the Jackson component (converting objects or collections to JSON strings)

The interceptor

It’s very similar to filters, it solves similar problems.

Similarities between interceptors and filters

  1. The steps are basically similar: stop, handle and release.
  2. They can all form chains. Early execution ends play, early execution ends play.

Differences between interceptors and filters

  1. Filters are concepts in JavaEE that are executed before servlets. Interceptors are a concept of MVC frameworks such as springMVC, executed after the master controller Servlet and before the sub-controller.
  2. The scope of interception is different.
  3. The use of IoC containers is different (interceptors can use IoC containers directly).

Creating interceptors

How to create interceptors:

Method 1: Implement HandlerInterceptor, which is recommended

Method 2: inherit HandlerInterceptorAdapter JDK8 after this way have been out of date, because the interface has given an empty implementation.

Public class MyInterceptor1 implements HandlerInterceptor {/** ** implements HandlerInterceptor {/** ** implements HandlerInterceptor; * @param Request * @param Response * @param handler In order to be able to handle all kinds of situations, The type is Object * @return Return value true Permit false Permit (interceptors and target resources will not be executed) * Generally redirect to a page before permit * @throws Exception */ public Boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("--------MyInterceptor1 preHandle ---------------"); return false; } /** * The target resource of the request (which can be a sub-controller, Or static resources) after execution @param Request @param Response @param handler @param modelAndView The final return type of each sub-controller method is modelAndView * Here we can get the return value of the sub-controller, * @throws Exception */ public void postHandle(HttpServletRequest Request, HttpServletResponse Response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("--------MyInterceptor1 postHandle ---------------"); } /** * The SpringMVC process is not finished after the controller is divided. Result - > / WEB - INF/templates/result. * view rendering HTML < p th: text = ${MSG} > < / p > will Thymeleaf th tag of the page data in the Model to replace the use of * (the form Th :action=@{/user/login}></form> /springmvc/user/login Operations such as shutting down resources * @param request * @param Response * @param handler * @param ex Exceptions that occur during a request * @throws Exception */ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("--------MyInterceptor1 afterCompletion ---------------"); }}Copy the code

Configuring interceptors

<! Configure interceptor global interceptor, Intercept after all requests for SpringMVC total controller - > < bean class = "com. Atguigu. Interceptor. MyInterceptor1" > < / bean > < MVC: interceptors > <! < MVC :interceptor> < MVC :mapping path="/user/*"/> <bean class="com.atguigu.interceptor.MyInterceptor2"></bean> </mvc:interceptor> </mvc:interceptors>Copy the code

The order in which an interceptor’s methods are executed:

The interceptor preHandle- > (if true is returned) executes the sub-controller handler- > interceptor postHandle- > View Render – > interceptor afterCompletion

Order of execution for multiple interceptors:

PreHandle for interceptor 1 – > preHandle for interceptor 2 – > (if true is returned) perform sub-controller handler- > postHandle for interceptor 2 – > postHandle for interceptor 1 – > view rendering – > afterCompletion for interceptor 2 – > block AfterCompletion for interceptor 1

Different interceptors execute first who:

It is determined by the configuration order and has nothing to do with the global local.

After access confirmation, access to sub-controllers and static resources will go through the interceptor (the access path for the master controller is “/”)

Interceptor application

public class MyInterceptor1 implements HandlerInterceptor { @Autowired private ServletContext servletContext; @Autowired private UserController userController; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1. Solve the problem of Chinese garbled / / request. SetCharacterEncoding (" utf-8 "); Check whether the user has logged in // If the access path is the login page or the sub-controller method path, if the access path is the registration page or the sub-controller method path // If the access path is the verification code path, /* String requestURL = request.getrequestUrl ().toString(); If (requestURL is one of the five paths){// Return true; } String user = (String)request.getSession().getAttribute("user"); if(user ==null ){ response.sendRedirect("/xxx/login.html"); return false; } */ System.out.println("--------MyInterceptor1 preHandle ---------------"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //1. /* String MSG = (String)modelAndView.getModel().get(" MSG "); If (MSG. The contains (" guns ") | | MSG. The contains (" pornography ")) {MSG. Replace (" guns ", "* * * *"); MSG. Replace (" replace ","?? ") ); modelAndView.addObject("msg",msg); //String viewName = modelAndView.getViewName(); //String viewName = modelAndView.getViewName(); //result //modelAndView.setViewName(viewName+"Test"); System.out.println("--------MyInterceptor1 postHandle ---------------"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("--------MyInterceptor1 afterCompletion ---------------"); //1. Handle exceptions. //2.Copy the code

Type converter

Built-in type converter

Using annotations

@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer empId; private String empName; @NumberFormat(pattern = "##,###,###.##") private double empSalary; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date hiredate; / /!!!!! }Copy the code
@Controller @Slf4j public class EmployeeController { @RequestMapping("/emp/saveEmp") public String saveEmp(Employee emp, BindingResult BindingResult){log.debug("EmployeeController saveEmp: "+emp); if(bindingResult.hasErrors()){ return "error"; } return "result"; }}Copy the code
<p th:errors="${employee.hiredate}"></p>
<p th:errors="${employee.empSalary}"></p>

Copy the code

Custom type converter

You need to implement the Converter<> interface

public class AddressConverter implements Converter<String, Address> { public Address convert(String source) { // 1. Split source String[] split = source.split(","); // String type = split[0]; // Hebei String city = split[1]; // String street = split[2]; Address Address = new Address(province, city, street); // 3. Return address; }}Copy the code
@Data @AllArgsConstructor @NoArgsConstructor public class Employee { private Integer empId; private String empName; @NumberFormat(pattern = "##,###,###.##") private double empSalary; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date hiredate; / /!!!!! private Address address; }Copy the code
<form th:action="@{/emp/saveEmp}" method="post"> <input type="text" name="empSalary" value="11,123.45"><br> start time <input Type ="text" name="hiredate" value="1999-12-23"><br> address <input type="text" name="address" value=" Hebei "> <input type="submit"> </form>Copy the code

Data validation

JSR 303 is a standard framework provided by Java for Bean data validation, which has been included in JavaEE 6.0 standard. JSR 303 specifies validation rules by annotating Bean attributes with standard annotations like @notnull, @max, etc., and validates beans through a standard validation interface.

annotations The rules
@Null The annotation value must be NULL
@NotNull The annotation value cannot be null
@AssertTrue The value must be true
@AssertFalse The value must be false
@Min(value) The value must be greater than or equal to value
@Max(value) The value must be less than or equal to value
@DecimalMin(value) The value must be greater than or equal to value
@DecimalMax(value) The value must be less than or equal to value
@Size(max,min) The size of the annotations must be within the limits of Max and min
@Digits(integer,fratction) Annotated value The value must be a number and must be within an acceptable range
@Past Note values can only be used for dates and must be past dates
@Future Note values can only be used for dates and must be future dates
@Pattern(value) The annotated value must conform to the specified regular expression

JSR 303 is just a set of standards that require an implementation to be available. Hibernate Validator is a reference implementation of JSR 303. In addition to supporting all standard validation annotations, it also supports the following extended annotations:

annotations The rules
@Email The annotated value must be a well-formed Email address
@Length The annotated value string size must be within the specified range
@NotEmpty The value string cannot be an empty string
@Range The values must be within the specified range

Rely on

<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> < version > 6.2.0. Final < / version > < / dependency > < the dependency > < groupId > org. Hibernate. The validator < / groupId > - < artifactId > hibernate validator - an annotation processor < / artifactId > < version > 6.2.0. The Final < / version > < / dependency >Copy the code
public class Employee { private Integer empId; private String empName; @NumberFormat(pattern = "##,###,###.##") private double empSalary; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date hiredate; / /!!!!! private Address address; @Size(min=6,max = 12) @Email private String email; }Copy the code
@Controller @Slf4j public class EmployeeController { @RequestMapping("/emp/saveEmp") public String saveEmp(@Validated Employee emp, BindingResult BindingResult){log.debug("EmployeeController saveEmp: "+emp); if(bindingResult.hasErrors()){ return "error"; } return "result"; }}Copy the code

Anomaly map

Associate the exception type with a specific view to establish a mapping

java.lang.NullPointerException———–>error-null.html

java.lang.FileNotFoundException———–>error-notfoundl.html

java.lang.RuntimeException———->error-run.html

java.lang.Exception———->error.html

Benefits of exception mapping

Micro: Use declarative instead of programmatic exception management

Let exception control and core business decouple, both maintain separately, better structure.

Macro: Exceptions are managed using the same rule across the project level.

The whole project code style is more unified and concise.

Facilitate collaboration between team members.

XML implementation

Create exception mappings in the SpringMVC configuration file

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.NullPointerException">exp-null</prop>
            <prop key="java.io.FileNotFoundException">exp-notfound</prop>
            <prop key="java.lang.RuntimeException">exp-run</prop>
            <prop key="java.lang.Exception">exp</prop>
        </props>
    </property>
    <property name="exceptionAttribute" value="atguiguException"></property>
</bean>

Copy the code

2 Create an exception result page

Use Thymeleaf to receive data and display it.

3 Create a sub-controller that sends exceptions and test it

@Controller @Slf4j public class UserController { @RequestMapping("/user/save1") public String save1(){ String str = null; System.out.println(str.length()); // return "result"; } @RequestMapping("/user/save2") public String save2(){ int n = 10/0; Return "result"; } @RequestMapping("/user/save3") public String save3() throws FileNotFoundException { FileInputStream fis = new FileInputStream("d:/sdf/adfadf.txt"); return "result"; } @RequestMapping("/user/save4") public String save4() throws SQLException { String url ="adfadsf"; String username ="root"; String password ="root"; Connection conn = DriverManager.getConnection(url,username,password); return "result"; }}Copy the code

Annotation implementation

Define a class that specifies exception mappings in its methods

/** * This is a @Component */ @controllerAdvice public class MyExceptionHandler {@ExceptionHandler(value = NullPointerException.class) public String resolveNullPointerException(Exception e,Model model){ model.addAttribute("atguiguException",e); return "exp-null2"; } @ExceptionHandler(value = RuntimeException.class) public String resolveRuntimeException(Exception e,Model model){ model.addAttribute("atguiguException",e); return "exp-run"; }}Copy the code

Note:

  1. Define an exception handling class and add @ControllerAdvice
  2. Define methods specified exception handling, add annotations that exception type @ ExceptionHandler (value = NullPointerException. Class)

Component scan, scan this class

<! -- Component (@controller@service) scan --> <context:component-scan base-package="com.atguigu.controller,com.atguigu.exceptionmapping"></context:component-scan>Copy the code

3 Create a sub-controller that sends exceptions and test it

If both XML and annotations exist, annotations take precedence.

There’s no need to do both. Annotations are recommended.

Distinguishing between request types (Ajax and non-Ajax)

If you want to distinguish between request types, only annotations can be used to give a solution for both request types.

Prepare regular and Ajax requests

<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" th:src="@{/js/vue.js}"></script>
    <script type="text/javascript" th:src="@{/js/axios.min.js}"></script>
</head>
<body>
   <h3>Exception handling: Non-Ajax requests</h3>
    <a th:href="@{/user/save2}">The Ajax request</a>
   <h3>Exception handling: Ajax requests</h3>
   <div id="app">
       <a href="javascript:void(0)" @click="testAjax()">An Ajax request</a>
   </div>

</body>
<script type="text/javascript">
    new Vue({
        el:"#app".methods: {testAjax:function(){
                axios({
                    method:"POST".url:"[[@{/user/save5}]]"
                })
                    .then(function(response){
                        console.info(response.data);
                    })
                    .catch(function(response){
                        console.info(response) }); }}});</script>
</html>

Copy the code
@Controller
@Slf4j
public class UserController {
    @RequestMapping("/user/save2")
    public String save2(a){
        int n = 10/0; // Arithmetic exception -- > running exception
        return "result";
    }
    @RequestMapping("/user/save5")
    @ResponseBody
    public String save5(a){
        int n = 10/0; // Arithmetic exception -- > running exception
        return "ok"; // Returns the character instead of jumping to the page}}Copy the code

2 test

Pre-ajax exception mapping also works, but returns the entire exception page rather than the exception string.

For Ajax requests, when an exception occurs, jump to the exception page and return, it is received in then, not catch.

3 Define tool classes

public class MyUtil {
    /** * Determine whether the current request is an Ajax request *@paramRequest Request object *@return* true: current request is an Ajax request * false: current request is not an Ajax request */
    public static boolean judgeRequestType(HttpServletRequest request) {

        // 1. Get the request header
        String acceptHeader = request.getHeader("Accept");
        String xRequestHeader = request.getHeader("X-Requested-With");

        / / 2. The judgment
        return(acceptHeader ! =null && acceptHeader.contains("application/json")) || (xRequestHeader ! =null && xRequestHeader.equals("XMLHttpRequest"));// only for jQuery}}Copy the code

4 Handle both cases in the approach to implementing exception mapping

@ExceptionHandler(value = RuntimeException.class)
public String resolveRuntimeException(Exception e, Model model, HttpServletResponse response, HttpServletRequest request){

    if(MyUtil.judgeRequestType(request)){// It is an Ajax request
        try {
            response.getWriter().print(e);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return null;
    }
    model.addAttribute("atguiguException",e);
    return "exp-run"; // This is to jump to a page
}

Copy the code

Exception handling: Ajax requests

When exception mapping is used, the exception information returned by Ajax requests when an exception occurs is retrieved from THEN

File upload

The underlying layer is based on commons-Fileupload component to encapsulate and simplify operations.

1 Adding a Dependency

<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.1</version>
</dependency>

Copy the code

This dependency depends on the COMMONs-io JAR, which will be imported during import.

2 Configuring the parser (spring.xml)

<! -- Configure multimedia parser, related to file upload -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="utf-8"></property>
</bean>

Copy the code

3 Preparing the Page

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String userId;
    private String realName;
    private Integer age;
    private String photo;// The path to the photo
}

Copy the code
<h3>File upload</h3>
 <form th:action="@{/user/register}" method="post" enctype="multipart/form-data">User name:<input type="text" name="userId"><br>Real Name:<input type="text" name="realName"><br>Age:<input type="text" name="age"><br>Photo:<input type="file" name="photo"><br>
 </form>

Copy the code

Pay attention to

  1. The request must be POST.
  2. The request body must be encoded as multipart/form-data (via the encType attribute of the form tag).
  3. Use the input tag. The type property is set to File to generate the file upload box.

4 Preparing controllers

@Controller
@Slf4j
public class UserController {
    @RequestMapping("/user/register")
    public String register(User user, MultipartFile photo1) throws IOException {
        log.debug(user.toString());
        log.debug(photo1.toString());
        user.setPhoto(photo1.getOriginalFilename());
        // Upload the file to the specified path
        photo1.transferTo(new File("d:/upload/"+photo1.getOriginalFilename()));
        return "result"; }}Copy the code

5 Test

Note:

Because the User class has the String photo attribute, the variable name of MultipartFile should not be repeated.

6 MultipartFile class

attribute

log.debug(photo1.getOriginalFilename()); // The original name of the uploaded file zsf.webp zcs.jpg
log.debug(photo1.getContentType()); //MIME type tomcat web. XML has MIME list HTML text/ HTML; jpg images/jpeg txt text/plain
log.debug(photo1.getName()); 
log.debug(photo1.getSize()+""); // File size
//log.debug(photo1.getBytes()+""); // File pairs correspond to the contents of the byte array
log.debug(photo1.getInputStream()+"");// Upload file requires input stream, download file requires output stream

Copy the code

File upload – Perfect

1. If the directory does not exist, create it

 // Perfection 1: Create a folder if it doesn't already exist
    File  dir = new File("d:/upload");
    if(! dir.exists()){//dir.mkdir(); // Only d:/upload can be created
        dir.mkdirs();D :/upload/ ABC /def/ghi
    }

Copy the code

2. Avoid file overwriting with the same name (UUID)

UUID, short for universal unique Identifier (UUID), is a software construction standard and part of the Open Software Foundation’s efforts in distributed computing environments. The idea is that all elements in a distributed system can have unique information, without the need for a central controller to identify information. With such dependencies, each person can create a UUID that does not conflict with others. In this case, there is no need to worry about name duplication at database creation.

Note: Only unique is guaranteed, not ordered.

The UUID consists of the following parts:

  1. The current date and event, the first part of the UUID is time dependent. If you generate a UUID a few seconds later, the first part is different and the rest are the same.
  2. Clock sequence.
  3. The globally unique IEEE and its identification number are obtained from the MAC address of the nic if there is a nic, and obtained in other ways if there is no NIC.

use

 String fileNewName = UUID.randomUUID().toString(); //36f0b33c-e10d-440e-927b-2d96e6d9f051
    String extName = photo1.getOriginalFilename().substring(photo1.getOriginalFilename().lastIndexOf(".")); //zadfad.adfad.sf.webp
    fileNewName += extName;

Copy the code

3. Limit the type of files to be uploaded

You are advised to limit by MIME type.

 // Method 1: limit by extension
// if(extName! =null &&(extName.equals(".jpg") || extName.equals(".gif"))){ }
 // Method 2: Limit by MIME type
 String contentType = photo1.getContentType().toLowerCase();
 if(!"image/jpeg".equals(contentType) && !"image/gif".equals(contentType)){ // If not image/jpeg image/ GIF
     model.addAttribute("error"."Upload JPG and GIF files only");
     return "error";

Copy the code

4. Limit the size of uploaded files

Method 1: After receiving, obtain the attributes to see whether they meet the requirements.

{...
// Perfection 4: Limit the size of uploaded files (not recommended)
long size = photo1.getSize();
if(size>10*1024) {//10K
    model.addAttribute("error"."Upload file cannot exceed 10K");
    return "error";
}
Copy the code

Disadvantages:

If according to this way, then is in the sub controller limit size, low efficiency.

Option 2[Recommended] : Limit the file size through the master controller (spring-mVC.xml).

<! -- Configure multimedia parser, related to file upload -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="utf-8"></property>
    <! -- Limit the total size of multiple files uploaded at once -->
    <property name="maxUploadSize" value="100000"></property>
    <! -- Limit the size of each uploaded file -->
    <property name="maxUploadSizePerFile" value="10000"></property>
</bean>

Copy the code

5 Replace physical paths with logical paths

The reason:

  1. There’s no concept of drive letters in Linux, there’s no concept of drive letters in Windows. The upload folder cannot be specified uniformly, for example, d:/upload.
  2. You can also have the upload folder in the project (not the development-time directory, but the post-deployment directory), which may vary from server to server. Tomcat and IDEA are installed in different locations, resulting in different directories.

Solution: Use logical paths instead of physical paths

How to operate?

This is done through the getRealPath method of the servletContext object generated at project startup. Its String argument refers to the path (logical path) after the root directory path of the deployed project, which returns a full physical path.

// Perfection 5: Use logical paths instead of physical paths
String upDir = "/upload"; // Logical path
String realPath = servletContext.getRealPath(upDir);
/ / physical path c: / adfa/adfadf/adfad/upload/adfad/adfad/upload

// Perfection 1: Create a folder if it doesn't already exist
File  dir = new File(realPath);
if(! dir.exists()){//dir.mkdir(); // Only d:/upload can be created
    dir.mkdirs();D :/upload/ ABC /def/ghi
}

Copy the code

Pay attention to

If war is selected during project release, a WAR package is created and automatically deployed to the Tomcat WebApp. The war package is decompressed automatically when the server is started.

If the release project is chosen with the War exploded package, it is not packaged as a WAR package and published to the Tomcat Webapp (in a directory of IDEA).

File upload – Use a file server

Some drawbacks of file uploads without a file server:

  1. When a Web application is redeployed, the old build results are usually cleaned up. In this case, the files uploaded by users are deleted, resulting in data loss.
  2. After a project runs for a long time, it will upload a large number of file base classes, which will slow down Tomcat.
  3. When the server is running in cluster mode, a file is uploaded to one instance of the cluster, but the file is not present in other instances, causing data inconsistencies.
  4. Dynamic capacity expansion is not supported. Once a new hard disk or server instance is added to the system, the path used for uploading and downloading will change. As a result, The Java code needs to be rewritten and compiled, and the entire project will be redeployed.

Solution:

Provide a dedicated file server (one will do). The one running Tomcat is called the application server (there may be multiple clusters).

File server type:

Third-party platforms (such as Ali’s OSS object storage service and Qiuniuyun);

Build your own server: FastDFS.

File download

<h3>File download</h3>
 <img th:src="@{/upload/35b27821-710e-444a-8f5e-226dd7fb5116.gif}"><br>
 <a th:href="@{/upload/35b27821-710e-444a-8f5e-226dd7fb5116.gif}">Download (open directly)</a>
 <hr>
<a th:href="@{/user/download(photoName=35b27821-710e-444a-8f5e-226dd7fb5116.gif)}">Download (pop-up prompt to download)</a>

Copy the code
@RequestMapping("/user/download")
public void download(String photoName, HttpServletResponse response) throws IOException {
    // Create input streams and output streams
    String realPath = servletContext.getRealPath("/upload"); /C:\\Users\\Administrator\\IdeaProjects\\springmvc\\pro11_updownload\\target\\pro11_updownload-1.0-SNAPSHOT\\upload
    File file = new File(realPath,photoName);//
    InputStream is = new FileInputStream(file);
    OutputStream os = response.getOutputStream(); // Write to the client, that is download
    // Specify three response headers (actually one) to control the processing of the browser, not directly display, but popup
    //response.setContentLength((int)file.length());
    //response.setContentType(); //MIME types are also stored in the database
    Suggestion: When saving user information to the database, save the original name, UUID name, and MIME type of the photo
  response.setHeader("Content-Disposition"."attachment; filename=zwj.gif"); / / attachment in the attachment
    // Use input streams and output streams
    IOUtils.copy(is,os);
    // Close the input and output streams
    is.close();
    os.close();
}

Copy the code

What if the file is not in the project directory, but in the D: \upload project?

  1. You cannot specify a path directly in a hyperlink

    <a th:href="@{/upload/35b27821-710e-444a-8f5e-226dd7fb5116.gif}">Download (open directly)</a><! -- False -->
    Copy the code
  2. Whether directly open, or download attachments, to use programming

    File file = new File("d:/upload",photoName);
    response.setHeader("Content-Disposition"."attachment; filename=zwj.gif"); / / attachment in the attachment
    response.setHeader("Content-Disposition"."inline"); // Open it directly
    
    Copy the code

Supplementary content [not important]

1. Default location and name of the SpringMVC configuration file

<servlet>
      <servlet-name>DispatcherServlet</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<! -- <init-param>-->
<! -- <param-name>contextConfigLocation</param-name>-->
<! -- <param-value>classpath:spring-mvc.xml</param-value>-->
<! -- </init-param>-->
      <load-on-startup>1</load-on-startup>
   </servlet>

Copy the code

2. More parameters for @requestMapping

demand Mapping way
According to the Accept – Language: useful – CN, useful; Q = 0.8 mapping @RequestMapping ( value=”/xxx”, headers= “Accept-Language=zh-CN,en; Q = 0.8 “)
The request parameter must contain userName @RequestMapping(value = “/xxx”, params=”userName”)
The request parameters cannot contain userName @RequestMapping(value = “/xxx”, params=”! userName”)

3. @ModelAttribute

@ModelAttribute
public void before(a){
    System.out.println("-------- executes each method of the sub-controller before calling it ----------");
}

Copy the code

The effect

  1. Executes before each handler method.
  2. Some data can be pre-stored in the request domain (application)

How Spring MVC works [Important]

The core process of SpringMVC

Execute the process

  1. First, the browser sends the request — >DipatcherServlet, and the front-end controller does not process the request itself after receiving it, but delegates it to other components for processing, as a unified access point for global process control.
  2. DispatcherServlet — >HandlerMapping. The Handler mapper will map requests to HandlerExecutionChain objects (containing one Handler Handler object and multiple HanlderInterceptor interceptor objects).
  3. DispatcherServlet — >HandlerAdapter, the processor adapter will wrap the processor as an adapter to support many types of processors, that is, the application of the adapter design pattern, making it easy to support many types of processors.
  4. HandlerAdapter — > Calls the handler to handle the corresponding function and returns a ModelAndView object (the Model part is the Model data returned by the business object, and the View part is the logical View name)
  5. DispatcherServlet — >ViewResolver, the View parser will parse the logical View name into the physical View and return the View object.
  6. DispatcherServlet — >View, which will render based on the passed Model data, which is actually a Map data structure
  7. DispatcherServlet — > response, returns control to the DispatcherServlet, which returns the response to the user, and the process ends.

Conclusion emphasis

  1. The master controller is not out of control; it asks for results every time it calls another component, and calls the next component based on the results.
  2. Return value of sub-controller: ModelAndView. Model is the data, and View is the View to jump to.
  3. If it’s an Ajax request, you don’t have a ModelAndView, you just return a piece of data. The client processes the data fragment by calling back the illusion.
  4. The SpringMVC process is fixed, and the main tasks of the developer are to develop sub-controllers, view files, and possibly interceptors.

For SpringMVC core API

There are four components of springMVC

  1. DispatcherServlet: Master controller.
  2. HandlerMapping: Establishes a mapping between request paths and sub-controllers.
  3. HandlerExecutionChain: The return value of a call to the HandlerMapping component by the master controller is a chain of execution, consisting not only of the sub-controller methods to execute, but also of the interceptors that respond.
  4. HandlerAdapter: Methods that call sub-controllers, not between master controllers, but by HanlderAdapter.
  5. [have] ViewResolver: logical view (result) – > physical view (/ WEB – INF/templates/result. HTML)

① Central controller DispatcherServlet

It is found that it has an HttpServlet in its parent class, and the Servlet’s execution entry is Service ().

② HandlerMapping

There will be multiple @RequestMapping in a project, and each RequestMapping corresponds to a class or method. How does a user get a method for a request path? This is done through HandlerMapping. The result returned is the processor for the access path.

③ Processor execution chain HandlerExecutionChain

Why request Handler, return HandlerExecutionChain?

This is because Handler execution is preceded by one or more interceptors, and interceptors are executed in chain mode. All handlerExecutionChains contain information about one handler and interceptors to execute.

④ HandlerAdapter

The execution of the processor is not performed directly by the master controller, but through the processor adapter. Because there will be XML, annotation, and other processor forms, the specific execution will be different, through different HandlerAdapter implementation.

Including RequestMappingHandlerAdapter implementation class is responsible for parsing mapping annotation way.

SimpleServletHandlerAdapter implementation class is XML mode of the adapter.

⑤ ViewResolver

ViewResolver implements logical view to physical view resolution, for example for the following ViewResolver.

public interface ViewResolver{
	@Nullable
	View resolveViewName(String var1,Locale var2)throws Exception;
}
Copy the code

“Main” is the logical view, and the suffix – prefixed web-INF/JSP /main.jsp is the physical view.

Request processing

The doDispatch() method coordinates and dispatches the entire request process at a macro level, and understanding this method gives you an idea of how SpringMVC handles requests in general.

In class: org. Springframework. Web. Servlet. DispatcherServlet

Method: doDispatch()

Source code for this method:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

                try {
                    processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest ! = request;/* The first step is to get the execution chain (one handler and multiple interceptors) */
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
				/* Step 2, get the adapter and use it to execute handler*/
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return; }}/* The preHandler method of the interceptor is not executed if it returns false
                    if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
                    }
				/* Step 4, execute handler to return ModelAndView*/
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    /* postHandler*/
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
				/* Step 6, the processDispatchResult() method parses the view, renders the view, and executes afterCompletion*/ of the interceptor
                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); }}}Copy the code

Request processing source code interpretation

1 Call HandlerMapping to get the chain of execution

Find the login of the sub-controller method to be executed in the execution chain, UserController, wrapped as a HandlerMethod.

Can be found that there are three interceptors, a system default ConversionServiceExposingInterceptor, the other two are custom interceptors, execution sequence and allocation sequence.

ConversionServiceExposingInterceptor is Spring A HandlerInterceptor, MVC is used to add an attribute to the request, the attribute name for ConversionService. Class. The getName (), value is the Spring MVC configuration defines a type conversion services. This type conversion service is used to convert the request parameters or return values during request processing.

By default, the Spring MVC configuration mechanism will actively building a ConversionServiceExposingInterceptor apply to all requests.

2 for HandlerAdapter

If you use annotations, call RequestMappingHandlerAdapter.

Execute the interceptor’s PreHandler in sequence

Pay attention to

If the interceptor’s preHandler method results in false, handler, postHandler, is not executed, but afterCompletion methods are still executed.

4 execute the split controller method and return ModelAndView

The underlying ModelAndView is a HashMap structure in which elements’ keys have attributes such as View, Model, Status, cleared, and so on. The view holds the logical view and the Model holds the data.

Execute interceptor postHandler in reverse order

6 Reverse the afterComoletion of the interceptor

SpringMVC startup process

1 Initialize the operation invocation roadmap

ApplicationContext is refer to the IoC container, if not the web project, before I use ClasspathApplicationContext for using the ApplicationContext. In the case of a Web project, the WebApplicationContext is used

Debugging tips:

Forward and backward in the debug page: CRTL + Alt + (<– –>)

2 initWebApplicationContext method

Methods the source code

protected WebApplicationContext initWebApplicationContext(a) {
    /* Create IoC container and return */
    /* The parent container, from the ServletContext, looks for an IoC container that exists at the first level. If it exists, it will be the parent of the IoC container that is being created
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    /* WaC will now create the container */
        WebApplicationContext wac = null;
        if (this.webApplicationContext ! =null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if(! cwac.isActive()) {if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }

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

        if (wac == null) {
            /* Create the current container, rootContext may be empty */
            wac = this.createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            synchronized(this.onRefreshMonitor) {
                this.onRefresh(wac); }}if (this.publishContext) {
            String attrName = this.getServletContextAttributeName();
            /* Put the currently created IoC container into the ServletContext, which is available to all requests from all users, equivalent to a global resource */
            this.getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }

Copy the code

3 createWebApplicationContext

  protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<? > contextClass =this.getContextClass();
        if(! ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            /* Create IoC container */
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
 
            wac.setEnvironment(this.getEnvironment());
            /* Specifies who the parent container is */
            wac.setParent(parent);
            String configLocation = this.getContextConfigLocation();
            if(configLocation ! =null) {
                wac.setConfigLocation(configLocation);
            }
			/* This method is used to read files, configure container properties, initialize, etc.. * /
            this.configureAndRefreshWebApplicationContext(wac);
            /* Return the created IoC container */
            returnwac; }}Copy the code

4 Locate the code that gets the springMVC configuration file path name

You can see in an internal class ServletConfigPropertyValues HttpServletBean classes

 private static class ServletConfigPropertyValues extends MutablePropertyValues {
        public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException { Set<String> missingProps = ! CollectionUtils.isEmpty(requiredProperties) ?new HashSet(requiredProperties) : null;
            Enumeration paramNames = config.getInitParameterNames();

            while(paramNames.hasMoreElements()) {
                // From the breakpoint you can see property=contextConfigLocationString property = (String)paramNames.nextElement(); \// Value =classpath: spring-mvC.xml
                Object value = config.getInitParameter(property);
                this.addPropertyValue(new PropertyValue(property, value));
                if(missingProps ! =null) { missingProps.remove(property); }}if(! CollectionUtils.isEmpty(missingProps)) {throw new ServletException("Initialization from ServletConfig for servlet '" + config.getServletName() + "' failed; the following required properties were missing: " + StringUtils.collectionToDelimitedString(missingProps, ",")); }}}Copy the code

Locate and read the springMVC configuration file and the codes in the @RequestMapping content of the sub-controller

protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
    /* Initializing @requestMapping */
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
    /* Initializes the view parser */
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }
Copy the code

ContextLoaderListener principle source code

Note: The technical solution we discuss in this section does not “have to” do this, but only “can” do it.

1. Introduction of problems

After the SSM is consolidated, the SSM configuration file contains too much content. You can divide it into two configuration files. Configure how the two folders are loaded.

Method 1: DispatcherServlet loads all configuration files

<! -- Configure SpringMVC master controller, unique Servlet -->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <! -- Specify the name and location of the SpringMVC configuration file, with default location -->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring*.xml</param-value>
    </init-param>
    <! Load the master controller when you start the server
    <load-on-startup>1</load-on-startup>
</servlet>


Copy the code

Features: There is only one IoC container that holds all the beans

Method 2: DispatcherServlet loads the SpringMVC configuration file and uses ContextLoaderListener to load another configuration file.

Features:

  • There will be two IoC containers
  • The parent container when loading an IoC container created by another configuration file with ContextLoaderListener; The IoC container created by the SpringMVC configuration file loaded by the DispatcherServlet is a child container.

Note:

The loading sequence of Servlet, Filter, Listener is Linstener>Filter>Servlet

2. Create IoC containers using Linstener

<! -- Configure global context parameters -->
<context-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>classpath:spring-persist.xml</param-value>
</context-param>
<! -- Specify listener -->
<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Copy the code

The ServletContextListener class: listens for project context with only two methods.

public interface ServletContextListener extends EventListener {
// Execute this method when the project starts
    void contextInitialized(ServletContextEvent var1);
// This method is executed when the project is destroyed
    void contextDestroyed(ServletContextEvent var1);
}
Copy the code

Relationship between two containers:

Father and son relationship.

The IoC container created using ContextLoaderListener to load another configuration file is the parent container.

The IoC container created by the DispatcherServlet loading the SpringMVC configuration file is a child container.

3. Problems and solutions

Question:

If the path of

is not set properly, the Bean is created repeatedly.

How to check:

Change the total log level of logback to DEBUG.

Disadvantages:

  1. Duplicate beans consume more resources.
  2. The Controller created by Spring-MVC calls the Service and Dao created by spring-MVC. However, there is no transaction setting in the configuration file of Spring-MVC. So calling services and DAOs created by SpringMVC will not be able to use transactions, which is a serious problem.

Solution 1: Scan for different packages in both profiles.

<! -- Configure annotation scan base path -->
<context:component-scan base-package="com.atguigu.service,com.atguigu.dao"></context:component-scan>

Copy the code
<! -- Configure annotation scan base path -->
<context:component-scan base-package="com.atguigu.controller"></context:component-scan>

Copy the code

Results:

The Controller is created in SpringMVC, the Service is created in Listener and the transaction is applied. When SpringMVC can’t find a Service in its own IoC container, it looks for it in the parent container. Problem solved.

Solution 2: Scan for different packages in both profiles

Method 1:

Context :include-filter Specifies the packets to be scanned

It is worth noting:

Use context:include-filter with caution. Its mode is loaded by default. Turn off the default scanning mode [use-default-filters=”false”] and it will only scan the include folder.

<! Configure the reference path for annotation scanning:  use-default-filters="true" @Controller @Service @Repository @Component use-default-filters="false" Scan only the annotations specified by include-filter in the base package -->
<context:component-scan base-package="com.atguigu" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

Copy the code

Method 2:

Context :exclude-filter Specifies packets that are not scanned.

<! -- Configure annotation scan base path: @controller@servicer@repository @component -->
<context:component-scan base-package="com.atguigu">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

Copy the code