Mind mapping

Wechat public account has been opened: [Java technology lovers], remember to pay attention to those who haven’t

This article has been included in my Github selection, welcome to Star: github.com/yehongzhi/l…

An overview of the

SpringMVC is a very familiar framework, because the most popular built-in MVC framework for SpringBoot is SpringMVC. My motivation for writing this article is to review and relearn SpringMVC.

To understand SpringMVC, look at a process diagram:

From the flow chart, we can see:

  • Receiving Request requests from the front-end.
  • Find the corresponding processor to process the request according to the mapping path, and return to ModelAndView after processing.
  • Perform view parsing, view rendering, and return response results.

The summary is: parameters receive, define mapping path, page jump, return response result.

Of course, this is just the most basic core functionality, in addition to the definition of interceptors, global exception handling, file upload and download, etc.

I. Construction project

In the old project, since there was no SpringBoot and no automatic configuration, you needed to use the web. XML file to define a DispatcherServlet. Most Internet applications now use SpringBoot, so I’m going to go straight to SpringBoot. It’s easy to introduce dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Copy the code

2. Define Controller

There are five ways to define the Controller processor using SpringMVC.

2.1 Implement the Controller interface

Early SpringMVC was defined this way:

/ * * *@authorYe Hongzhi public account: Java technology enthusiast *@name DemoController
 * @dateThe 2020-08-25 "* * /
@org.springframework.stereotype.Controller("/demo/controller")
public class DemoController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        // Business processing
        return null; }}Copy the code

2.2 Implement the HttpRequestHandler interface

Similar to the first approach, this is also done by implementing the interface:

/ * * *@authorYe Hongzhi public account: Java technology enthusiast *@name HttpDemoController
 * @dateThe 2020-08-25 if * * /
@Controller("/http/controller")
public class HttpDemoController implements HttpRequestHandler{
    @Override
    public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
        // Business processing}}Copy the code

2.3 Implementing the Servlet Interface

This approach is no longer recommended, but you can see that the underlying SpringMVC uses servlets.

@Controller("/servlet/controller")
public class ServletDemoController implements Servlet {
    // The following are the Servlet lifecycle methods
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {}@Override
    public ServletConfig getServletConfig(a) {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {}@Override
    public String getServletInfo(a) {
        return null;
    }

    @Override
    public void destroy(a) {}}Copy the code

Since this is not recommended, the adapter is not loaded by default. Add:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public SimpleServletHandlerAdapter simpleServletHandlerAdapter(a) {
        return newSimpleServletHandlerAdapter(); }}Copy the code

2.4 using the @ RequestMapping

This approach is the most common because the above definitions require a single class to define a path, resulting in many classes. Using annotations is relatively lightweight.

@Controller
@RequestMapping("/requestMapping/controller")
public class RequestMappingController {

    @RequestMapping("/demo")
    public String demo(a) {
        return "HelloWord"; }}Copy the code

2.4.1 Restful style supported

It also supports Restful style, using the method attribute to define the operation mode on the resource:

	@RequestMapping(value = "/restful", method = RequestMethod.GET)
    public String get(a) {
        / / query
        return "get";
    }

    @RequestMapping(value = "/restful", method = RequestMethod.POST)
    public String post(a) {
        / / create
        return "post";
    }

    @RequestMapping(value = "/restful", method = RequestMethod.PUT)
    public String put(a) {
        / / update
        return "put";
    }

    @RequestMapping(value = "/restful", method = RequestMethod.DELETE)
    public String del(a) {
        / / delete
        return "post";
    }
Copy the code

2.4.2 Ant style is supported

	// Match /antA or /antB urls
    @RequestMapping("/ant?" )
    public String ant(a) {
        return "ant";
    }

    // Match /ant/a/create or /ant/b/create urls
    @RequestMapping("/ant/*/create")
    public String antCreate(a) {
        return "antCreate";
    }

    // Match /ant/create or /ant/a/b/create urls
    @RequestMapping("/ant/**/create")
    public String antAllCreate(a) {
        return "antAllCreate";
    }
Copy the code

2.5 use HandlerFunction

The last is to use the HandlerFunction functional interface, which was introduced after Spring5.0 and is mainly used for reactive interface development, namely Webflux development.

Those who are interested can search relevant data on the Internet to learn. This may be very large in length and is not repeated here.

3. Receiving parameters

After you define the Controller, you need to receive the parameters from the front end. How do you receive them?

3.1 Receiving Common Parameters

RequestMapping = RequestMapping = RequestMapping = RequestMapping = RequestMapping

@RequestMapping(value = "/restful", method = RequestMethod.POST)
public String post(Integer id, String name, int money) {
    System.out.println("id:" + id + ",name:" + name + ",money:" + money);
    return "post";
}
Copy the code

3.2 @requestParam Parameter name binding

If you don’t want to use the parameter name as the parameter name, you can use @requestParam to bind the parameter name:

