In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the knowledge of "how to use SpringBoot to implement the back-end interface". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Required dependency package
The SpringBoot configuration project is used here, and the focus of this article is on the backend interface, so you only need to import a spring-boot-starter-web package:
Org.springframework.boot spring-boot-starter-web
This article also uses swagger to generate API documents and lombok to simplify classes, but both are not necessary and can be used or not.
Parameter check
An interface generally verifies the parameters (request data) safely, so there is no need to say much about the importance of parameter verification, so we should pay attention to how to check the parameters.
Business layer check
First, let's take a look at the most common practice, which is parameter verification at the business layer:
Public String addUser (User user) {if (user = = null | | user.getId () = = null | | user.getAccount () = = null | | user.getPassword () = = null | | user.getEmail () = = null) {return "object or object field cannot be empty" } if (StringUtils.isEmpty (user.getAccount ()) | | StringUtils.isEmpty (user.getPassword ()) | | StringUtils.isEmpty (user.getEmail ()) {return "cannot enter an empty string";} if (user.getAccount (). Length ())
< 6 || user.getAccount().length() >11) {return "account must be 6-11 characters long";} if (user.getPassword () .length ()
< 6 || user.getPassword().length() >16) {return "password must be 6-16 characters long";} if (! Pattern.matches ("^ [a-zA-Z0-9 characters -] + @ [a-zA-Z0-9 characters -] + (\. [a-zA-Z0-9 characters -] +) + $", user.getEmail ()) {return "incorrect mailbox format" } / / after the parameter verification, write the business logic return "success";}
Of course, there is nothing wrong with this, and the format is neatly typeset at a glance, but this is too tedious, this has not yet carried out the business operation, there are so many lines of code just for a parameter check, it is not elegant enough.
Let's improve it by using two sets of Validator, Spring Validator and Hibernate Validator, for convenient parameter verification! These two sets of Validator dependency packages are already included in the aforementioned web dependency packages, so they can be used directly.
Validator + BindResult for verification
Validator can easily make verification rules and automatically help you complete the verification. First of all, add comments to the fields that need to be verified in the input parameter. Each comment corresponds to different verification rules, and the information after verification failure can be made:
@ Data public class User {@ NotNull (message = "user id cannot be empty") private Long id; @ NotNull (message = "user account cannot be empty") @ Size (min = 6, max = 11, message = "account length must be 6-11 characters") private String account @ NotNull (message = "user password cannot be empty") @ Size (min = 6, max = 11, message = "password must be 6-16 characters long") private String password; @ NotNull (message = "user mailbox cannot be empty") @ Email (message = "incorrect mailbox format") private String email;}
After the verification rules and error messages are configured, you only need to add @ Valid annotation to the parameters that the API needs to verify and add the BindResult parameter to facilitate verification:
@ RestController @ RequestMapping ("user") public class UserController {@ Autowired private UserService userService; @ PostMapping ("/ addUser") public String addUser (@ RequestBody @ Valid User user, BindingResult bindingResult) {/ / if any parameter verification fails, the error message will be encapsulated into an object and assembled in BindingResult for (ObjectError error: bindingResult.getAllErrors ()) {return error.getDefaultMessage () } return userService.addUser (user);}}
In this way, when the request data is passed to the interface, Validator automatically completes the verification, and the verification result is encapsulated in BindingResult. If there is an error message, we return it directly to the front end, and the business logic code is not executed at all.
At this point, the verification code in the business layer is no longer needed:
Public String addUser (User user) {/ / write business logic return "success" directly;}
Now you can take a look at the effect of parameter verification. We deliberately pass a parameter that does not meet the verification rules to this API, first pass an error data to the interface, and deliberately make the password field not meet the verification condition:
{"account": "12345678", "email": "123@qq.com", "id": 0, "password": "123"}
Let's take a look at the response data of the interface:
Isn't that much more convenient? It is not difficult to see that using Validator check has the following benefits:
Simplify the code, the previous business layer so a large part of the verification code has been omitted.
Easy to use, so many verification rules can be easily implemented, such as mailbox format verification, before their own handwritten regular expressions to write such a long list, but also easy to make mistakes, using Validator directly a note to do. (there are more comments on verification rules, which you can learn by yourself.)
Reducing coupling and using Validator allows the business layer to focus only on the business logic, separating it from the basic parameter verification logic.
Using Validator+ BindingResult is already a very convenient and practical way of parameter verification, and many projects do this in actual development, but this is still not very convenient, because you have to add a BindingResult parameter for each interface you write, and then extract the error message and return it to the front end.
This is a bit of a hassle, and there is a lot of repetitive code (although you can encapsulate this repeated code into a method). Can we get rid of the BindingResult step? Of course you can!
Validator + automatically throws an exception
We can completely remove the BindingResult step:
@ PostMapping ("/ addUser") public String addUser (@ RequestBody @ Valid User user) {return userService.addUser (user);}
What happens after you get rid of it? Try it directly, or deliberately pass a parameter that does not conform to the verification rules to the interface as before. At this point, we can see that the interface has thrown a MethodArgumentNotValidException exception by observing the console:
In fact, this has achieved the desired effect. If the parameter verification is not passed, the next business logic will not be executed. After removing the BindingResult, an exception will be thrown automatically. If an exception occurs, the business logic will not be executed naturally. In other words, there is no need to add BindingResult-related operations at all.
However, the matter is not over, the exception is thrown, but we have not written the code to return the error message, so what data will the front end respond to if the parameter verification fails?
Let's take a look at the data of the interface response after the exception occurred just now:
Yes, it directly responds to the information related to the entire error object to the front end! This is very uncomfortable, but to solve this problem is also very simple, that is, we are going to talk about global exception handling!
Global exception handling
The failure of parameter verification will automatically throw an exception. Of course, it is impossible for us to manually catch the exception for handling, otherwise we might as well use the previous BindingResult method. You don't want to catch this exception manually, and you have to handle it, so you can use SpringBoot global exception handling to achieve the effect once and for all!
Basic use
First, we need to create a new class, annotate it with @ ControllerAdvice or @ RestControllerAdvice, and the class is configured as a global processing class. (this depends on whether your Controller layer uses @ Controller or @ RestController.)
Then create a new method in the class, annotate the method with @ ExceptionHandler and specify the type of exception you want to handle, and then write the operation logic for the exception in the method to complete the global handling of the exception!
Let's now demonstrate the MethodArgumentNotValidException global processing thrown for a parameter verification failure:
@ RestControllerAdvice public class ExceptionControllerAdvice {@ ExceptionHandler (MethodArgumentNotValidException.class) public String MethodArgumentNotValidExceptionHandler (MethodArgumentNotValidException e) {/ / get the ObjectError object ObjectError objectError = e.getBindingResult () .getAllErrors () .get (0) from the exception object; / / then extract the error message and return return objectError.getDefaultMessage ();}}
Let's take a look at the response data after this verification failure:
Yes, what is returned this time is the error message we made! We gracefully achieve the functionality we want through global exception handling! In the future, if we want to write API parameter verification, we only need to annotate the Validator verification rule on the member variables of the input parameter, and then add @ Valid to the parameter to complete the verification. If the verification fails, an error message will be returned automatically without any other code! More verification ideas: SpringBoot to achieve general interface parameter verification
Custom exception
Of course, global processing does not handle only one exception, and the purpose is not just to optimize a parameter verification method. In actual development, how to handle exceptions is actually a very troublesome thing. Traditional handling of exceptions generally has the following annoyances:
Is to catch an exception (try... Catch) or throw exception (throws)
Is it processed at the controller layer or at the service layer or at the dao layer
The way to handle exceptions is to do nothing, or to return specific data, and what data will be returned if returned
Not all exceptions can be caught in advance. What if there is an exception that is not caught?
All these problems can be solved by global exception handling, which is also called unified exception handling. What do global and unified handling represent? On behalf of norms! With norms, many problems will be easily solved!
You already know the basic use of global exception handling, so let's go a step further to standardize exception handling in the project: custom exceptions.
In many cases, we need to manually throw an exception, for example, in the business layer, when some conditions do not conform to the business logic, I can manually throw an exception to trigger a transaction rollback. Then the easiest way to throw an exception manually is throw new RuntimeException ("exception message"), but it is better to use customization:
Custom exceptions can carry more information than one string like this.
In project development, many people are often responsible for different modules, and the use of custom exceptions can unify the way of external exception display.
The semantics of custom exceptions are clearer, and you can tell at a glance that they are manually thrown in the project.
Let's start writing a custom exception now:
@ Getter / / No need for setter public class APIException extends RuntimeException {private int code; private String msg; public APIException () {this (1001, "interface error");} public APIException (String msg) {this (1001, msg);} public APIException (int code, String msg) {super (msg); this.code = code as long as the getter method This.msg = msg;}}
Remember to add the handling of our custom exception to the global exception handling class just now:
ExceptionHandler (APIException.class) public String APIExceptionHandler (APIException e) {return e.getMsg ();}
In this way, the handling of exceptions is more standardized, and of course, you can also add the handling of Exception, so that no matter what exception occurs, we can shield it and send the response data to the front end. However, it is recommended to do this when the project is launched at last, which can shield the error information and expose it to the front end. Do not do this in development for debugging convenience.
Now that the global exception handling and custom exception handling are ready, I wonder if you have found a problem, that is, when we throw a custom exception, the global exception handling only responds to the error message msg in the exception to the frontend, and does not return the error code code. This leads to what we are going to talk about next: the unified response of data.
Unified response of data
Now we have standardized the parameter verification and exception handling, but we have not standardized the response data! For example, if I want to get a paged information data, I will naturally return a list of data if I get it successfully. If I fail to get it, the background will respond to the abnormal information, that is, a string. That is to say, the front-end developer has no idea what the data from the back-end response will look like! Therefore, unified response data is a must in the front and back end specification!
Custom unified response body
The first step in unified data response is to customize a response class by ourselves. No matter whether the background is running normally or an exception occurs, the data format of the response to the front end is unchanged! So how do you define the response body? About exception design: how to design exceptions more elegantly
You can refer to our custom exception class, and also give a response information code code and response information description msg:
@ Getter public class ResultVO {/ * status code, for example, 1000 represents a successful response * / private int code; / * * response message, which is used to describe the specific data of the response * / private String msg; / * response * / private T data Public ResultVO (T data) {this (1000, "success", data);} public ResultVO (int code, String msg, T data) {this.code = code; this.msg = msg; this.data = data;}}
Then we modify the return value of the global exception handling:
@ ExceptionHandler (APIException.class) public ResultVO APIExceptionHandler (APIException e) {/ / Note, the return type here is custom response body return new ResultVO (e.getCode (), "response failure", e.getMsg ());} @ ExceptionHandler (MethodArgumentNotValidException.class) public ResultVO MethodArgumentNotValidExceptionHandler (MethodArgumentNotValidException e) {ObjectError objectError = e.getBindingResult () .getAllErrors () .get (0) / / Note: the return type here is custom response body return new ResultVO (1001, "parameter verification failed", objectError.getDefaultMessage ());}
Let's take a look at what data will be sent to the front end if an exception occurs:
OK, the response of this exception message is very good, the status code, response description and error prompt data are returned to the front end, and all exceptions will return the same format! The exception is solved here. Don't forget that we also need to modify the return type when we go to the API. Let's add an API to see the effect:
@ GetMapping ("/ getUser") public ResultVO getUser () {User user = new User (); user.setId (1L); user.setAccount ("12345678"); user.setPassword ("12345678"); user.setEmail ("123@qq.com"); return new ResultVO (user);}
See what happens if the response is correct:
In this way, whether it is a correct response or an exception, the format of the response data is unified and very standardized!
The data format is standardized, but the response code code and response information msg are not yet standardized! Have you found that no matter whether it is a correct response or an abnormal response, the response code and response information can be set as much as you want? if 10 developers write 10 different response codes for the same type of response, then the format specification of this unified response is meaningless! Therefore, the response code and response information must be standardized.
Response code enumeration
To standardize the response code and response information in the response body with enumerations, let's create a response code enumeration class now:
@ Getter public enum ResultCode {SUCCESS (1000, "Operation successful"), FAILED (1001, "response failure"), VALIDATE_FAILED (1002, "Parameter Verification failure"), ERROR (5000, "unknown error"); private int code; private String msg; ResultCode (int code, String msg) {this.code = code; this.msg = msg;}}
Then modify the construction method of the response body so that it can only accept the response code enumeration to set the response code and response information:
Public ResultVO (T data) {this (ResultCode.SUCCESS, data);} public ResultVO (ResultCode resultCode, T data) {this.code = resultCode.getCode (); this.msg = resultCode.getMsg (); this.data = data;}
Then modify the response code setting method of global exception handling at the same time:
@ ExceptionHandler (APIException.class) public ResultVO APIExceptionHandler (APIException e) {/ / Note, the response code passed here enumerates return new ResultVO (ResultCode.FAILED, e.getMsg ());} @ ExceptionHandler (MethodArgumentNotValidException.class) public ResultVO MethodArgumentNotValidExceptionHandler (MethodArgumentNotValidException e) {ObjectError objectError = e.getBindingResult () .getAllErrors () .get (0) / / Note: the response code passed here enumerates return new ResultVO (ResultCode.VALIDATE_FAILED, objectError.getDefaultMessage ());}
In this way, the response code and response information can only be enumerated, so that the response data format, response code and response information can be standardized and unified! These can be referred to: Java project construction foundation: unified results, unified exceptions, unified logs.
Global processing of response data
The API returns the unified response body + the exception also returns the unified response body, which is actually very good, but there is still room for optimization. You know, it's normal to have hundreds of interfaces defined under a project. If each interface returns data with a response body, it seems a bit troublesome. Is there any way to avoid this packaging process? Of course, there is a drop, but it still needs to be dealt with as a whole.
First, create a class with annotations to make it a global processing class. Then we can enhance our controller by inheriting the methods overridden by the ResponseBodyAdvice API, depending on the code and comments:
@ RestControllerAdvice (basePackages = {"com.rudecrab.demo.controller"}) / / Note, add the package public class ResponseControllerAdvice implements ResponseBodyAdvice {@ Override public boolean supports (MethodParameter returnType, Class > aClass) {/ / if the type returned by the interface itself is ResultVO, then there is no need for additional operation, return false return! returnType.getGenericParameterType () .equals (ResultVO.class) } @ Override public Object beforeBodyWrite (Object data, MethodParameter returnType, MediaType mediaType, Class > aClass, ServerHttpRequest request, ServerHttpResponse response) {/ / String type cannot be wrapped directly, so do some special processing if (returnType.getGenericParameterType (). Equals (String.class)) {ObjectMapper objectMapper = new ObjectMapper () Try {/ / wraps the data in ResultVO, then converts it into a json string and responds to the front-end return objectMapper.writeValueAsString (new ResultVO (data));} catch (JsonProcessingException e) {throw new APIException ("return String type error") Wrap the original data in ResultVO return new ResultVO (data);}}
These two overridden methods are used to enhance the data before controller returns, and the supports method will not execute the beforeBodyWrite method until it returns true, so if some cases do not require enhancement, you can judge in the supports method. The real operation on the returned data is still in the beforeBodyWrite method, where we can wrap the data directly, so that there is no need to wrap the data on every interface, which saves us a lot of trouble.
We can now remove the data packaging of the interface to see the effect:
@ GetMapping ("/ getUser") public User getUser () {User user = new User (); user.setId (1L); user.setAccount ("12345678"); user.setPassword ("12345678"); user.setEmail ("123@qq.com"); / / Note: this is the User type returned directly, and return user; is not wrapped in ResultVO.
Then let's look at the response data:
Successfully packaged the data!
Note: the wrapper data in the beforeBodyWrite method cannot directly overturn the data of String type, so it is necessary to carry out special processing. We will not talk about too many details here. If you are interested, you can learn more about it yourself.
This is the end of the content of "how to implement the back-end interface with SpringBoot". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.