spring boot 统一异常处理+@Valid 验证 (二)
2019-10-11 本文已影响0人
东本三月
1.使用环境
- spring boot 2.1.9 +mybatis-plus 3.2.0
2.增加异常类
- 在项目添加一些异常类,实现对项目抛出异常的"合并".
- 这里我把所有异常分为两种,一是业务提示性的异常,如 ""用户名不能少于6位,"已审核的数据不能再次审核" 等等.这些我用ServiceException类进行展示. 二是项目出错性的异常,如 空指针,类型转换异常等,我用ErrorException类进行保存.
- 用一个父类BasicException来让两个异常类进行继承,方便扩展
BasicException
/**
* 所有自定义异常的父类
* @time 2018年9月26日10:53:05
* @author authstr
*/
public class BasicException extends RuntimeException {
public final static String DEFAULT_CODE=BaseExceptionEnum.SERVER_ERROR.getCode();
String code;
Object data;
public BasicException() {
super();
}
public BasicException(String message) {
super(message);
code=DEFAULT_CODE;
}
public BasicException(String code, String message){
this(message);
this.code=code;
}
public BasicException(String code, String message, Object data){
this(message);
this.code=code;
this.data=data;
}
/**
* 通过消息枚举创建异常
* @param msgEnum
*/
public BasicException(ExceptionEnumInterface msgEnum){
this(msgEnum.getCode(),msgEnum.getMessage());
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
ServiceException
/**
* 消息型异常,用来在接口返回值显示消息
* @time 2018年9月26日10:52:51
* @author authstr
*
*/
public class ServiceException extends BasicException {
public ServiceException() {
super();
}
public ServiceException(String message) {
super(message);
code=DEFAULT_CODE;
}
public ServiceException(String code, String message){
this(message);
this.code=code;
}
public ServiceException(String code, String message, Object data){
this(message);
this.code=code;
this.data=data;
}
}
ErrorException
/**
* 错误型异常,用来记录系统内部未知的出错
* @time 2018年9月26日10:53:05
* @author authstr
*/
public class ErrorException extends BasicException {
public ErrorException() {
super();
}
public ErrorException(String message) {
super(message);
this.code=DEFAULT_CODE;
}
public ErrorException(String code, String message){
this(message);
this.code=code;
}
public ErrorException(String code, String message, Object data){
this(message);
this.code=code;
this.data=data;
}
}
3.添加提示信息枚举
- 一般来说错误提示都要有错误代码和错误信息,这些信息用枚举进行统一储存比较方便.
- 实现比较简单,创建接口ExceptionEnumInterface作为所有信息枚举的抽象,再添加BaseExceptionEnum,实现接口,记录一些基础的常用的提示信息.
- 后续其他模块可以在本模块增加枚举类,来保存和使用所需要的提示信息
ExceptionEnumInterface
/**
* 异常消息枚举的实现接口
*/
public interface ExceptionEnumInterface {
String getCode();
String getMessage();
}
BaseExceptionEnum
public enum BaseExceptionEnum implements ExceptionEnumInterface {
SUCCESS("0","操作成功"),
SERVER_ERROR("500", "服务器异常"),
PARA_ERROR("400", "请求参数错误"),
UNKNOWN_ERROR("-1", "未知异常");
private String code;
private String message;
BaseExceptionEnum(String code, String message){
this.code=code;
this.message=message;
}
@Override
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
4. 提供异常的便捷抛出
- 一般情况,都是通过if判断,然后抛出异常并设置异常的属性,较麻烦.可以用一个类Assert来减少代码量
Assert
/**
* 用于快捷的抛出异常
* @author authstr
*/
public class Assert {
/**
* 为false时抛出一个"服务器错误"的异常
* @param exp 为false抛出异常
* @time 2018年9月17日10:10:33
* @author authstr
*/
public static void isTrue(boolean exp) {
Assert.isTrue(exp,BaseExceptionEnum.SERVER_ERROR.getMessage());
}
/**
* 为false时抛出一个指定message的异常
* @param exp 为false抛出异常
* @param message 该异常要显示的信息
* @time 2018年9月17日10:13:05
* @author authstr
*/
public static void isTrue(boolean exp, String message) {
Assert.isTrue(exp, message, false);
}
/**
* 为false时抛出一个指定枚举内信息的异常
* @param exp 为false抛出异常
* @param msgEnum 该异常要使用的异常信息枚举
* @time 2019年10月11日16:34:08
* @author authstr
*/
public static void isTrue(boolean exp, ExceptionEnumInterface msgEnum) {
Assert.isTrue(exp, msgEnum.getCode(), msgEnum.getMessage());
}
public static void isTrue(boolean exp, String code,String message) {
Assert.isTrue(exp, code, message,null,false);
}
/**
* 为false时抛出一个指定message的消息异常或者错误异常
* @param exp 为false抛出异常
* @param message 该异常要显示的信息
* @param isError 该异常是否为错误异常
* @time 2018年9月17日10:13:17
* @author authstr
*/
public static void isTrue(boolean exp, String message, boolean isError) {
Assert.isTrue(exp, BaseExceptionEnum.SERVER_ERROR.getCode(), message,null,isError);
}
/**
* 为false时抛出一个指定信息的消息异常或者错误异常
* @param exp 为false抛出异常
* @param message 异常的详细说明
* @param data 该异常要附带的数据信息
* @param isError 该异常是否为错误异常
* @time 2018年9月26日10:11:19
* @author authstr
*/
public static void isTrue(boolean exp, String code, String message,Object data,boolean isError){
if (!exp) {
if (!isError) {
throw new ServiceException(code,message,data);
}else{
throw new ErrorException(code,message,data);
}
}
}
/**
* 对象为空时,抛出一个异常
* @param o
* @time 2019年3月21日20:07:56
*/
public static void notNull(Object o){
if(o==null){
throw new ErrorException(BaseExceptionEnum.PARA_ERROR.getMessage());
}
}
5.增加response数据封装类
- 对请求返回的数据与相关信息进行封装,来规范请求返回的格式,减少代码量
- 由三个类组成,一是SuccessResponseData,表示成功的请求响应,二是ErrorResponseData,表示出错的请求响应,三是ResponseData,作为之前两个类的父类
ResponseData
import org.springframework.util.StringUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 对请求返回的数据进行封装 父类
* @author authstr
* @time 2019年10月11日14:10:28
*/
public class ResponseData {
private Boolean success;
private String code;
private String message;
private Object data;
public ResponseData(){}
public ResponseData(Boolean success,String code,String message,Object data){
this.success = success;
this.code = code;
this.message = message;
this.data = data;
}
/**
* 将枚举的信息添加到对象
* @param msgenum
*/
public void addEnumInfo( ExceptionEnumInterface msgenum ){
setCode(msgenum.getCode());
setMessage(msgenum.getMessage());
}
/**
* 将异常的信息添加到对象
* @param e
*/
public void addExceptionInfo( BasicException e ){
setSuccess(false);
if(StringUtils.hasText(e.getCode())){
setCode(e.getCode());
}
if(StringUtils.hasText(e.getMessage())){
setMessage(e.getMessage());
}
if(e.getData()!=null){
setData(e.getData());
}
//获取异常的行号信息 进行储存
//该功能后续再补充
}
public static SuccessResponseData success() {
return new SuccessResponseData();
}
public static SuccessResponseData success(Object object) {
return new SuccessResponseData(object);
}
public static SuccessResponseData success(String code, String message, Object object) {
return new SuccessResponseData(code, message, object);
}
public static ErrorResponseData error(String message) {
return new ErrorResponseData(message);
}
public static ErrorResponseData error(String code, String message) {
return new ErrorResponseData(code, message);
}
public static ErrorResponseData error(String code, String message, Object object) {
return new ErrorResponseData(code, message, object);
}
/**
* 创建一个默认的成功返回对象.之后以键值对的方式,保存两个对象到Date
* @param key1 对象1的key
* @param value1 对象1
* @param key2 对象2的key
* @param value2 对象2
*/
public static SuccessResponseData success(String key1,Object value1,String key2,Object value2) {
SuccessResponseData successResponseData=new SuccessResponseData();
successResponseData.setData( key1, value1, key2, value2);
return successResponseData;
}
/**
* 创建一个默认的成功返回对象.之后以父子关系的方式,保存两个对象到Date.父对象将转换为map作为data,子对象将作为map的一个键值对
* @param parentData 父对象
* @param childName 子对象的key
* @param childData 子对象
*/
public static SuccessResponseData success(Object parentData,String childName,Object childData) {
SuccessResponseData successResponseData=new SuccessResponseData();
successResponseData.setParentChildData( parentData, childName, childData);
return successResponseData;
}
/**
* 用于以键值对的方式,保存两个对象到Date
* @param key1 对象1的key
* @param value1 对象1
* @param key2 对象2的key
* @param value2 对象2
*/
public void setData(String key1,Object value1,String key2,Object value2) {
putData(key1,value1);
putData(key2,value2);
}
/**
* 用于以父子关系的方式,保存两个对象到Date.父对象将转换为map作为data,子对象将作为map的一个键值对
* @param parentData 父对象
* @param childName 子对象的key
* @param childData 子对象
*/
public void setParentChildData(Object parentData,String childName,Object childData) {
putData(parentData);
putData(childName,childData);
}
/**
* 将字段data设置为Map对象,原来的对象会转换为Map进行保存,无法转换则保存到'data'键
*/
public Map<String,Object> dataSetMap(){
Object old_object=this.getData();
//判断当前data储存的是否为Map
if(!(old_object instanceof Map)){
Map<String,Object> mapData=null;
//如果不是map,创建一个map, 原来的对象会转换为Map进行保存,无法转换保存到'data'键
mapData=new HashMap<>();
if(old_object!=null){
try {
Map<String,Object> tmep=PropertyUtils.describe(old_object);
//移除无关的class键
tmep.remove("class");
mapData.putAll(tmep);
}catch (Exception e){
e.printStackTrace();
mapData.put("data",old_object);
}
}
setData(mapData);
}
return (Map<String,Object> )getData();
}
/**
* 将一个键值对保存到 data的 map 里
* @param key
* @param value
*/
public void putData(String key,Object value){
Map<String,Object> mapData=dataSetMap();
mapData.put(key,value);
}
/**
* 将一个对象转换为map,合并到 data的map 里
* @param entity
*/
public void putData(Object entity){
Map<String,Object> mapData=dataSetMap();
if(entity!=null){
try {
Map<String,Object> tmep= PropertyUtils.describe(entity);
//移除无关的class键
tmep.remove("class");
mapData.putAll(tmep);
}catch (Exception e){
e.printStackTrace();
mapData.put(entity.getClass().getName(),entity);
}
}
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
SuccessResponseData
/**
* 对请求成功 要返回的数据进行封装
* @author authstr
* @time 2019年10月11日14:11:10
*/
public class SuccessResponseData extends ResponseData {
public SuccessResponseData() {
super(true, BaseExceptionEnum.SUCCESS.getCode(), BaseExceptionEnum.SUCCESS.getMessage(), (Object)null);
}
public SuccessResponseData(Object object) {
super(true, BaseExceptionEnum.SUCCESS.getCode(),BaseExceptionEnum.SUCCESS.getMessage(), object);
}
public SuccessResponseData(String code, String message, Object object) {
super(true, code, message, object);
}
}
ErrorResponseData
/**
* 对请求失败 要返回的数据进行封装
* @author authstr
* @time 2019年10月11日14:12:23
*/
public class ErrorResponseData extends ResponseData {
private String exceptionClazz;
public ErrorResponseData() {
super(false, BaseExceptionEnum.SERVER_ERROR.getCode(), BaseExceptionEnum.SERVER_ERROR.getMessage(), null);
}
public ErrorResponseData(String message) {
super(false, BaseExceptionEnum.SERVER_ERROR.getCode(), message, null);
}
public ErrorResponseData(String code, String message) {
super(false, code, message, null);
}
public ErrorResponseData(String code, String message, Object object) {
super(false, code, message, object);
}
public String getExceptionClazz() {
return exceptionClazz;
}
public void setExceptionClazz(String exceptionClazz) {
this.exceptionClazz = exceptionClazz;
}
}
6.在model添加@Vaild的相关注解,并进行验证
- 以user为例
@Data
@TableName(value = "base_user")
public class BaseUser extends BaseModel {
//用户名
@NotEmpty(message = "用户名不能为空")
@Length(min=6,max = 16,message = "用户名需为6-16字符")
private String username;
//密码
@NotEmpty(message = "密码不能为空")
private String password;
// 性别
private String sex_id;
//邮箱
private String email;
//手机号
private String phone;
//备注
private String remark;
//状态
private Integer status_id;
}
- controller使用@Valid注解
@RequestMapping("add")
public ResponseData add(@Valid BaseUser user) {
userService.add(user);
return ResponseData.success();
}
7. 对所有异常进行捕获处理
/**
* 控制层的超类,
* 主要对控制层的异常进行捕获,通过接口返回或者进行其他处理
* @author authstr
* 2019年10月7日18:16:41
*/
@Component
public class AbstractController {
protected Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 捕获其他异常
* @param ex
* @return
*/
@ExceptionHandler(value={Exception.class})
@ResponseBody
public ErrorResponseData exceptionHandler(Exception ex) {
ErrorResponseData res=new ErrorResponseData();
//不确定的异常,在返回值显示未知异常,具体错误消息进行记录和打印
res.addEnumInfo(BaseExceptionEnum.UNKNOWN_ERROR);
ex.printStackTrace();
this.log.error( "系统出现未知异常 :" + ex.getMessage());
return res;
}
/**
* 捕获消息型的异常
* @param ex
* @return
*/
@ExceptionHandler(value={ServiceException.class})
@ResponseBody
public ErrorResponseData exceptionHandler(ServiceException ex) {
ErrorResponseData res=new ErrorResponseData();
res.addExceptionInfo(ex);
return res;
}
/**
* 捕获出错型的异常
* @param ex
* @return
*/
@ExceptionHandler(value={ErrorException.class})
@ResponseBody
public ErrorResponseData exceptionHandler(ErrorException ex) {
ErrorResponseData res=new ErrorResponseData();
//出现错误型异常,在返回值显示服务器异常,具体错误消息进行记录和打印
ex.printStackTrace();
log.error("系统执行出现异常:" + ex.getMessage());
return res;
}
/**
* 捕获数据绑定异常
* @param ex
* @return
*/
@ExceptionHandler(value={BindException.class,MethodArgumentNotValidException.class})
@ResponseBody
public ErrorResponseData bindExceptionHandler(Exception ex) {
ErrorResponseData res=new ErrorResponseData();
//出现参数不正确的异常,在返回值显示提示信息,具体错误消息进行记录和打印
//设置为参数错误
res.addEnumInfo(BaseExceptionEnum.PARA_ERROR);
List<FieldError> fieldErrors=null;
if(ex instanceof BindException){
fieldErrors=((BindException)ex).getBindingResult().getFieldErrors();
}else if(ex instanceof MethodArgumentNotValidException){
fieldErrors=((MethodArgumentNotValidException)ex).getBindingResult().getFieldErrors();
}else{
return res;
}
List<String> allError=new ArrayList<>();
//将第一个错误信息作为响应的错误信息
res.setMessage(fieldErrors.get(0).getDefaultMessage());
//记录其他错误信息
for (FieldError error:fieldErrors){
allError.add(error.getDefaultMessage());
}
res.setData(allError);
return res;
}
}