	/** * value: parameter name * required: Indicates whether this parameter must be included in the request. The default value is true. * defaultValue: indicates the defaultValue */
    @RequestMapping(value = "/restful", method = RequestMethod.GET)
    public String get(@RequestParam(value = "userId", required = false, defaultValue = "0") String id) {
        System.out.println("id:" + id);
        return "get";
    }
Copy the code

3.3 @pathvariable Path parameter

Map the placeholder {XXX} parameter in the URL to the input parameter of the action method via @pathvariable. The demo code is as follows:

@RequestMapping(value = "/restful/{id}", method = RequestMethod.GET)
public String search(@PathVariable("id") String id) {
    System.out.println("id:" + id);
    return "search";
}
Copy the code

3.4 @requestheader bind the RequestHeader attribute

How to get the information about the request header?

Use the @requestheader annotation in a similar way to @requestParam:

	@RequestMapping("/head")
    public String head(@RequestHeader("Accept-Language") String acceptLanguage) {
        return acceptLanguage;
    }
Copy the code

3.5 @cookievalue Binds the Cookie value of the request

Get the value of Cookie in Request:

	@RequestMapping("/cookie")
    public String cookie(@CookieValue("_ga") String _ga) {
        return _ga;
    }
Copy the code

3.6 Binding request parameters to POJO objects

Defines a User entity class:

public class User {
    private String id;
    private String name;
    private Integer age;
    // Getter and setter methods
}
Copy the code

Define a @requestMapping operation:

	@RequestMapping("/body")
    public String body(User user) {
        return user.toString();
    }
Copy the code

As long as the request parameter is the same as the attribute name, the user object is automatically populated:

3.6.1 Cascading properties are supported

Now we have an Address class that stores Address information:

public class Address {
    private String id;
    private String name;
    // Getter and setter methods
}
Copy the code

Add the address attribute to User:

public class User {
    private String id;
    private String name;
    private Integer age;
    private Address address;
    // Getter and setter methods
}
Copy the code

Address. Name and address. Id will be filled automatically if they are passed as parameters:

3.6.2 @initBinder resolves property name conflicts when receiving multiple objects

If you have two POJO objects with the same property name, isn’t that a conflict? For example, user and address have id and name attributes, which would conflict if received at the same time:

	// Both user and address have id and name attributes
	@RequestMapping(value = "/twoBody", method = RequestMethod.POST)
    public String twoBody(User user, Address address) {
        return user.toString() + "," + address.toString();
    }
Copy the code

You can use @initbinder to bind the parameter name:

	@InitBinder("user")
    public void initBindUser(WebDataBinder webDataBinder) {
        webDataBinder.setFieldDefaultPrefix("u.");
    }

    @InitBinder("address")
    public void initBindAddress(WebDataBinder webDataBinder) {
        webDataBinder.setFieldDefaultPrefix("addr.");
    }
Copy the code

3.6.3 @RequestBody automatically parses JSON strings to encapsulate objects

The front-end passes in a JSON string, which is automatically converted to a POJO object.

	@RequestMapping(value = "/requestBody", method = RequestMethod.POST)
    public String requestBody(@RequestBody User user) {
        return user.toString();
    }
Copy the code

Note that to use a POST request, the content-type on the sender side is set to Application /json and the data is a JSON string:

There are even some people who prefer to receive with a Map:

However, do not use a Map to receive data, otherwise the code will be difficult to maintain, the old man may not understand your Map in the data, so it is best to define a POJO object.

4. Parameter type conversion

In fact, the SpringMVC framework has a number of type converters built in, such as the number you pass in as a string and the input parameter you receive as int, long, will automatically convert it for you.

In package of org. Springframework. Core. The convert. The converter, as shown in figure:

Sometimes if the built-in type converter is not enough to meet the business needs, how to expand, very simple, see me operation. What is a Java technology enthusiast (tactical backward)?

First of all, the built-in Converter implements the Converter interface, which I also implement:

public class StringToDateConverter implements Converter<String.Date> {
    @Override
    public Date convert(String source) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            //String is converted to Date
            return sdf.parse(source);
        } catch (Exception e) {
            // Type conversion error
            e.printStackTrace();
        }
        return null; }}Copy the code

Then register the converter with the Spring container:

@Configuration
public class ConverterConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addFormatters(FormatterRegistry registry) {
        // Add type converter
        registry.addConverter(newStringToDateConverter()); }}Copy the code

All Date strings are automatically converted to Date, which is very convenient:

5. Page hopping

Before the separation of the front and back end, the work of the page jump is controlled by the back end, using JSP to display data. Although JSP is almost never used in Internet projects today, I felt I needed to learn because some older projects still use JSP or require refactoring.

If you return a string in the RequestMapping method, you will not be redirected to the specified JSP page, which requires some configuration.

The first step is to add the Maven configuration to parse the JSP.

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>7.0.59</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>
Copy the code

Second, add a view parser.

@Configuration
public class WebAppConfig extends WebMvcConfigurerAdapter {
    @Bean
    public InternalResourceViewResolver viewResolver(a) {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setViewClass(JstlView.class);
        returnviewResolver; }}Copy the code

Step 3: Set the IDEA configuration.

Step 4, create the JSP page.

Step 5: Create Controller Controller.

@Controller
@RequestMapping("/view")
public class ViewController {
    @RequestMapping("/hello")
    public String hello(a) throws Exception {
        return "hello"; }}Copy the code

That’s it, start the project, visit /view/hello and see:

It’s that simple, right

Vi. @ ResponseBody

If the front and back end separation is adopted, the page jump does not need the back end control, the back end only needs to return JSON, how to return?

Just use the @responseBody annotation, which automatically returns the object as JSON data.

The @responseBody annotation can be placed on a class or method as follows:

// for classes and methods
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
Copy the code

To demonstrate:

@RequestMapping("/userList")
@ResponseBody
public List<User> userList(a) throws Exception {
    List<User> list = new ArrayList<>();
    list.add(new User("1".Yao Daqiu.18));
    list.add(new User("2"."Li Xing Xing".18));
    list.add(new User("3"."Dong min".18));
    return list;
}
Copy the code

Test /view/userList:

Seven, @ ModelAttribute

There are many uses of @modelattribute, which are explained in the following sections.

7.1 For methods with no return value

In the Controller class, the @ModelAttribute annotation method is executed before all RequestMapping methods.

@Controller
@RequestMapping("/modelAttribute")
public class ModelAttributeController {
	// Execute this method first
    @ModelAttribute
    public void modelAttribute(Model model){
        // Put data in the request field
        model.addAttribute("userName"."Public account: Java Technology enthusiast");
    }

    @RequestMapping("/index")
    public String index(a){
        // Jump to the inex.jsp page
        return "index"; }}Copy the code

The index.jsp page looks like this:

<%@ page contentType="text/html; charset=UTF-8" language="java"% > < HTML > < head > < title > home page < / title > < / head > < body > <! - get the attribute value - > < h1 > ${userName} < / h1 > < / body > < / HTML >Copy the code

Like a Controller interceptor, the @ModelAttribute annotation method is executed before RequestMapping. So be careful with it.

Start the project and access /modelAttribute/index to see:

Even if the userName attribute value is not put in the index() method, the JSP page will get it because the modelAttribute() method was put in before the index() method was executed.

7.2 On a method that returns a value

RequestMapping (RequestMapping, RequestMapping, RequestMapping, RequestMapping, RequestMapping, RequestMapping) ¶

// put it on the method with arguments
@ModelAttribute
public User userAttribute(a) {
    // equivalent to model.addattribute ("user",new user ("1", "Java technology enthusiast ", 18));
    return new User("1"."Java Technology enthusiast".18);
}

@RequestMapping("/user")
public String user(a) {
    return "user";
}
Copy the code

Create a user.jsp:

<%@ page contentType="text/html; charset=UTF-8" language="java"% > < HTML > < head > < title > home page < / title > < / head > < body > < h1 > ID: ${user. ID} < / h1 > < h1 > name: ${user. The name} < / h1 > <h1> age :${user.age} age </h1> </body> </ HTML >Copy the code

Test it out:

