Java - Spring MVC Framework Details

Posted by lettheflamesbegin on Tue, 19 Oct 2021 19:06:58 +0200

1. Comments on @RequestMapping

Add the @RequestMapping annotation before the method to process the request to configure the mapping relationship between the request path and the method to process the request!

In this note it is declared that:

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

And:

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

When using annotations, you can configure a property named value whose value is an array of string types:

@RequestMapping(value={"Value 1", "Value 2", "Value 3"})

The purpose of this property is to configure the path of the mapping, which is the default property, so it is not necessary to explicitly declare the property name when configuring, that is, the above configuration can be simplified to:

@RequestMapping({"Value 1", "Value 2", "Value 3"})

When configuring annotated attributes, if the value of the attribute is of an array type, its value can be in an array format or an element of it and does not necessarily need to be written as an array, for example:

@RequestMapping("Value 1")

Therefore, the following are equivalent:

@RequestMapping(value={"reg.do"})
@RequestMapping({"reg.do"})
@RequestMapping(value="reg.do")
@RequestMapping("reg.do")

That is, when the value is in the array format and only one value is currently set, it does not matter if the value is not written in the array format. When the value is set, you do not need to write out the value=, but write the value directly!

The path and value attributes in this annotation are exactly equivalent, and the path was added from SpringMVC version 4.2!

Furthermore, this property is not only used for paths prior to the configuration method, but can also be declared and configured before the controller class, for example:

@RequestMapping("user")
@Controller
public class UserController {

	@RequestMapping("login.do")
	public String showLogin() {
		return "login";
	}

}

After adding annotations before the class, when the path mapped before a method is configured in the access controller class, the path before the class needs to be added to the URL before the method! For example, with the code above, the access path should be http://localhost:8080/ Project name/user/login.do!

Also, after adding annotations before classes, this layer of paths must be added before the path mapped by each method of the current controller class!

It is strongly recommended to add this comment before each controller class!

When configuring paths, redundant / negligible on both left and right sides, and when configuring both classes and methods before, there is no need to consider the / problem between annotation paths of class annotation path splicing methods! In other words, adding configurations before classes and methods is equivalent:

/user	/login.do
/user	login.do
/user/	/login.do
/user/	login.do
user	/login.do
user	login.do
user/	/login.do
user/	login.do

The above practices are equivalent, in practice, select one of them and keep using a fixed style in the project!

In addition, in the source code of the comment, it states:

RequestMethod[] method() default {};

The above attributes are used to constrain how requests are requested! Assume that the controller is configured to:

@RequestMapping(path="handle_reg.do", method=RequestMethod.POST)

A 405 error occurs when a client submits a request using GET:

HTTP Status 405 – Method Not Allowed

Message : Request method 'GET' not supported

When multiple attributes are configured in a comment, the attribute name must be explicitly specified in the configuration of each attribute! Only if you configure one property and the property is default, you do not have to explicitly specify the property name!

2. Comments on @RequestParam

You can add the @RequestParam comment before the parameters of the method that handles the request!

In this note it is declared that:

@AliasFor("name")
String value() default "";

@AliasFor("value")
String name() default "";

That is, you can configure the value or name attributes in the comment, which are equivalent! Its function is to configure which request parameters will be submitted by the client to bind to the parameters of the current method! By default, assuming the client submits a parameter named username, declaring String username in the parameter list of the method that handles the request obtains the value of the parameter submitted by the client, that is, as long as it has the same name! However, if the names do not match, you can configure them with this annotation!

Assuming the client submits a request parameter named uname, comment on the parameter in the way the controller handles the request:

@RequestParam("uname") String username

Request parameters can also be obtained normally! So the purpose of the above configuration is to bind the parameter named uname submitted by the client to the parameter named username in the method of processing the request!

When the above comment is added, if the client makes a request without submitting a parameter with a matching name, by default, 400 errors will occur:

HTTP Status 400 – Bad Request

Message : Required String parameter 'uname' is not present

The above problem occurs because of the existence in the source code of @RequestParam:

boolean required() default true;

That is, the source code declares a property named required with a boolean value and a true default value!

Therefore, if you must use this annotation and allow the request parameter to be uncommitted, you can configure it to:

@RequestParam(name="uname", required=false) String username

Finally, the source code for this comment includes:

String defaultValue() default ValueConstants.DEFAULT_NONE;

This property is used to configure the default value, that is, when the client does not submit the specified request parameters, it is equivalent to submitting a value! For example, configure:

@RequestParam(name="uname", required=false, defaultValue="admin") String username

Of course, when using the defaultValue property, be sure to set the required property to false!

3. About Session

Because the HTTP protocol is stateless, the server cannot distinguish between multiple requests from the same client, and if you need to save some data or state of the user, you need to use Session.

Session is data stored in memory on the server side, and after each client submits a request, there will be a copy of the data on the server side that is dedicated to this client!

In Spring MVC, if you need to use Session, you only need to add a parameter of type HttpSession to the list of parameters of the method that handles the request, and then, during processing the request, call the API of the parameter object to encapsulate or retrieve the data.

Using HttpSession directly as a parameter for the method of processing requests, the main problem is that it is not easy to execute unit tests!

In Spring MVC, there is also a framework for managing Sessions by placing data encapsulated in Session in ModelMap, that is, either data to be forwarded or Session data in ModelMap, and then adding the @SessionAttributes annotation before the controller class to configure it "Which attribute of the data in ModelMap is the data in Session" and this is not really putting the data in Session, it is managed by the framework and cannot be cleaned up even by calling the invalidate() method of HttpSession!

Overall, using SpringMVC to manage Sessions is relatively cumbersome. Although HttpSession is not easy to perform unit tests, it is very simple to use and the code is intuitive, so HttpSession is generally used more.

@RequestMapping(path = "handleLogin.do", method = { RequestMethod.GET, RequestMethod.POST})
public String handleLogin (
          String username,
          String password,
          @RequestParam(name = "sex", required = false, defaultValue = "Unknown") String sex,
          String[] skill,
          ModelMap modelMap,
          HttpSession session
  ) {
      System.out.println("username = " + username);
      System.out.println("password = " + password);
      System.out.println("sex = " + sex);
      System.out.println("skill = " + skill);
//        System.out.println("skill.length = " + skill.length);
//        if (skill.length > 0) {
//            for (int i = 0; i < skill.length; i++) {
//                System.out.println("skill[" + i + "] = " + skill[i]);
//            }
//        }

      if ("admin".equals(username)) {
          if ("123456".equals(password)) {
              System.out.println("Login Successful");
              session.setAttribute("id", username + password);
              modelMap.addAttribute("username", username);
              return "redirect:../main/index.do";
          } else {
              System.out.println("Password error");
              return "loginFailed";
          }
      } else {
          System.out.println("user name does not exist");
          return "loginFailed";
      }
  }

4. Interceptor

Interceptor: A code component that enables several requests to automatically execute one of them! The component has the option to let go of the requests it processes or to prevent execution from continuing!

In the Spring MVC project, if interceptors are required, first, custom classes are needed to implement the HandlerInterceptor interceptor interface:

public class LoginInterceptor implements HandlerInterceptor {
	
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("LoginInterceptor.preHandle()");
		return false;
	}

	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("LoginInterceptor.postHandle()");
	}

	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("LoginInterceptor.afterCompletion()");
	}

}

Then add the configuration in spring.xml:

<!-- Configure Interceptor Chain -->
<mvc:interceptors>
	<!-- Configure the first interceptor -->
	<mvc:interceptor>
		<!-- Intercept Path -->
		<mvc:mapping path="/main/index.do" />
		<!-- Interceptor Class -->
		<bean class="cn.tedu.spring.LoginInterceptor"></bean>
	</mvc:interceptor>		
	<!-- Configure the second interceptor -->
</mvc:interceptors>

You can see that when the preHandle() method of the interceptor class returns false, it prevents further running, a blank space is displayed in the browser's page, and when true is returned, it releases, and preHandle() -> the method in the controller class -> postHandle () -> afterCompletion () is executed in turn.

So, the only way to truly intercept is the preHandle() method!

If you need to achieve the effect of login interception, that is, if you are already logged in, allow normal access, and if you are not logged in, redirect to the login page. In the interceptor's preHandle() method, you can judge the data in Session, choose to let it go or block it, and redirect it to the login page when it blocks it! For example:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
	System.out.println("LoginInterceptor.preHandle()");
	HttpSession session = request.getSession();
	if (session.getAttribute("id") == null) {
		// Redirection: absolute path must be used!
		response.sendRedirect(request.getContextPath() + "/user/login.do");
		// Returning false means intercepted, no more execution
		return false; // //You must return false here or the program will continue!
	}
	return true;
}

If there are other paths that need to be handled by the interceptor, you can add more <mvc:mapping />nodes to the configuration, for example:

<!-- Configure Interceptor Chain -->
<mvc:interceptors>
	<!-- Configure the first interceptor -->
	<mvc:interceptor>
		<!-- Intercept Path -->
		<mvc:mapping path="/main/index.do" />
		<mvc:mapping path="/user/password.do" />
		<mvc:mapping path="/blog/delete.do" />
		<mvc:mapping path="/blog/publish.do" />
		<mvc:mapping path="/blog/edit.do" />
		<!-- Interceptor Class -->
		<bean class="cn.tedu.spring.LoginInterceptor"></bean>
	</mvc:interceptor>		
	<!-- Configure the second interceptor -->
</mvc:interceptors>

You can also use asterisk (*) wildcards when configuring paths, for example:

<mvc:mapping path="/blog/*" />

The above configurations can be matched to/blog/delete.do, /blog/edit.do, and so on...

It is important to note that an asterisk can only represent resources, not multilevel paths, such as / blog/* cannot match paths like / blog/2019/spring.do! If you need to wildcard multilevel paths and resources, you must use two asterisks (**), such as configure / blog/**!

In addition to this, you can add <mvc:exclude-mapping /> nodes to configure exception paths/exclusion paths, such as <mvc:mapping path="/user/**"/> which should be set as "exceptions" in order to ensure the normal use of registration and login functions:

<!--  Configure Interceptor Chain  -->
<mvc:interceptors>
     <!-- Configure the first interceptor -->
     <mvc:interceptor>
         <!-- 1. Intercept Path(Order must precede) -->
         <mvc:mapping path="/main/index.do"></mvc:mapping>
         <mvc:mapping path="/user/**"/>
         <!-- 2. Exceptions (whitelist) -->
         <mvc:exclude-mapping path="/user/login.do"></mvc:exclude-mapping>
         <mvc:exclude-mapping path="/user/handleLogin.do"></mvc:exclude-mapping>
         <mvc:exclude-mapping path="/user/reg.do"></mvc:exclude-mapping>
         <!-- Interceptor Class -->
         <bean class="cn.tedu.spring.LoginInterceptor"></bean>
     </mvc:interceptor>
     <!-- Configure the second interceptor -->
 </mvc:interceptors>

Any path added to the Exception is equivalent to the interceptor not handling requests for those paths, and all methods in the interceptor are not executed!

In a <mvc:interceptor>configuration, the node order of the children must be <mvc:mapping /> first and <bean> last!

5. Use filters to solve the scrambling of POST requests

In the Spring MVC framework, all codes used by default are ISO-8859-1, which does not support Chinese!

In the Spring MVC framework, a CharacterEncodingFilter filter class is defined to handle character encoding, so you should configure this filter class in web.xml and specify the encoding used:

<filter>
	<filter-name>CharacterEncodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>utf-8</param-value>
	</init-param>
</filter>

<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

6. Spring MVC Summary

  1. Understand the role of the SpringMVC framework;

  2. Understand the configuration of Dispatcher Servlet and CharacterEncodingFilter in web.xml;

  3. Understand the configuration related to component scanning and Thymeleaft in spring.xml, mainly which template parser to use, and the configuration of prefix and suffix;

  4. Master the correct way to create classes (you must add the @Controller annotation within the scope of the component scan);

  5. Master adding methods to handle requests;

  6. Master the use of @RequestMapping and @RequestParam annotations;

  7. Master how to receive request parameters;

  8. Master how to forward data;

  9. Master redirection;

  10. Master Session management;

  11. Master the use of interceptors;

  12. Understand the core SpringMVC execution flowchart.

Appendix 1: About Interceptor and Filter

Interceptors and filters are components that can be applied to a number of different requests, and they can prevent runs or releases or even form chains.

Filters are components in Java EE, and Interceptors are components in SpringMVC! That is, any Java Web project can use filters, but only projects that use the Spring MVC framework can use interceptors, and if the path to Dispatcher Servlet in the Spring MVC framework is configured as *.do, only requests that are processed by the Spring MVC framework (requests suffixed with.Do) can be processed by interceptors!

Filters are components that execute before all Servlets, while interceptors in SpringMVC execute for the first time after Dispatcher Servlet and before Controller! (For the SpringMVC core execution flowchart, the filter executes at position 1, while the interceptor executes at position 4 for the first time)

Filters need to be configured in web.xml, and mapped paths are relatively restricted (no whitelist), resulting in a large amount of configuration code (4 lines per path), while interceptors are simple and flexible to configure!

Therefore, if a request is processed by the SpringMVC framework and does not care about the processing time, the Interceptor should be preferred; otherwise, if it must be processed before all Servlet s, the Filter must be used.

If this article helps you, please give it a simple compliment, thank you~

Topics: Java Spring RESTful