使用 @ControllerAdvice 注解,优化异常处理

2020-04-27  

最近写代码的时候,遇到一个问题:

框架遇到异常后会返回一个 JSON 格式的数据,如:{"msg":"id不存在","code":"1001"}
为了方便前端统一处理,正确返回的 Controller 返回也要是这个格式。但要考虑各种情况的异常信息,Service 类就也要放回这个格式的类。

问题示例代码如下:

Controller 类

package blog.controller;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import blog.service.UserService;

/**
 * 用户信息管理
 * @author ConstXiong
 * @date 2020-04-26 20:49:02
 */
@Controller
@RequestMapping("/user")
public class UserController {
	
	@Autowired
	private UserService userService;

	/**
	 * 保存用户电话
	 * @param id
	 * @param tel
	 * @return
	 */
	@RequestMapping("saveUserTel")
	@ResponseBody
	public Map<String, Object> saveUserTel(Integer id, String tel) {
		return userService.saveUserTel(id, tel);
	}
	
}

 

Service 类

package blog.service;

import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * 用户信息管理服务类
 * @author ConstXiong
 * @date 2020-04-26 10:52:58
 */
@Service
public class UserService {

	/**
	 * 保存用户电话信息
	 * @param id
	 * @param tel
	 */
	public Map<String, Object> saveUserTel(int id, String tel) {
		Map<String, Object> result = new HashMap<String, Object>();
		//校验id是否存在
		if (id < 1) {
			result.put("code", "1001");//假设 id 不能为空的 错误码是1001
			result.put("msg", "id不存在");
			return result;
		}
		if (StringUtils.isEmpty(tel)) {
			result.put("code", "1002");//假设  tel 为空的 错误码是1002
			result.put("msg", "电话不能为空");
			return result;
		}
		if (tel.length() > 11) {
			result.put("code", "1003");//假设  tel 长度超过11 错误码1003
			result.put("msg", "电话号码超长");
			return result;
		}
		result.put("code", "0");//保存成功的错误
		result.put("msg", "保存成功");
		return result;
	}

}

 

请求url返回如下:

http://localhost:8081/blog/user/saveUserTel.do?id=-1&tel=13888888888
{"msg":"id不存在","code":"1001"}

http://localhost:8081/blog/user/saveUserTel.do?id=1&tel=138888888888
{"msg":"电话号码超长","code":"1003"}

http://localhost:8081/blog/user/saveUserTel.do?id=1&tel=13888888888
{"msg":"保存成功","code":"0"}

 

Service 类中存在这 2 个问题:

  1. saveUserTel(int id, String tel) 方法返回一个代表结果的 Map 类有些不妥,返回一个 boolean 类型的保存成功或保存失败似乎更容易理解
  2. 在实际开发中 saveUserTel(int id, String tel) 这类处理业务逻辑的方法,代码都比较复杂,以这种方式写,到后期阅读这段代码就比较困难,可维护性也差

 

那如何解决这些问题呢?

​@ControllerAdvice 是 spring web 中的一个注解,作用是对 Controller 进行增加,我们可以使用这个注解进行全局的异常处理。

通过 @ControllerAdvice 进行整改,是一种方式。

 

代码如下:

1、新增自定义异常

package blog.exception;

/**
 * 自定义异常
 * @author ConstXiong
 * @date 2020-04-27 10:19:15
 */
public class CustomException extends RuntimeException {

	private static final long serialVersionUID = 675600741944661752L;
	
	private String code;
	
	public CustomException(String code, String msg) {
		super(msg);
		this.code = code;
	}

	public String getCode() {
		return code;
	}
}

 

2、新增自定义异常处理类

package blog.handler;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import blog.exception.CustomException;

/**
 * 全局异常处理,捕获所有Controller中抛出的异常。
 */
@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(CustomException.class)
	@ResponseBody
	@ResponseStatus(HttpStatus.OK)
	public Map<String, Object> handleCustomException (CustomException e) throws Exception {
		Map<String, Object> error = new HashMap<String, Object>();
		error.put("code", e.getCode());
		error.put("msg", e.getMessage());
		return error;
	}

}

 

3、新增信息校验类 Checker

package blog.service;

import org.springframework.util.StringUtils;

import blog.exception.CustomException;

/**
 * 信息校验类
 * @author ConstXiong
 * @date 2020-04-26 20:28:41
 */
public class Checker {

	/**
	 * 必须大于等于某值
	 * @param num
	 * @param min
	 * @param errCode
	 * @param errMsg
	 */
	public static void mustLargerThan(int num, int min, String errCode, String errMsg) {
		if (num < min) {
			throw new CustomException(errCode, errMsg);
		}
	}

	/**
	 * 不能为空
	 * @param str
	 * @param errCode
	 * @param errMsg
	 */
	public static void mustNotEmpy(String str, String errCode, String errMsg) {
		if (StringUtils.isEmpty(str)) {
			throw new CustomException(errCode, errMsg);
		}
	}

	/**
	 * 字符串长度不能超过
	 * @param str
	 * @param len
	 * @param errCode
	 * @param errMsg
	 */
	public static void lengthNoLongerThan(String str, int len, String errCode, String errMsg) {
		if (str.length() > len) {
			throw new CustomException(errCode, errMsg);
		}
	}

}

 

4、整改 Controller 和 Service 类的代码

package blog.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import blog.service.UserService;

/**
 * 用户信息管理
 * @author ConstXiong
 * @date 2020-04-26 20:49:02
 */
@Controller
@RequestMapping("/user")
public class UserController {
	
	@Autowired
	private UserService userService;
	
	/**
	 * 保存用户电话
	 * @param id
	 * @param tel
	 * @return
	 */
	@RequestMapping("saveUserTel")
	@ResponseBody
	public Map<String, Object> saveUserTel(Integer id, String tel) {
		userService.saveUserTel(id, tel);
		
		//success 可以放到常量类里定义
		Map<String, Object> success = new HashMap<String, Object>();
		success.put("code", "0");
		success.put("msg", "保存成功");
		
		return success;
	}
	
}

 

package blog.service;

import org.springframework.stereotype.Service;

/**
 * 用户信息管理服务类
 * @author ConstXiong
 * @date 2020-04-26 10:52:58
 */
@Service
public class UserService {

	/**
	 * 保存用户电话信息
	 * @param id
	 * @param tel
	 */
	public boolean saveUserTel(int id, String tel) {
		Checker.mustLargerThan(id, 0, "1001", "id不存在");
		Checker.mustNotEmpy(tel, "1002", "电话不能为空");
		Checker.lengthNoLongerThan(tel, 11, "1003",  "电话号码超长");
		return true;
	}

}

 

整改完之后,请求 url 返回与之前相同。

 

总结一下这样处理的好处:

  1. Service 类不需要返回 Controller 返回的数据结构,只关注自己的逻辑与返回值,提高公共可用性
  2. 统一处理了异常返回信息
  3. 可以抽象出公共的校验信息

 

PS:

  • Controller 里的 success 可以放到系统常量里直接返回,不用每次都 new
  • 错误码和错误信息根据业务系统需求,进行常量定义
  • 还可以通过 SpringMVC 中的 HandlerInterceptor 实现

 

ConstXiong 备案号:苏ICP备16009629号-3