美文网首页架构Spring FrameWork
5. spring-boot REST 全局异常处理

5. spring-boot REST 全局异常处理

作者: kaenry | 来源:发表于2016-09-20 20:37 被阅读6418次

    讲点实用的小技巧,学习前端之后才发现以前写的代码真是给前端儿搞了不少事,在此诚恳道歉

    单页应用越来越多以及移动化之后,服务化已经是老生常谈了,在前文代码的基础上做些简单的通用模块的处理,后端返回结果的不一致性真的会给前端带来很大的麻烦,故此为止:

    1. 全局异常捕捉及处理
    2. REST FULL基本常见规范

    直接贴核心代码。统一返回结果RestResult

    public class RestResult<T> {
    
        private boolean result;
        private String message;
        private T data;
    
        private RestResult() {}
    
        public static <T> RestResult<T> newInstance() {
            return new RestResult<>();
        }
    
        // ...setter and getter
    
        @Override
        public String toString() {
            return "RestResult{" +
                    "result=" + result +
                    ", message='" + message + '\'' +
                    ", data=" + data +
                    '}';
        }
    }
    

    result工具类RestResultGenerator

    /**
     * Created by kaenry on 2016/9/20.
     * RestResultGenerator
     */
    public class RestResultGenerator {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(RestResultGenerator.class);
    
        /**
         * normal
         * @param success
         * @param data
         * @param message
         * @param <T>
         * @return
         */
        public static <T> RestResult<T> genResult(boolean success, T data, String message) {
            RestResult<T> result = RestResult.newInstance();
            result.setResult(success);
            result.setData(data);
            result.setMessage(message);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("generate rest result:{}", result);
            }
            return result;
        }
    
        /**
         * success
         * @param data
         * @param <T>
         * @return
         */
        public static <T> RestResult<T> genSuccessResult(T data) {
    
            return genResult(true, data, null);
        }
    
        /**
         * error message
         * @param message error message
         * @param <T>
         * @return
         */
        public static <T> RestResult<T> genErrorResult(String message) {
    
            return genResult(false, null, message);
        }
    
        /**
         * error
         * @param error error enum
         * @param <T>
         * @return
         */
        public static <T> RestResult<T> genErrorResult(ErrorCode error) {
    
            return genErrorResult(error.getMessage());
        }
    
        /**
         * success no message
         * @return
         */
        public static RestResult genSuccessResult() {
            return genSuccessResult(null);
        }
    }
    

    统一异常拦截处理:RestExceptionHandler

    /**
     * Created by kaenry on 2016/9/20.
     * RestExceptionHandler
     */
    @ControllerAdvice(annotations = RestController.class)
    public class RestExceptionHandler {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);
    
        @ExceptionHandler
        @ResponseBody
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        private <T> RestResult<T> runtimeExceptionHandler(Exception e) {
            LOGGER.error("---------> huge error!", e);
            return RestResultGenerator.genErrorResult(ErrorCode.SERVER_ERROR);
        }
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseBody
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        private <T> RestResult<T> illegalParamsExceptionHandler(MethodArgumentNotValidException e) {
            LOGGER.error("---------> invalid request!", e);
            return RestResultGenerator.genErrorResult(ErrorCode.ILLEGAL_PARAMS);
        }
    
    }
    

    无论请求成功或失败统一返回RestResult,可自由定义,比如加上错误code或异常的多次处理以及日志啊什么的,代码都很简单,这里就不详细介绍了,返回的结果类似{"result":true,"message":null,"data":{"id":3,"username":"kaenry","password":"jianshu"}}spring-boot默认使用Jackson解析拼装json,如需要忽略null,加个注解即可:@JsonInclude(JsonInclude.Include.NON_NULL),fastjson默认开启。@Valid注解会验证属性,不通过会先交给BindingResult,如果没有这个参数则会抛出异常MethodArgumentNotValidException@ExceptionHandler捕捉到异常则会进入illegalParamsExceptionHandler方法返回结果:

    {
      "result": false,
      "message": "request params invalid"
    }
    

    测试RestController修改已有代码UserRestController:

    @RestController
    @RequestMapping("/api/users")
    public class UserRestController {
    
        @Autowired
        IUserService userService;
    
        /**
         * get all user, GET
         * @return
         */
        @RequestMapping(value = "", method = RequestMethod.GET)
        public RestResult<List<User>> all() {
            List<User> all = userService.findAll();
            return RestResultGenerator.genSuccessResult(all);
        }
    
        /**
         * add single user
         * @param user username, password
         * @return RestResult
         * @throws Exception valid check
         */
        @RequestMapping(value = "", method = RequestMethod.POST)
        public RestResult<User> save(@Valid @RequestBody User user) throws Exception {
            User save = userService.save(user);
            return RestResultGenerator.genSuccessResult(save);
        }
    
        /**
         * get single user by id, GET /id
         * @param id user id
         * @return RestResult<User>
         * @throws Exception
         */
        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        public RestResult<User> get(@PathVariable Long id) throws Exception {
            User user = userService.findById(id);
            return RestResultGenerator.genSuccessResult(user);
        }
    
        /**
         * delete user by id
         * @param id user id
         * @return success
         * @throws Exception
         */
        @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        public RestResult delete(@PathVariable Long id) throws Exception {
            userService.delete(id);
            return RestResultGenerator.genSuccessResult();
        }
    
        /**
         * update user for all props
         * @param id update user id
         * @param newUser new props
         * @return updated User
         * @throws Exception
         */
        @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
        public RestResult<User> updateAll(@PathVariable Long id, @Valid @RequestBody User newUser) throws Exception {
            User user = userService.findById(id);
            // copy all new user props to user except id
            BeanUtils.copyProperties(newUser, user, "id");
            user = userService.save(user);
            return RestResultGenerator.genSuccessResult(user);
        }
    
        /**
         * update user for some props
         * @param id update user id
         * @param newUser some props
         * @return updated user
         * @throws Exception
         */
        @RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
        public RestResult<User> update(@PathVariable Long id, @Valid @RequestBody User newUser) throws Exception {
            User user = userService.findById(id);
            // copy all new user props to user except null props
            BeanUtils.copyProperties(newUser, user, Utils.getNullPropertyNames(newUser));
            user = userService.save(user);
            return RestResultGenerator.genSuccessResult(user);
        }
    }
    

    尽量按照RESTFULL标准书写的:

    1. GET /api/users,获取全部
    2. POST /api/users,新增一个
    3. GET /api/users/:id,获取单个
    4. DELETE /api/users/:id,删除单个
    5. PUT /api/users/:id,全量更新
    6. PATCH /api/users/:id,部分更新

    代码都很简单,注意参数尽量使用Bean,非特殊情况千万不要使用诸如Map作为接收参数,图一时痛快,饮恨一生啊;在这里使用@RequestBody的原因是因为现在的前端(因为有了nodejs)大多都会采用JSON直传而不是传统意义上的form了,对应其实就是http协议里的请求头从application/x-www-form-urlencoded换成了application/json;这里在更新的时候有个小技巧,使用BeanUtils复制需要的属性,getNullPropertyNames方法是返回对象里面的为null的属性,因为不需要更新,具体请看代码。地址还是那个地址:https://github.com/kaenry/spring-boot-magneto/releases/tag/v1.8.2
    毕竟不是真实项目,没有写测试,测试工具推荐使用PostMan插件,记得先获取token,随便上个图

    新增一个用户,kaenry/jianshu

    相关文章

      网友评论

      • SoulOnTheLoad:怎么用PostMan获取token?
        kaenry: @SoulOnTheLoad 在其他几篇oauth2的文章里有,找找看
      • 拿着号码牌徘徊:ErrorCode.SERVER_ERROR请问 errorCode来源于哪个包
        kaenry: @f7d20e00bc2b code是根据一般意义自己定义的,源码里有
      • d9db52f13b77:请问,RestExceptionHandler中有多个方法,多个方法有多个ExceptionHandler ,如果每个方法上面都指定一种异常, 这个注解是如何排列他们拦截的顺序的
        kaenry:@Super_Lgx 在一个Handler里这个是没有顺序的,一般而言会设计不同的异常走不同的Handler,尽量避免一个异常会同时有好几个Handler,这时此方法会失效,如果无法避免,可以在分多个类@ControllerAdvice,这个注解是可以有顺序的

      本文标题:5. spring-boot REST 全局异常处理

      本文链接:https://www.haomeiwen.com/subject/ikpoettx.html