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.
- 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.
- 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 example | explain |
---|---|
/resources/ima?e.png | One character in the path is variable, such as / resources / image png |
/resources/*.png | Multiple 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}/versions | Match a path and extract the value in the path, such as / projects/MyApp/versions |
/projects/{project:[a-z]+}/versions | Match 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:
- null pattern s have the lowest priority.
- Patterns containing wildcards have the lowest priority (such as / * *).
- If both pattern s contain wildcards, the longer one has higher priority.
- Patterns with fewer matching symbols and fewer path variables have higher priority.
- 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 parameters | explain |
---|---|
WebRequest, NativeWebRequest | It 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.ServletResponse | Request and parameter information of Servlet |
javax.servlet.http.HttpSession | Requested Session information |
javax.servlet.http.PushBuilder | Server 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.Principal | Login information of the current user |
HttpMethod | Request mode, such as GET, POST, etc |
java.util.Locale | Internationalization information in request |
java.util.TimeZone + java.time.ZoneId | Requested time zone information |
java.io.InputStream, java.io.Reader | Used to get the input stream of the original Body of the request |
java.io.OutputStream, java.io.Writer | Output stream for writeback response |
@PathVariable | Path variables, such as petId in "/ pets/{petId}" |
@MatrixVariable | Parameters separated by semicolons, such as GET /pets/42;q=11;r=22 |
@RequestParam | Get the parameters in the request, including files of multipart type |
@RequestHeader | Request header information |
@CookieValue | Cookie information in request |
@RequestBody | The requested Body will be converted to the specified type of data using HttpMessageConverter. |
HttpEntity\<B> | Similar to @ RequestBody |
@RequestPart | Used to obtain data in multipart / form data |
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap | Gets the parameters used to render the HTML view |
@ModelAttribute | Used to get properties in the model |
Errors, BindingResult | Get parameter verification result information |
SessionStatus + class-level @SessionAttributes | Session information |
UriComponentsBuilder | Get parameter information in the matching process |
@SessionAttribute | Get a Session attribute |
@RequestAttribute | Get properties in request |
Processing methods can also support many types of return values. Different types of returns have different meanings.
Return parameters | explain |
---|---|
@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 |
HttpHeaders | Return only Header, not body |
String | Find the View according to the return value and parse it into a model |
View | Return to a view |
java.util.Map, org.springframework.ui.Model | The model used to render the View, which is determined by RequestToViewNameTranslator |
@ModelAttribute | The model used to render the View, which is determined by RequestToViewNameTranslator |
ModelAndView | Returns an available model view |
void | It 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, SseEmitter | Asynchronously write the converted Body of HttpMessageConverter into the Response |
StreamingResponseBody | Write the return asynchronously to the Response |
Reactive types — Reactor, RxJava, or others through ReactiveAdapterRegistry | Asynchronous 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:
- pet in @ ModelAttribute attribute added during request preprocessing;
- Find pet from @ SessionAttributes attribute in HttpSession;
- Find the pet attribute from the request parameter or pathVariable;
- 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:
- Add @ ModelAttribute to the input parameter of the processing method to obtain the attribute value in the Model already in WebDataBinder.
- Adding the @ ModelAttribute annotation on a class (such as Controller) will initialize the model for all requests.
- 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:
- Bind the parameters in the request with the processing method parameters;
- Convert the Spring type data in the request into the parameter type of the processing method;
- 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!