Spring MVC parsing Controller annotation

In the previous article, I introduced the DispatcherServlet, the core component of Spring MVC. DispatcherServlet connects the requests in the Servlet container (such as Tomcat) with the components in Spring and is the hub of Spring web applications. However, in our daily development, we often do not need to know the role of the hub in detail. We only need to deal with the requests distributed to us by the hub. The most common component in Spring that handles request business logic is the Controller. This article will introduce Spring Controller and related components in detail.

Definition of Controller

Controller is a special component in spring. This component will be recognized by spring as a component that can accept and process web page requests. Spring provides annotation based controller definition methods: @ controller and @ RestController annotations. The annotation based controller definition does not need to inherit or implement the interface. Users can freely define the interface signature. The following is an example of the Spring Controller definition.

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String handle(Model model) {
        model.addAttribute("message", "Hello World!");
        return "index";
    }
}

@The Controller annotation inherits the @ Component annotation of Spring, declares the corresponding class as the Bean corresponding to Spring, and can be managed by Web components@ The RestController annotation is a combination of @ Controller and @ ResponseBody, @ ResponseBody indicates that the return of the function does not need to be rendered as a View, but should be written back to the client directly as the content of the Response.

Mapping relationship RequestMapping

Definition of path

After defining a Controller, we need to map requests from different paths to different Controller methods. Spring also provides an annotation based mapping method: @ RequestMapping. Normally, users can add @ RequestMapping annotation on the Controller class and method. The spring container will recognize the annotation and assign the requests that meet the path conditions to the corresponding methods for processing. In the following example, "GET /persons/xxx" will call the getPerson method for processing.

@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}

Path matching

Spring supports two path matching methods, which are well compatible. Spring uses PathPattern for path matching by default.

  1. PathPattern : match the path using the pre resolved method. Specially designed for Web path matching, it can support complex expressions and has high execution efficiency.
  2. AntPathMatcher : the solution for classpath, file system and other resources in Spring is inefficient.

PathPattern is basically downward compatible with the logic of AntPathMatcher, and supports path variable and "* *" multi segment path matching. The following are some examples of PathPattern:

Path exampleexplain
/resources/ima?e.pngOne character in the path is variable, such as / resources / image png
/resources/*.pngMultiple characters in the path are variable, such as / resources / test png
/resources/**Multiple segments in the path are variable, such as / resources/test/path/xxx
/projects/{project}/versionsMatch a path and extract the value in the path, such as / projects/MyApp/versions
/projects/{project:[a-z]+}/versionsMatch a path that conforms to the regular expression, and extract the value in the path, such as / projects/myapp/versions

The variables matched in the Path can be obtained using @ PathVariable. The Path variable can be at the method or class level, and the matched variables will be automatically changed Type conversion , if the conversion fails, an exception will be thrown.

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}

@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}

Path conflict

When a request matches multiple patterns, you need to select the closest Pattern path. Spring provides the strategy of selecting the closest path for Pattern and AntPathMatcher. The logic between them is similar. Only PathPattern is introduced here. For the PathPattern, spring provides the PathPattern SPECIFICITY_ Comparator is used to compare the priorities between paths. The comparison rules are as follows:

  1. null pattern s have the lowest priority.
  2. Patterns containing wildcards have the lowest priority (such as / * *).
  3. If both pattern s contain wildcards, the longer one has higher priority.
  4. Patterns with fewer matching symbols and fewer path variables have higher priority.
  5. The longer the path, the higher the priority.

No longer supported after Spring 5.3* If the suffix matches, by default "/ person" will match all "/ person. *"

Types of parameters accepted and returned

RequestMapping can also specify what type of parameters the interface accepts and what type of parameters it returns. This is usually specified in the content type of the request header:

@PostMapping(path = "/pets", consumes = "application/json") 
public void addPet(@RequestBody Pet pet) {
    // ...
}

@GetMapping(path = "/pets/{petId}", produces = "application/json") 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

Select according to parameters or Header

RequestMapping also supports judging whether to process the request according to the request parameters or Header.

  • If only the value of parameter myParam is accepted as myValue, it can be specified as follows:

    @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
    public void findPet(@PathVariable String petId) {
        // ...
    }
  • If only the value of myParam in the request header is myValue, it can be specified as follows:

    @GetMapping(path = "/pets", headers = "myHeader=myValue") 
    public void findPet(@PathVariable String petId) {
        // ...
    }

Program registration RequestMapping

In the previous tutorial, we talked about how to map paths through @ RequestMapping. Using this method will automatically map paths to annotated methods. Although this method is very convenient to use, it lacks some flexibility. If I want to dynamically map the relationship between paths according to the Bean configuration information, the annotation method cannot meet this requirement. Spring provides a method to dynamically register RequestMapping. The registration example is as follows:

@Configuration
public class MyConfig {

    // Get the RequestMappingHandlerMapping and user-defined component UserHandler that maintain the mapping relationship from the container
    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) 
            throws NoSuchMethodException {

        // Generate path matching information
        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); 

        // Methods needed to get the mapping
        Method method = UserHandler.class.getMethod("getUser", Long.class); 

        // Register the mapping information between paths and methods
        mapping.registerMapping(info, handler, method); 
    }
}

processing method

Through request mapping, you can usually map requests to a method in turn. This method is processing method (Handler Methods). The parameters and return values of the processing method can use the information in many requests (such as @ RequestParam, @RequestHeader), etc. these parameters support Optional encapsulation.

Method parametersexplain
WebRequest, NativeWebRequestIt contains request parameters, request and Session information. It is mainly used for internal parameter parsing and other operations in the Spring framework
javax.servlet.ServletRequest, javax.servlet.ServletResponseRequest and parameter information of Servlet
javax.servlet.http.HttpSessionRequested Session information
javax.servlet.http.PushBuilderServer push is one of the new features in HTTP/2 protocol. It aims to predict the resource demand of the client by pushing the resources of the server to the cache of the browser, so that when the client sends a web page request and receives a response from the server, the resources it needs are already in the cache. This is a performance enhancement function to improve the loading speed of web pages. In Servlet 4.0, the server push function is exposed through the PushBuilder instance, which is obtained from the HttpServletRequest instance.
java.security.PrincipalLogin information of the current user
HttpMethodRequest mode, such as GET, POST, etc
java.util.LocaleInternationalization information in request
java.util.TimeZone + java.time.ZoneIdRequested time zone information
java.io.InputStream, java.io.ReaderUsed to get the input stream of the original Body of the request
java.io.OutputStream, java.io.WriterOutput stream for writeback response
@PathVariablePath variables, such as petId in "/ pets/{petId}"
@MatrixVariableParameters separated by semicolons, such as GET /pets/42;q=11;r=22
@RequestParamGet the parameters in the request, including files of multipart type
@RequestHeaderRequest header information
@CookieValueCookie information in request
@RequestBodyThe requested Body will be converted to the specified type of data using HttpMessageConverter.
HttpEntity\<B>Similar to @ RequestBody
@RequestPartUsed to obtain data in multipart / form data
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMapGets the parameters used to render the HTML view
@ModelAttributeUsed to get properties in the model
Errors, BindingResultGet parameter verification result information
SessionStatus + class-level @SessionAttributesSession information
UriComponentsBuilderGet parameter information in the matching process
@SessionAttributeGet a Session attribute
@RequestAttributeGet properties in request

Processing methods can also support many types of return values. Different types of returns have different meanings.

Return parametersexplain
@ResponseBody@RestController contains this annotation, which indicates that the return value is written to the Response using HttpMessageConverter, and view resolution will not be performed
HttpEntity\<B>, ResponseEntity\<B>Similar to @ ResponseBody, the return value is written directly to Response
HttpHeadersReturn only Header, not body
StringFind the View according to the return value and parse it into a model
ViewReturn to a view
java.util.Map, org.springframework.ui.ModelThe model used to render the View, which is determined by RequestToViewNameTranslator
@ModelAttributeThe model used to render the View, which is determined by RequestToViewNameTranslator
ModelAndViewReturns an available model view
voidIt usually means that no Body is returned
DeferredResult\<V>After the asynchronous results are introduced in detail
Callable\<V>Asynchronous return result, which will be described in detail later
ListenableFuture\<V>, java.util.concurrent.CompletionStage\<V>, java.util.concurrent.CompletableFuture\<V>Similar to DeferredResult, it returns the call result asynchronously
ResponseBodyEmitter, SseEmitterAsynchronously write the converted Body of HttpMessageConverter into the Response
StreamingResponseBodyWrite the return asynchronously to the Response
Reactive types — Reactor, RxJava, or others through ReactiveAdapterRegistryAsynchronous return in Flux scenario

Type conversion

The parameters of network requests are often of String type, but when mapping to the back end, they need to be converted to the data types required by the processing method (such as @ RequestParam, @RequestHeader, @ PathVariable, @ MatrixVariable and @ CookieValue). In this case, Spring will obtain the type conversion service and Attribute Editor in the container for conversion, and users can also WebDataBinder Inject the transformation services you need into.

Matrix parameters

Matrix parameters are actually some specifications on Url coding in RFC3986. Matrix parameters are separated by semicolons, and multiple values of matrix parameters are separated by commas, such as / cars;color=red,green;year=2012, multiple values can also be separated by semicolons, such as color=red;color=green;color=blue

If a URL needs to contain a Matrix parameter, the Matrix parameter should be a path variable, otherwise the Matrix parameter will affect the path matching:

// GET /pets/42;q=11;r=22

// The last path must be the path variable {petId}, otherwise the path matching will fail
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}

Not only the last section of the URL can be added with Matrix parameters, but any section of the URL can be added with Matrix parameters, as shown below:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}

The Matrix parameter allows you to set a default value. This default value is used when the user does not transfer this parameter:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}

If the path contains many Matrix parameters, it may be troublesome to receive them one by one. We can receive them in the form of sets through MultiValueMap:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

If you need to use the Matrix parameter in the program, you need to configure the UrlPathHelper with removeSemicolonContent=false.

@RequestParam

@RequestParam is used to bind the parameters in the request (query parameters or form parameters) to the corresponding method parameters. By default, the request parameters are not allowed to contain the specified parameters, but the user can specify required=false to allow the request parameters to be set to the corresponding method parameters. If the parameter type of the method is not String type, Spring will automatically perform type conversion. When the parameter type of @ RequestParam annotation is Map \ < String, String > and @ RequestParam does not specify the parameter name, Spring will inject all parameters into the Map.

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { 
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

@RequestHeader

An Http request often includes a request header and a Body. We can bind the request header and the parameters of the processing method through @ RequestHeader. The @ RequestHeader also supports Map. Suppose a request has the following headers:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

If we need to get the accept encoding and keep alive tags in the method, we can get them through the following code:

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, 
        @RequestHeader("Keep-Alive") long keepAlive) { 
    //...
}

@CookieValue

If we need to get the cookie information in a request, we can get it through @ cookie value. The method is as follows:

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { 
    //...
}

@ModelAttribute

@ModelAttribute can map the parameters in the request to objects and then pass them to the corresponding methods.

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
    // method logic...
}

In the above example, the request parameters can come from pet and can come from the following ways:

  1. pet in @ ModelAttribute attribute added during request preprocessing;
  2. Find pet from @ SessionAttributes attribute in HttpSession;
  3. Find the pet attribute from the request parameter or pathVariable;
  4. Initializes the data using the default constructor.
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) { 
    // ...
}

@SessionAttributes and @ SessionAttributes

@SessionAttributes are used to share Session data among multiple requests. This annotation can only be loaded on the class. In the first request, the Session data will be put into the SessionAttributes, and the data will be cleared at the end of the Session.

@Controller
@SessionAttributes("pet") // Put data into Session
public class EditPetForm {

    // Query data from Session
    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
            // Clear the data in the Session
            status.setComplete(); 
            // ...
        }
    }
}

If the Session attribute is not managed by the Controller but by other components (such as Filter), we can use @ SessionAttribute to bind the data in the Session with the parameters in the processing method.

@RequestMapping("/")
public String handle(@SessionAttribute User user) { 
    // ...
}

@RequestAttribute

@RequestAttribute is similar to @ SessionAttributes. One is request level and the other is Session level, which will not be described in detail here.

@GetMapping("/")
public String handle(@RequestAttribute Client client) { 
    // ...
}

Multipart parameter

As we said in the previous article, the DispatcherServlet will contain the MultipartResolver component. If the data requested at one time is multipart / form data type, the DispatcherServlet will parse the uploaded file into MultipartFile format file. Javax is also supported in servlet 3 servlet. http. Part receives files instead of MultipartFile. When uploading multiple files, you can use list or Map to obtain parameters.

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}

Multipart can also encapsulate the files to be received as objects for receiving.

class MyForm {

    private String name;

    private MultipartFile file;

    // ...
}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        if (!form.getFile().isEmpty()) {
            byte[] bytes = form.getFile().getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}

In addition to uploading files through the browser, we can also upload files in Json format through RestFul:

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {
    // ...
}

@RequestBody and HttpEntity

@RequestBody should be one of the most used parameters in daily development. We can bind the Body in the request to the parameter object in the processing method through @ RequestBody. Spring will call HttpMessageConverter service to deserialize the data in the request into the parameter object in the processing method@ The RequestBody can also be used in combination with the @ Validated annotation. If the verification fails, an exception will be thrown or the user will be handed over to handle the verification exception information.

@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
    // ...
}

The principle of HttpEntity is similar to that of @ RequestBody, but the request body will be encapsulated in HttpEntity.

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}

@ResponseBody and ResponseEntity

@ResponseBody means that the returned value will be directly serialized into a String through HttpMessageConverter and written into the Response. The @ RestController we usually use is composed of @ ResponseBody and @ Controller.

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

ResponseEntity and @ ResponseBody, but the status code and return header information will be included on the basis of the return.

@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).build(body);
}

JSON Views

Spring has built-in support for Jackson JSON and supports Jackson's Json serialization view , when using @ ResponseBody and ResponseEntity to return data, you can specify the fields to be displayed during Json serialization according to @ JsonView.

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

We can also realize the serialization of different views of objects by programming, as follows:

@RestController
public class UserController {

    @GetMapping("/user")
    public MappingJacksonValue getUser() {
        User user = new User("eric", "7!jd#h23");
        MappingJacksonValue value = new MappingJacksonValue(user);
        value.setSerializationView(User.WithoutPasswordView.class);
        return value;
    }
}

For View based solutions, we can add corresponding objects and Json serialized views in the Model. The examples are as follows:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

Model object

The model object in spring is responsible for passing data between the controller and the view that presents the data. Spring provides @ ModelAttribute to get and write the attributes of model objects. There are many ways to use @ ModelAttribute:

  1. Add @ ModelAttribute to the input parameter of the processing method to obtain the attribute value in the Model already in WebDataBinder.
  2. Adding the @ ModelAttribute annotation on a class (such as Controller) will initialize the model for all requests.
  3. Add @ ModelAttribute to the return value of the processing method, indicating that the return value will be used as the attribute of the model.
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // add more ...
}

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}

DataBinder

We talked a lot about how to bind the request parameter and processing method into the annotation or type of the parameter, and we know that the request parameter needs to be type converted to the corresponding type of data. However, annotation is just a tag and does not actually perform parameter binding and type conversion. There must be a component in Spring for parameter binding and type conversion. This component is WebDataBinder. WebDataBinder has the following functions:

  1. Bind the parameters in the request with the processing method parameters;
  2. Convert the Spring type data in the request into the parameter type of the processing method;
  3. Format the data of the rendered form.

Spring provides users with an interface to modify WebDataBinder. Users can define the method annotated by @ InitBinder in the Controller and modify the definition of WebDataBinder in the method:

@Controller
public class FormController {

    @InitBinder 
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

exception handling

In the relevant chapters on DispatcherServlet, we know that DispatcherServlet contains exception resolution components, which will resolve exceptions when they occur. The exception handling component commonly used in daily development is ExceptionHandlerExceptionResolver, which is used to handle the corresponding exception using the method with @ ExceptionHandler annotation when an exception is encountered. This method can be defined in Controller or ControllerAdvice.

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
    
    @ExceptionHandler({FileSystemException.class, RemoteException.class})
    public ResponseEntity<String> handle(Exception ex) {
        // ...
    }
}

If we need to define many @ exceptionhandlers, we can choose to define them in @ ControllerAdvice instead of in each Controller.

If an exception matches multiple @ exceptionhandlers, Spring will try to use the @ ExceptionHandler closest to the exception inheritance system to handle the exception.

Controller Advice

If we need to define global @ InitBinder or @ ExceptionHandler, we should not define these methods in the Controller. Spring provides @ ControllerAdvice to add global configuration:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

I am the fox fox. Welcome to my WeChat official account: wzm2zsd

This article is first released to WeChat official account, all rights reserved, no reprint!

Keywords: Java

Added by pellky on Wed, 16 Feb 2022 11:27:33 +0200