14 tips for writing Spring MVC controller

Posted by edtlov on Tue, 04 Jan 2022 06:41:24 +0100

Typically, in Spring MVC, we write a controller class to handle requests from clients. Then, the controller calls the business class to handle business-related tasks, and then redirects the client to the logical view name, which is parsed by Spring's scheduler Servlet to render the results or output. This completes the round trip of a typical request response cycle.

Use @ Controller stereotype

This is the easiest way to create a Controller class that can handle one or more requests. Only annotate a class @ Controller with a stereotype, for example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
    @RequestMapping("/")
    public String visitHome() {
      
        return "home";
    }
}

As you can see, the visitHome() method handles requests from the application context path (/) by redirecting to the view named home.

Note: @ Controller prototype can only be used when annotation driven is enabled in Spring configuration file:

<annotation-driven />

When annotation driven is enabled, the Spring container automatically scans classes under the package specified by the following statement:

<context:component-scan base-package="net.codejava.spring" />

The class annotated by @ Controller is configured as a Controller. This is preferable because it is simple: there is no need to declare bean s for the Controller in the configuration file.

Note: by using the @ Controller annotation, you can have a multi action Controller class that can handle multiple different requests. For example:

@Controller
public class MultiActionController {
    @RequestMapping("/listUsers")
    public ModelAndView listUsers() {
    }
    @RequestMapping("/saveUser")
    public ModelAndView saveUser(User user) {
    }
    @RequestMapping("/deleteUser")
    public ModelAndView deleteUser(User user) {
    }
}

As you can see from the controller class above, there are three different processing methods for handling requests, / listUsers, / saveUser, and / deleteUser, respectively.

Implement controller interface

Another (perhaps Classic) way to create a Controller in Spring MVC is to have the class implement the Controller interface. For example:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class MainController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        System.out.println("Welcome main");
        return new ModelAndView("main");
    }
}

The implementation class must override the handleRequest() method, which will be called by the Spring scheduler Servlet when the matching request enters. The request URL pattern processed by this controller is defined in the context configuration file of Spring as follows:

<bean name="/main" class="net.codejava.spring.MainController" />

However, the disadvantage of this method is that the controller class cannot process multiple request URL s.

Extend AbstractController class

If you want to easily control supported HTTP methods, session and content caching. Extending your controller AbstractController class is ideal. Consider the following example:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class BigController extends AbstractController {
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        System.out.println("You're big!");
        return new ModelAndView("big");
    }
}

This creates a single action controller with configurations for supported methods, sessions, and caches, which can then be specified in the controller's bean declaration. For example:

<bean name="/big" class="net.codejava.spring.BigController">
    <property name="supportedMethods" value="POST"/>
</bean>

This configuration indicates that the POST handler method of this controller only supports this method.

Spring MVC also provides several controller classes designed for specific purposes, including:

AbstractUrlViewController

MultiActionController

ParameterizableViewController

ServletForwardingController

ServletWrappingController

UrlFilenameViewController

Specifies the URL mapping for the handler method

This is a mandatory task when encoding a controller class designed to handle one or more specific requests. Spring MVC provides the @ RequestMapping annotation, which is used to specify the URL mapping.

For example:

@RequestMapping("/login")

This maps the URL pattern of / login to be handled by the annotated method or class. When this annotation is used at the class level, the class becomes a single action controller. For example:

 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/hello")
public class SingleActionController {
    @RequestMapping(method = RequestMethod.GET)
    public String sayHello() {
        return "hello";
    }
}

When the @ RequestMapping annotation is used at the method level, you can have a multi action controller. For example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserController {
    @RequestMapping("/listUsers")
    public String listUsers() {
        return "ListUsers";
    }
    @RequestMapping("/saveUser")
    public String saveUser() {
        return "EditUser";
    }
    @RequestMapping("/deleteUser")
    public String deleteUser() {
        return "DeleteUser";
    }
}

@The RequestMapping annotation can also be used to specify multiple URL patterns to be processed by a method. For example:

@RequestMapping({"/hello", "/hi", "/greetings"})

In addition, this annotation has other properties that may be useful in some cases, such as method.

Specifies the HTTP request method for the handler method

You can use the method attribute of the annotation to specify which HTTP methods (GET, POST, PUT, etc.) the handler method supports @ RequestMapping. Here is an example:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class LoginController {
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String viewLogin() {
        return "LoginForm";
    }
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String doLogin() {
        return "Home";
    }
}

This controller has two methods / login that handle the same URL pattern, but the former is used for the GET method and the latter for the POST method.

For more information about using the @ RequestMapping annotation, see the @ RequestMapping annotation.

Mapping request parameters to handler methods

One of the cool features of Spring MVC is that you can use the @ RequestParam annotation to retrieve request parameters as general parameters of handler methods. This is a good way to separate the controller HttpServletRequest from the interface of the Servlet API.

@RequestMapping(value = "/login", method = RequestMethod.POST)
public String doLogin(@RequestParam String username,
                      @RequestParam String password) {
}

Spring binds the method parameter username and password to HTTP request parameters with the same name. This means that you can call the URL as follows (if the request method is GET):

http: // localhost: 8080 / spring / login?username = scott&password = tiger

Type conversion is also done automatically. For example, if you declare an integer parameter of the following types:

@RequestParam int securityNumber

Spring will then automatically convert the value of the request parameter (string) to the specified type (integer) in the handler method.

If the parameter name is different from the variable name, you can specify the actual name of the parameter as follows:

@RequestParam("SSN") int securityNumber

The @ RequestParam annotation also has two additional properties, which may be useful in some cases. This property specifies whether the parameter is required. For example: required

@RequestParam(required = false) String country

This means that the parameter country is optional; Therefore, it may be lost from the request. In the above example, if such a parameter does not exist in the country request, the variable will be null.

Another property is defaultValue, which can be used as a fallback value when the request parameter is empty. For example:

@RequestParam(defaultValue = "18") int age

Map if the method parameter is type, Spring also allows us to access map < string, string > as objects with all parameters. For example:

doLogin(@RequestParam Map<String, String> params)

Then, the mapping parameters contain all the request parameters in the form of key value pairs. For more information about using the @ RequestParam annotation, see the @ RequestParam annotation.

Return to model and view

After processing the business logic, the handler method should return a view, which is then parsed by the spring scheduler servlet. Spring allows ModelAndView to return a String or object from the handler method. In the following example, the handler method returns a String and represents a view named LoginForm:

 
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String viewLogin() {
    return "LoginForm";
}

This is the easiest way to return the view name. However, if you want to send other data to the view, you must return a ModelAndView object. Consider the following handler methods:

@RequestMapping("/listUsers")
public ModelAndView listUsers() {
    List<User> listUser = new ArrayList<>();
    // Get user list from DAO
    ModelAndView modelView = new ModelAndView("UserList");
    modelView.addObject("listUser", listUser);
    return modelView;
}

As you can see, this handler method returns a ModelAndView User object that holds the view name UserList and a collection of objects that can be used in the view.

Spring is also very flexible because you can declare a ModelAndView object as a parameter to a handler method without creating a new object. Therefore, the above example can be rewritten as follows:

@RequestMapping("/listUsers")
public ModelAndView listUsers(ModelAndView modelView) {
    List<User> listUser = new ArrayList<>();
    //Get user list from DAO
    modelView.setViewName("UserList");
    modelView.addObject("listUser", listUser);
    return modelView;
}

For more information about this class, see: ModelAndView class.

Put objects into the model

In an application that follows the MVC architecture, the controller (C) should pass the data to the model (M) and then use the model in the view (V). As we saw in the previous example, the addObject() method ModelAndView of this class puts objects into the model in the form of name value pairs:

modelView.addObject("listUser", listUser);
modelView.addObject("siteName", new String("CodeJava.net"));
modelView.addObject("users", 1200000);

Again, spring is very flexible. You can declare type parameters in Map handler methods. Spring uses this mapping to store the objects of the model. Let's look at another example:

@RequestMapping(method = RequestMethod.GET)
public String viewStats(Map<String, Object> model) {
    model.put("siteName", "CodeJava.net");
    model.put("pageviews", 320000);
    return "Stats";
}

This is simpler than using ModelAndView objects. Depending on your preference, you can use Map or ModelAndView objects. I want to thank Spring for its flexibility.

