• 中文
    • 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
    • 3k
    • pearPLUSxuleitest

      请登录之后再进行评论

      登录
    • 0
      1231
    • 0
      张甲49站长
      @xuleitest 谢谢
    • 0
      ssm初学者 赞一个 [s-1]
    • 赞助本站

      • 支付宝
      • 微信
      • QQ

      感谢一直支持本站的所有人!

      单栏布局 侧栏位置: