• 中文
    • English
  • 注册
  • 查看作者
  • SpringBoot:统一异常处理

    一.  抛出异常

    接下来我们要使用的相关数据基于本站《SpringBoot:对密码进行MD5加密》一文。在UserService中,我们的login和register两个方法的返回值都是Map,之所以使用map是因为我们需要将登录和注册的可能产生的错误结果等信息存储在Map中返回。

    除了这种方式以外,我们还可以将这一类的操作统一用异常来处理,可以大大简化我们的程序架构,提高开发效率。

    首先将UserService中的login和register的返回值修改为User和boolean

    public interface UserService {
        User login(String name, String password);
        boolean register(String name,String password);
    
    //    Map<String,Object> login(String name, String password);
    //    Map<String,Object> register(String name,String password);
    }

    在实现类中,将之前需要put进map中的错误信息,直接作为异常抛出:

    @Service("userService")
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public User login(String name, String password) {
            User user = userMapper.queryByName(name);
            if (user == null) {
                throw new RuntimeException("用户名不存在");
            }
            String password2 = user.getPassword();
            String md5 = MD5Util.getMd5(name.concat(password));
            if (!password2.equals(md5)) {
                throw new RuntimeException("密码错误");
            }
            user.setPassword(null);
            return user;
        }
    
        @Override
        public boolean register(String name, String password) {
    
            if (userMapper.queryByName(name) != null) {
                throw new RuntimeException("用户名已经存在");
            }
            User user = new User();
            String md5 = MD5Util.getMd5(name.concat(password));
            user.setName(name);
            user.setPassword(md5);
            Integer result = userMapper.doInsert(user);
            return result == 1;
        }
    
    }

    此时的UserController不需要再做error相关的判断:

    @Controller
    public class U serController {
        @Autowired
        private UserService userService;
    
        @PostMapping(value = "/login",produces = "application/json;charset=utf-8")
        @ResponseBody
        public R login(String name, String password) {
            User user = userService.login(name,password);
            return R.success(user);
        }
    
    
        @PostMapping(value = "/register",produces = "application/json;charset=utf-8")
        @ResponseBody
        public R register(String name, String password) {
            boolean register = userService.register(name, password);
            return register ?  R.success() : R.error();
        }
    
    }

    假如我们的数据库中有一个用户:zhangjia,密码也是zhangjia,那么我们访问http://localhost:8888/login?name=zhangjia&password=zhang,密码不正确,则页面显示

    {
        "timestamp": "2019-07-30T14:48:20.814+0000",
        "status": 500,
        "error": "Internal Server Error",
        "message": "密码错误",
        "path": "/login"
    }

    该json字符串是spring帮我们自动生成的默认的错误格式,但是一般情况下我们不会采用其默认的格式,通常会把返回Json字符串的所有方法的json格式统一,这里我们先暂时不去处理,具体的处理方法详情请看本文第三段。

    如果输入一个正确的用户名和密码,则页面显示:

    {
        "data": {
        "userId": 2,
        "name": "zhangjia",
        "password": null,
        "status": 1,
        "createTime": "2019-07-03T02:47:15.000+0000"
        },
        "success": true
    }

    二.  统一处理异常的类

    在Controller包下新建ExceptionHandler.java(放在util包下也可以),并添加@ControllerAdvice注解,添加该注解后,ExceptionHandler就可以作为异常处理的切面类,Controller包中的任何类产生异常,都将会交给ExceptionHandler来处理。

    import io.zhangjia.springbootmd5.util.R;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @ControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler
        @ResponseBody
        public R handle(Exception e) {
            return R.error().put("msg",e.getMessage());
        }
    }

    我们访问http://localhost:8888/login?name=zhangjia&password=zhang,密码不正确,则页面显示

    {
        "msg": "密码错误",
        "success": false
    }

    三.  统一格式

    在上文中我们曾说过,如果一个项目中有多个返回json字符串的方法,通常会把这些方法返回的json字符串格式统一,比如统一成以下格式:

    {
        "code": "0",
        "msg": "错误信息",
        "data": {
        ...
        }
    }

    我们可以新建一个工具类Result.java来生成上述格式的json字符串:

    package io.zhangjia.springbootmd5.util;
    
    public class Result {
        private int code; //状态码
        private String msg;  //错误信息
        private Object data;
        //如果需要用到data中的数据,则将Object修改为泛型T,同时Result也要变成泛型类Result<T>
        //因为使用泛型不需要向下转型,而Object需要
    
        public Result() {
        }
    
        public Result(String msg) {
            this.msg = msg;
        }
    
        public Result(String msg, Object data) {
            this.msg = msg;
            this.data = data;
        }
    
        public Result(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public Result(int code, String msg, Object data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }  
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    }

    接下来使用Result.java修改Controller即可:

    @Controller
    public class UserController {
        @Autowired
        private UserService userService;
    
        @PostMapping(value = "/login",produces = "application/json;charset=utf-8")
        @ResponseBody
        public Result login(String name, String password,HttpSession session) {
            User user = userService.login(name,password);        
            session.setAttribute("user",user);
            return new Result("登录成功");
        }
    
        @PostMapping(value = "/register",produces = "application/json;charset=utf-8")
        @ResponseBody
        public Result register(String name, String password) {
           userService.register(name, password);
            return new Result("注册成功");
        }
    
    }

    最后使用Result处理ExceptionHandler.java

    import io.zhangjia.springbootmd5.util.Result;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        /*
        * 自定义状态码
        * 100001:密码错误
        * 100002:用户名不存在
        * 100003:用户名已存在
        * */
        @ExceptionHandler
        @ResponseBody
        public Result handle(Exception e) {
            return new Result(100001,e.getMessage());
        }
    }

    上述代码的code以100001为例,接下来我们想实现这样的一个功能:handle方法可以自动根据不同的异常,选择对应的状态码,该功能可以通过自定义异常类来完成:新建exception包,并添加自定义类,一般命名为:项目名+Exception,这里以TestException为例:

    package io.zhangjia.springbootmd5.exception;
    
    public class TestException extends RuntimeException{
        private int code;
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public TestException(int code, String msg) { //构造方法x
            //RuntimeException中有接受字符串的构造方法,通过super将msg传入该方法
            super(msg);
            this.code = code;
        }
    }

    接下来回到ExceptionHandler.java,将其修改如下:

    import io.zhangjia.springbootmd5.exception.TestException;
    import io.zhangjia.springbootmd5.util.Result;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        /*
        * 自定义状态码对应的错误信息
        * 100001:密码错误
        * 100002:用户名不存在
        * 100003:用户名已存在
        * */
        @ExceptionHandler
        @ResponseBody
        public Result handle(Exception e) {
            if(e instanceof TestException) {
                TestException testException =  (TestException) e;
                int code = testException.getCode();
                String message = testException.getMessage();
                return new Result(code,message);
            }
            String message = e.getMessage();
            if(StringUtils.isEmpty(message)){
                message = "未定义错误";
            }
            return new Result(-1,message);
        }
    }

    最后修改UserServiceImpl ,将自定义的异常抛出即可。

    import io.zhangjia.springbootmd5.entity.User;
    import io.zhangjia.springbootmd5.exception.TestException;
    import io.zhangjia.springbootmd5.mapper.UserMapper;
    import io.zhangjia.springbootmd5.service.UserService;
    import io.zhangjia.springbootmd5.util.MD5Util;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Service("userService")
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public User login(String name, String password) {
            User user = userMapper.queryByName(name);
            if (user == null) {
    //            throw new RuntimeException("用户名不存在");
                throw new TestException(100002,"用户名不存在");
            }
            String password2 = user.getPassword();
            String md5 = MD5Util.getMd5(name.concat(password));
            if (!password2.equals(md5)) {
    //            throw new RuntimeException("密码错误");
                throw new TestException(100001,"密码错误");
            }
            user.setPassword(null);
            return user;
        }
    
        @Override
        public boolean register(String name, String password) {
    
            if (userMapper.queryByName(name) != null) {
    //            throw new RuntimeException("用户名已经存在");
                throw new TestException(100003,"用户名已存在");
            }
            User user = new User();
            String md5 = MD5Util.getMd5(name.concat(password));
            user.setName(name);
            user.setPassword(md5);
            Integer result = userMapper.doInsert(user);
            return result == 1;
        }
    
    }

    我们访问http://localhost:8888/login?name=zhangjia&password=zhang,密码不正确,则页面显示

    {
        "code": 100001,
        "msg": "密码错误",
        "data": null
    }

    接着访问http://localhost:8888/login?name=zhangjia11111&password=zhangjia,用户名不正确,则页面显示

    {
        "code": 100002,
        "msg": "用户名不存在",
        "data": null
    }

    最后访问http://localhost:8888/login?name=zhangjia&password=zhangjia,用户名和密码都正确,则页面显示

    {
        "code": 0,  
        "msg": "登录成功",
        "data": null
    }

    其中code为0,说明没有产生任何异常,之所以为0是因为Result中的int code默认值就是0

    四.  使用枚举类优化

    上述代码还存在一个问题,我们可以设想一下,假如有一天我们需要修改我们的错误码code,那么需要挨个修改我们的Service,假如一个错误码被用到了10次,那么就需要修改十个地方,这非常的不方便,我们可以将错误码设计成一个枚举类来解决这个问题:

    package io.zhangjia.springbootmd5.util;
    
    //枚举类,事先创建一系列的对象
    public enum ExceptionCode {
        //枚举元素,多个之间用逗号分隔,最后用分号结束
        INVALID_PASSWORD(100001, "密码错误"),
        USERNAME_NOT_EXIST(100002, "用户名不存在"),
        USERNAME_ALREADY_EXIST(100003, "用户名已存在");
    
    
        private int code;
        private String msg;
    
        //枚举类的构造方法必须私有化,private修饰符可以不写
        ExceptionCode(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }

    接下来修改ExceptionHandler.java

    package io.zhangjia.springbootmd5.exception;
    
    import io.zhangjia.springbootmd5.util.ExceptionCode;
    
    public class TestException extends RuntimeException{
        private int code;
    
        public int getCode() {
            return code;
        }
    
    
    /*    public TestException(int code, String msg) { //构造方法x
            //RuntimeException中有接受字符串的构造方法,通过super将msg传入该方法
            super(msg);
            this.code = code;
        }*/
    
        public TestException(ExceptionCode exceptionCode) { //构造方法x
            //RuntimeException中有接受字符串的构造方法,通过super将msg传入该方法
            super(exceptionCode.getMsg());
            this.code = exceptionCode.getCode();
        }
    }

    最后在UserServiceImpl中使用枚举类即可:

    @Service("userService")
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public User login(String name, String password) {
            User user = userMapper.queryByName(name);
            if (user == null) {
    //            throw new RuntimeException("用户名不存在");
    //            throw new TestException(100002,"用户名不存在");
                throw new TestException(ExceptionCode.USERNAME_NOT_EXIST);
            }
            String password2 = user.getPassword();
            String md5 = MD5Util.getMd5(name.concat(password));
            if (!password2.equals(md5)) {
    //            throw new RuntimeException("密码错误");
    //            throw new TestException(100001,"密码错误");
                throw new TestException(ExceptionCode.INVALID_PASSWORD);
            }
            user.setPassword(null);
            return user;
        }
    
        @Override
        public boolean register(String name, String password) {
    
            if (userMapper.queryByName(name) != null) {
    //            throw new RuntimeException("用户名已经存在");
    //            throw new TestException(100003,"用户名已存在");
                throw new TestException(ExceptionCode.USERNAME_ALREADY_EXIST);
            }
            User user = new User();
            String md5 = MD5Util.getMd5(name.concat(password));
            user.setName(name);
            user.setPassword(md5);
            Integer result = userMapper.doInsert(user);
            return result == 1;
        }
    
    }

  • 0
  • 3
  • 0
  • 3.4k
  • pearPLUSxuleitest

    请登录之后再进行评论

    登录
  • 0
    1231
  • 0
    张甲49站长
    @xuleitest 谢谢
  • 0
    ssm初学者 赞一个 [s-1]
  • 单栏布局 侧栏位置: