你的代码真的很优雅嘛?
导语
本人作为多年的Java码农,同时也是一个资深的白嫖党,文章向来都是只看不发,连个博客账号都没,惭愧!惭愧!为了写这篇文章,才临时起意注册了个账号。
本文没什么高深的技术,都是笔者在工作中的一些个人积累,有什么错误或不妥的地方,欢迎各位大佬在评论区探讨指正,大家共同进步。
文章核心
本文的核心就是对程序员经常写的Controller层入手,通过一步一步的小改动让你的代码变得越来越优雅。
正文
我们就从最简单的添加来举例,先贴一个新手的代码
@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", "参数不能为空");
return returnMap;
}
//在这里对参数做了正则规则校验(省略)
User user = new User(username, age, phone);
try {
user = userService.addUser(user);
} catch (Exception e) {
e.printStackTrace();
returnMap.put("status", 500);
returnMap.put("msg", "接口异常");
log.error("接口异常", e);
}
if (user!=null) {
returnMap.put("status", 200);
returnMap.put("msg", "success");
returnMap.put("data", user);
} else {
returnMap.put("status", 400);
returnMap.put("msg", "数据异常");
}
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);
}
}
我们来分析一下上面的Controller 代码有什么问题:
- 接口接收数据,如果后续User 对象字段增加了怎么办?
- 入口处的参数校验,那一坨代码可不可以省略?
- User 对象能不能简写
- 返回值是Map ,返回的内容到底是什么?
- try catch 代码能不能去掉?
- 干扰项太多不能一眼找出核心代码
下面我们来一步一步的解决这个问题:
1.首先接口请使用对象接受数据
(现在基本上都是前后端分离,请尽量使用 applicaiton/json )
@RequestMapping("/add")
@ResponseBody
private Map<String, Object> addUser(@RequestBody User user) {
2.参数校验可以用 @Validated
@RequestMapping("/add")
@ResponseBody
private Map<String, Object> addUser(@RequestBody @Validated User user) {
3.使用lombok对User进行简写
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class User {
private Integer id;
@NotNull(message="username不能为空")
private String username;
@NotNull(message="age不能为空")
private Integer age;
@NotNull(message="phone不能为空")
private String phone;
}
4.建立统一返回对象
@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接口异常", null);
}
public static UnifyResult clientError(String msg) {
return new UnifyResult(CLIENT_ERROR, msg, null);
}
public static UnifyResult clientError() {
return new UnifyResult(CLIENT_ERROR, "数据异常", null);
}
private UnifyResult(T data) {
this.status = 200;
this.msg = "success";
this.data = data;
}
}
5.使用统一封装对象替换Map 代码就变成如下这样了
@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("接口异常", e);
return UnifyResult.serverError();
}
if (user != null) {
return UnifyResult.success(user);
} else {
return UnifyResult.clientError();
}
}
}
6. @[email protected] 可以使用 @RestController 替代 ,
private static final Logger log = LoggerFactory.getLogger(UserController.class);
可以使用@Slf4j 替换
@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("接口异常", e);
return UnifyResult.serverError();
}
if (user != null) {
return UnifyResult.success(user);
} else {
return UnifyResult.clientError();
}
}
}
7. 建立统一异常处理 这里要用到的注解是 @ControllerAdvice + @ExceptionHandler
两个注解作用就是就是 Controller 抛异常 会进入标记了对应的@ExceptionHandler的方法
@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("参数校验异常: 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("错误的请求类型");
}
@ResponseBody
@ExceptionHandler
public Object exceptionHandler(Exception e) {
log.error("error", e);
//TODO 未知的异常,应该格外注意,可以发送邮件通知等
return UnifyResult.serverError();
}
}
这时候Controller 代码就变成这样了
@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();
}
}
}
能写成这样说明有点工作经验了,已经不是刚入门的程序员了。后续写法基本也固定这样了, 写了大量的代码后发现,每次都要返回包装类,能不能抽下封装下,答案是肯定的
8.抽象封装返回 这里要用到的注解 @ RestControllerAdvice +接口 ResponseBodyAdvice<T> 具体作用就是 在Controller 方法返回后, 获取返回的值,做近一步处理再返回给客户端。supports 返回true 表示支持该数据转换
@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);
}
}
因为@RestControllerAdvice = @ControllerAdvice + @ResponseBody 那么刚刚的Controller 异常处理拦截也可放在这个类里
这个配置类就变成了
@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("参数校验异常: 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("错误的请求类型");
}
@ExceptionHandler
public Object exceptionHandler(Exception e) {
log.error("error", e);
//TODO 未知的异常,应该格外注意,可以发送邮件通知等
return UnifyResult.serverError();
}
}
这里会遇到一个坑 如果Controller 返回值是String的话 会报类型转换异常,产生的原因
StringHttpMessageConverter 要比其它的 Converter 排得靠前一些,当返回 String 会默认调用StringHttpMessageConverter进行转换,解决的方法就是把这个StringHttpMessageConverter给删了就行,反正也用不到
@RestControllerAdvice
@Slf4j
public class ControllerResponseBodyAdvice implements ResponseBodyAdvice<Object>, WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//这里有坑 springboot2.0 覆盖configMessageConverter 不好使。converters 不要使用stream
//删除StringHttpMessageConverter转换器,主要为了Controller 返回为String 类型会导致类型转换错误问题
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("参数校验异常: 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("错误的请求类型");
}
@ExceptionHandler(Exception.class)
public Object exceptionHandler(Exception e) {
log.error("error", e);
//TODO 未知的异常,应该格外注意,可以发送邮件通知等
return UnifyResult.serverError();
}
}
那Controller最终变成了什么样子呢?
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/add")
public User addUser(@RequestBody @Validated User user) {
return userService.addUser(user);
}
}
感觉怎么样?????
思考:
上述 Controller代码中在接口中使用user实体对象接受数据是否合适? 下一章 准备讲一讲 DTO和实体对象之间的转化