欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

你的代码真的很优雅嘛?

程序员文章站 2022-05-14 15:54:58
...

导语

         本人作为多年的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 代码有什么问题:

  1. 接口接收数据,如果后续User 对象字段增加了怎么办?
  2. 入口处的参数校验,那一坨代码可不可以省略?
  3. User 对象能不能简写
  4. 返回值是Map ,返回的内容到底是什么?
  5. try catch 代码能不能去掉?
  6. 干扰项太多不能一眼找出核心代码 

下面我们来一步一步的解决这个问题:

        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和实体对象之间的转化