Introduction
As a Java coder for many years, I am also a senior White whoring party. I have always only read and can't send articles. I don't even have a blog account. I'm ashamed! be ashamed! In order to write this article, I temporarily registered an account. The reason for writing this article, I found that too many programmers started working after two or three months of training in training institutions, and then boasted that they had worked for 3 or 4 years. After the interview, it will be revealed when you really write code.
There is no advanced technology in this article. It is all the personal accumulation of the author in his work. If there are any mistakes or inappropriate places, you are welcome to discuss and correct them in the comment area and make common progress.
Article core
The core of this article is to start with the Controller layer often written by programmers, and make your code more and more elegant through small changes step by step.
text
Let's take the simplest addition as an example. First post a novice's code
@Controller @RequestMapping("/user") public class UserController { private static final Logger log = LoggerFactory.getLogger(UserController.class); @Autowired private UserService userService; @RequestMapping("/add") @ResponseBody private Map<String, Object> addUser(String username, Integer age, String phone) { Map<String, Object> returnMap = new HashMap<>(); if (StringUtils.isEmpty(username) || StringUtils.isEmpty(age) || StringUtils.isEmpty(phone)) { returnMap.put("status", 400); returnMap.put("msg", "Parameter cannot be empty"); return returnMap; } //Here, the parameters are checked by regular rules (omitted) User user = new User(username, age, phone); try { user = userService.addUser(user); } catch (Exception e) { e.printStackTrace(); returnMap.put("status", 500); returnMap.put("msg", "Interface exception"); log.error("Interface exception", e); } if (user!=null) { returnMap.put("status", 200); returnMap.put("msg", "success"); returnMap.put("data", user); } else { returnMap.put("status", 400); returnMap.put("msg", "Data exception"); } return returnMap; } }
public class User { private Integer id; private String username; private Integer age; private String phone; public User(String username, Integer age, String phone) { this.username = username; this.age = age; this.phone = phone; } public User() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public int hashCode() { return super.hashCode(); } @Override public boolean equals(Object obj) { return super.equals(obj); } }
Let's analyze the problems in the above Controller code:
- The interface receives data. What if the subsequent User object field is added?
- Can the parameter verification at the entrance be omitted?
- Can the User object be abbreviated
- The return value is Map. What is the returned content?
- Can the try catch code be removed?
- There are too many interference items to find the core code at a glance
Let's solve this problem step by step:
1. First, use the interface object to accept data
(at present, the front and rear ends are basically separated. Please try to use applicaiton/json)
@RequestMapping("/add") @ResponseBody private Map<String, Object> addUser(@RequestBody User user) {
2. Parameter verification can be performed with @ Validated
@RequestMapping("/add") @ResponseBody private Map<String, Object> addUser(@RequestBody @Validated User user) {
3. Shorthand User with lombok
@Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class User { private Integer id; @NotNull(message="username Cannot be empty") private String username; @NotNull(message="age Cannot be empty") private Integer age; @NotNull(message="phone Cannot be empty") private String phone; }
4. Create a unified return object
@Data @NoArgsConstructor @AllArgsConstructor public class UnifyResult<T> implements Serializable { private static final int SUCCESS = 200; private static final int SERVER_ERROR = 500; public static final int CLIENT_ERROR = 400; private Integer status; private String msg; private T data; public static UnifyResult success(T data) { return new UnifyResult(data); } public static UnifyResult serverError() { return new UnifyResult(SERVER_ERROR, "API Interface exception", null); } public static UnifyResult clientError(String msg) { return new UnifyResult(CLIENT_ERROR, msg, null); } public static UnifyResult clientError() { return new UnifyResult(CLIENT_ERROR, "Data exception", null); } private UnifyResult(T data) { this.status = 200; this.msg = "success"; this.data = data; } }
5. Replace the Map code with the unified encapsulated object as follows
@Controller @RequestMapping("/user") public class UserController { private static final Logger log = LoggerFactory.getLogger(UserController.class); @Autowired private UserService userService; @RequestMapping("/add") @ResponseBody private UnifyResult<User> addUser(@RequestBody @Validated User user) { try { user = userService.addUser(user); } catch (Exception e) { e.printStackTrace(); log.error("Interface exception", e); return UnifyResult.serverError(); } if (user != null) { return UnifyResult.success(user); } else { return UnifyResult.clientError(); } } }
6. The @ controller + @ ResponseBody can be replaced by @ RestController,
private static final Logger log = LoggerFactory.getLogger(UserController.class);
You can use @ Slf4j instead
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/add") private UnifyResult<User> addUser(@RequestBody @Validated User user) { try { user = userService.addUser(user); } catch (Exception e) { e.printStackTrace(); log.error("Interface exception", e); return UnifyResult.serverError(); } if (user != null) { return UnifyResult.success(user); } else { return UnifyResult.clientError(); } } }
7. Establish unified exception handling. The annotation to be used here is @ ControllerAdvice + @ExceptionHandler
The two annotations are used to enter the method marked with the corresponding @ ExceptionHandler when the Controller throws an exception
@Slf4j @ControllerAdvice public class MyExceptionHandler { @ResponseBody @ExceptionHandler(MethodArgumentNotValidException.class) public Object methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException exception) { BindingResult result = exception.getBindingResult(); StringBuilder sb = new StringBuilder(); if (result.hasErrors()) { List<ObjectError> errors = result.getAllErrors(); if (errors != null) { errors.forEach(p -> { FieldError fieldError = (FieldError) p; log.warn("Parameter verification exception: dto entity [{}],field [{}],message [{}]", fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage()); sb.append(fieldError.getDefaultMessage()); }); } } return UnifyResult.clientError(sb.toString()); } @ResponseBody @ExceptionHandler(HttpMessageNotReadableException.class) public Object httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException exception) { exception.printStackTrace(); return UnifyResult.clientError("Bad request type"); } @ResponseBody @ExceptionHandler public Object exceptionHandler(Exception e) { log.error("error", e); //You should pay special attention to the unknown exception of TODO. You can send email notification, etc return UnifyResult.serverError(); } }
This is the Controller code
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/add") private UnifyResult<User> addUser(@RequestBody @Validated User user) { user = userService.addUser(user); if (user != null ) { return UnifyResult.success(user); } else { return UnifyResult.clientError(); } } }
It's a little work experience to write such a description. I'm no longer a beginner programmer. The subsequent writing method is basically fixed. After writing a lot of code, I found that the packaging class should be returned every time. Can I take off the packaging? The answer is yes
8. Abstract encapsulation returns the annotation @ RestControllerAdvice + interface @ responsebodyadvice < T > to be used here. The specific function is to obtain the returned value after the Controller method returns, do further processing, and then return it to the client. supports returns true to support the data conversion
@RestControllerAdvice public class ControllerResponseBodyAdvice implements ResponseBodyAdvice<Object>{ @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { Class<?> clazz = returnType.getDeclaringClass(); return clazz.getAnnotation(RestController.class) == null ? false : true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof UnifyResult) { return body; } else if (body == null) { return UnifyResult.clientError(); } return UnifyResult.success(body); } }
Because @ RestControllerAdvice = @ ControllerAdvice + @ResponseBody, the Controller exception handling interception just now can also be placed in this class
The configuration class becomes
@RestControllerAdvice @Slf4j public class ControllerResponseBodyAdvice implements ResponseBodyAdvice<Object>{ @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { Class<?> clazz = returnType.getDeclaringClass(); return clazz.getAnnotation(RestController.class) == null ? false : true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof UnifyResult) { return body; } else if (body == null) { return UnifyResult.clientError(); } return UnifyResult.success(body); } @ExceptionHandler(MethodArgumentNotValidException.class) public Object methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException exception) { BindingResult result = exception.getBindingResult(); StringBuilder stringBuilder = new StringBuilder(); if (result.hasErrors()) { List<ObjectError> errors = result.getAllErrors(); if (errors != null) { errors.forEach(p -> { FieldError fieldError = (FieldError) p; log.warn("Parameter verification exception: dto entity [{}],field [{}],message [{}]", fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage()); stringBuilder.append(fieldError.getDefaultMessage()); }); } } return UnifyResult.clientError(stringBuilder.toString()); } @ExceptionHandler(HttpMessageNotReadableException.class) public Object httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException exception) { exception.printStackTrace(); return UnifyResult.clientError("Bad request type"); } @ExceptionHandler public Object exceptionHandler(Exception e) { log.error("error", e); //You should pay special attention to the unknown exception of TODO. You can send email notification, etc return UnifyResult.serverError(); } }
A pit will be encountered here. If the return value of the Controller is String, a type conversion exception will be reported. What is the cause
StringHttpMessageConverter ranks higher than other converters. When String is returned, it will call StringHttpMessageConverter by default for conversion. The solution is to delete this StringHttpMessageConverter. It can't be used anyway
@RestControllerAdvice @Slf4j public class ControllerResponseBodyAdvice implements ResponseBodyAdvice<Object>, WebMvcConfigurer { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { //There's a hole in springboot 2 0 overwriting configMessageConverter is not easy. converters do not use stream //The StringHttpMessageConverter is deleted mainly because the Controller returns String type, which will lead to type conversion errors Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); while (iterator.hasNext()) { HttpMessageConverter<?> next = iterator.next(); if (next instanceof StringHttpMessageConverter) { iterator.remove(); } } } @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { Class<?> clazz = returnType.getDeclaringClass(); return clazz.getAnnotation(RestController.class) == null ? false : true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof UnifyResult) { return body; } else if (body == null) { return UnifyResult.clientError(); } return UnifyResult.success(body); } @ExceptionHandler(MethodArgumentNotValidException.class) public Object methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException exception) { BindingResult result = exception.getBindingResult(); StringBuilder stringBuilder = new StringBuilder(); if (result.hasErrors()) { List<ObjectError> errors = result.getAllErrors(); if (errors != null) { errors.forEach(p -> { FieldError fieldError = (FieldError) p; log.warn("Parameter verification exception: dto entity [{}],field [{}],message [{}]", fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage()); stringBuilder.append(fieldError.getDefaultMessage()); }); } } return UnifyResult.clientError(stringBuilder.toString()); } @ExceptionHandler(HttpMessageNotReadableException.class) public Object httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException exception) { exception.printStackTrace(); return UnifyResult.clientError("Bad request type"); } @ExceptionHandler(Exception.class) public Object exceptionHandler(Exception e) { log.error("error", e); //You should pay special attention to the unknown exception of TODO. You can send email notification, etc return UnifyResult.serverError(); } }
What did the Controller eventually become?
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/add") public User addUser(@RequestBody @Validated User user) { return userService.addUser(user); } }
How did you feel? 😂
reflection:
In the above Controller code, is it appropriate to use the user entity object in the interface to accept data? The next chapter is going to talk about the transformation between DTO and entity objects