The default value of the property that you put into the Request field is the first letter of the class name lowercase camel case, if you want to customize it? It’s easy to write:

// Custom attribute named "u"
@ModelAttribute("u")
public User userAttribute(a) {
    return new User("1"."Java Technology enthusiast".18);
}
/ * * the JSP will change to write like this: < h1 > ID: ${u.i d} < / h1 > < h1 > name: ${u.n ame} < / h1 > < h1 > age: ${u.a ge}, < / h1 > * /
Copy the code

7.3 Putting it in RequestMapping

@Controller
@RequestMapping("/modelAttribute")
public class ModelAttributeController {
    
    @RequestMapping("/jojo")
    @ModelAttribute("attributeName")
    public String jojo(a) {
        return "Jo-jo! I don't want to be a human being!"; }}Copy the code

In this case, the value returned by the RequestMapping method is not the JSP view. Instead, you put the return value into the value of an attribute in the Request field called attributeName. The view is the URL in the RequestMapping annotation, so create a corresponding JSP page:

<%@ page contentType="text/html; charset=UTF-8" language="java"% > < HTML > < head > < title > home page < / title > < / head > < body > < h1 > ${attributeName} < / h1 > < / body > < / HTML >Copy the code

Test it out:

7.4 Put it on the method input parameter

Put on an input parameter, which means to extract the corresponding attribute value from the previous Model and pass it into the method as an input parameter. As follows:

@ModelAttribute("u")
public User userAttribute(a) {
    return new User("1"."Java Technology enthusiast".18);
}

@RequestMapping("/java")
public String user1(@ModelAttribute("u") User user) {
    // Take the value returned by the @modelAttribute ("u") method and print it
    System.out.println("user:" + user);
    return "java";
}
Copy the code

Test it out:

8. Interceptor

Interceptors are important. They are used for many purposes, such as login verification, permission verification, and so on. How does SpringMVC add interceptors?

The HandlerInterceptor interface has three methods that need to be overwritten.

  • PreHandle () : Called before the request is processed by the business handler. Pretreatment.
  • PostHandle () : executed after the business processor completes the execution of the request and before the view is generated. Post-processing.
  • AfterCompletion () : Called after the DispatcherServlet has fully processed the request and can be used to clean up resources, etc. Return processing (the page has already been rendered);

Custom interceptor, implementation of the interface HandlerInterceptor:

public class DemoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Preprocess, return true and continue execution. If login verification is required, return false if login verification fails or true if login verification passes.
        System.out.println("Execute the preHandle() method");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        / / post-processing
        System.out.println("Execute postHandle() method");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // called after the DispatcherServlet has fully processed the request
        System.out.println("Execute afterCompletion() method"); }}Copy the code

Then add the interceptor to the Spring container:

@Configuration
public class ConverterConfig extends WebMvcConfigurationSupport {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/ * *"); }}Copy the code

/** represents all paths.

Global exception handling

SpringMVC itself handles some exceptions globally, so there’s a built-in exception handler, where is that?

See the class diagram for the HandlerExceptionResolver interface:

You can see from the class diagram that there are four types of exception handlers:

  • DefaultHandlerExceptionResolver, default exception handler. Returns different exception views based on different types of exceptions.
  • SimpleMappingExceptionResolver, simple mapping exception handlers. Resolve exceptions by configuring the relationship between the exception class and the View.
  • ResponseStatusExceptionResolver, status code exception handler. Resolve with@ResponseStatusAnnotation type exception.
  • ExceptionHandlerExceptionResolverException handlers in the form of annotations. right@ExceptionHandlerAnnotation method for exception resolution.

The first default exception handler is the built-in exception handler, which is generally ignored for some common exception handling. The last three are the ones to watch out for, and are for extension.

9.1 SimpleMappingExceptionResolver

This translates to simply mapping exception handlers. The purpose is that we can specify an exception and jump to the specified page when it is thrown. Look at the demonstration.

Add the spring-config. XML file to the resources directory.


      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <! Define the default exception handling page -->
        <property name="defaultErrorView" value="err"/>
        <! -- Define the name of the attribute used by the exception handling page to obtain the exception information. Default is exception -->
        <property name="exceptionAttribute" value="ex"/>
        <! -- Define exceptions that require special handling, using the class name or full pathname as the key and the exception as the page name as the value -->
        <property name="exceptionMappings">
            <props>
                <! -- exception, err means err.jsp page -->
                <prop key="java.lang.Exception">err</prop>
                <! -- Can be configured with multiple prop -->
            </props>
        </property>
    </bean>
</beans>
Copy the code

The second step is to load the XML file in the startup class:

@SpringBootApplication
@ImportResource("classpath:spring-config.xml")
public class SpringmvcApplication {

    public static void main(String[] args) { SpringApplication.run(SpringmvcApplication.class, args); }}Copy the code

Step 3: Create an err.jsp page in the webApp directory:

<%@ page contentType="text/html; charset=UTF-8" language="java"%> < HTML > <head> <title> </head> <body> <h1500Page </h1> <br> <%-- Print Exception to page --%> <% Exception ex = (Exception)request.getAttribute("ex"); %>
<br>
<div><%=ex.getMessage()%></div>
<% ex.printStackTrace(new java.io.PrintWriter(out)); %>
</body>
</html>
Copy the code

That’s it. Write an interface test:

@Controller
@RequestMapping("/exception")
public class ExceptionController {
    @RequestMapping("/index")
    public String index(String msg) throws Exception {
        if ("null".equals(msg)) {
            // Throw a null pointer exception
            throw new NullPointerException();
        }
        return "index"; }}Copy the code

The effect is as follows:

This kind of exception handler is rarely seen in today’s projects where the front and back ends are separated.

9.2 ResponseStatusExceptionResolver

This exception handler is primarily used to handle exceptions with the @responseStatus annotation. Look at the demo code:

Define an exception class and decorate it with the @responseStatus annotation:

// The HttpStatus enumeration has all the status codes and returns a response code of 400
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class DefinedException extends Exception{}Copy the code

Write a Controller interface to test:

@RequestMapping("/defined")
public String defined(String msg) throws Exception {
    if ("defined".equals(msg)) {
        throw new DefinedException();
    }
    return "index";
}
Copy the code

Start the project and test it. It looks like this:

9.3 ExceptionHandlerExceptionResolver

Exception handlers in the form of annotations, which are the most commonly used. It is very simple and convenient to use.

Step 1, define a custom BaseException:

public class BaseException extends Exception {
    public BaseException(String message) {
        super(message); }}Copy the code

Second, define an error notification entity class ErrorInfo:

public class ErrorInfo {
    public static final Integer OK = 0;
    public static final Integer ERROR = -1;
    private Integer code;
    private String message;
    private String url;
    / / getter and setter
}
Copy the code

Third, define the GlobalExceptionHandler class:

// RestControllerAdvice is used here, which is a combination of @responseBody and @ControllerAdvice
// Returns a prompt that converts the entity class to JSON format, in line with the back-end separation architecture
@RestControllerAdvice
public class GlobalExceptionHandler {

    // There is a custom BaseException, which is handled by this method when a BaseException is thrown
    @ExceptionHandler(BaseException.class)
    public ErrorInfo errorHandler(HttpServletRequest req, BaseException e) throws Exception {
        ErrorInfo r = new ErrorInfo();
        r.setMessage(e.getMessage());
        r.setCode(ErrorInfo.ERROR);
        r.setUrl(req.getRequestURL().toString());
        returnr; }}Copy the code

When done, write a test interface:

@RequestMapping("/base")
public String base(String msg) throws Exception {
    if ("base".equals(msg)) {
        throw new BaseException("Test to throw BaseException, oh yeah!");
    }
    return "index";
}
Copy the code

Start the project, test:

omg

There’s definitely more to SpringMVC than what I’ve written, but once you’ve learned the above, you’re basically ready for everyday work.

If you want to go a little further, it is best to look at the source code of SpringMVC. I have written three previous articles, the Chain of Responsibility pattern and SpringMVC interceptor, the adapter pattern and SpringMVC, and the source code analysis of global exception handling. If you are interested, you can follow the official account to see my historical articles.

Wechat public account has been opened: [Java technology lovers], remember to pay attention to those who did not follow oh ~

Adhere to the original, continue to output both breadth and depth of technical articles.

This article has been included in my Github selection, welcome to Star: github.com/yehongzhi/l…

The code for all of the above examples is uploaded to Github:

Github.com/yehongzhi/m…

Please give me a thumbs-up if you think it is useful. Your thumbs-up is the biggest motivation for my creation

Refusing to be a salt fish, I’m a programmer trying to be remembered. See you next time!!

Ability is limited, if there is any mistake or improper place, please criticize and correct, study together!