Redirection in handler methods

If you want to redirect the user to another URL if the conditions are met, please redirect: / append before the URL. The following code snippet gives an example:

// Check login status
if (!isLogin) {
    return new ModelAndView("redirect:/login");
}
// Return to user list

In the above code, / login if the user is not logged in, the user will be redirected to the URL.

Process form submission and form validation

Spring makes it easy to handle form submission by providing annotations for @ ModelAttribute to bind form fields to form support objects and an interface for BindingResult to validate form fields. java training The following code snippet shows a typical handler method that processes and validates form data:

@Controller
public class RegistrationController {
    @RequestMapping(value = "/doRegister", method = RequestMethod.POST)
    public String doRegister(
        @ModelAttribute("userForm") User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // Form validation error
        } else {
            // Form input is OK
        }
        // Registration process
        return "Success";
    }
}

Learn more about the @ ModelAttribute annotation and the BindingResult interface from the official Spring documentation:

Use @ ModelAttribute on method parameters

Use @ ModelAttribute on method

Interface binding result

Process file upload

Spring also makes it easy to handle file uploads in handler methods by automatically binding upload data to an array of CommonsMultipartFile objects. Spring uses Apache Commons FileUpload as a multipart parser.

The following code snippet shows how easy it is to upload files from the client

@RequestMapping(value = "/uploadFiles", method = RequestMethod.POST)
public String handleFileUpload(
        @RequestParam CommonsMultipartFile[] fileUpload) throws Exception {
    for (CommonsMultipartFile aFile : fileUpload){
        // Store uploaded files
        aFile.transferTo(new File(aFile.getOriginalFilename()));
    }
    return "Success";
}

Automatically assemble business classes in the controller

The controller shall delegate the processing of business logic to relevant business classes. To do this, you can use the @ Autowired annotation to let Spring automatically inject the actual implementation of the business class into the controller. Consider the code snippet for the following controller classes:

@Controller
public class UserController {
    @Autowired
    private UserDAO userDAO;
    public String listUser() {
        // Lists the processing methods for all users
        userDAO.list();
    }
    public String saveUser(User user) {
        // How to save / update users
        userDAO.save(user);
    }
    public String deleteUser(User user) {
        // How to delete a user
        userDAO.delete(user);
    }
    public String getUser(int userId) {
        // Get the processing method of the user
        userDAO.get(userId);
    }
}

Here, all business logic related to user management is provided by the implementation of the UserDAO interface. For example:

interface UserDAO {
    List<User> list();
    void save(User user);
    void checkLogin(User user);
}

For more information about @ Autowired annotations, see annotation types auto assemble.

Access HttpServletRequest and HttpServletResponse

In some cases, you need to access the HttpServletRequest or HttpServletResponse object directly in the handler method. Through the flexibility of Spring, you only need to add relevant parameters to the processing method. For example:

@RequestMapping("/download")
public String doDownloadFile(
        HttpServletRequest request, HttpServletResponse response) {
    // Access request
    // Access response
    return "DownloadPage";
}

Spring detects and automatically injects HttpServletRequest and HttpServletResponse objects into methods. You can then access requests and responses, such as getting InputStream, OutputStream, or returning a specific HTTP code.

Follow the principle of single responsibility

Finally, there are two good practices you should follow when designing and writing Spring MVC controllers:

The controller class should not execute business logic. Instead, it should delegate business processing to the relevant business category. This keeps the controller focused on its design responsibility, which is to control the workflow of the application. For example:

@Controller
public class UserController {
    @Autowired
    private UserDAO userDAO;
    public String listUser() {
        userDAO.list();
    }
    public String saveUser(User user) {
        userDAO.save(user);
    }
    public String deleteUser(User user) {
        userDAO.delete(user);
    }
    public String getUser(int userId) {
        userDAO.get(userId);
    }
}

Create each individual controller for each business domain. For example, UserController is used to control the workflow of OrderController managed by users, and to control the workflow of order processing. For example:

 
@Controller
public class UserController {
}
@Controller
public class ProductController {
}
@Controller
public class OrderController {
}
@Controller
public class PaymentController {
}

Topics: Spring MVC