共计 17729 个字符,预计需要花费 45 分钟才能阅读完成。
背景
软件开发过程中,不可避免的是须要解决各种异样,就我本人来说,至多有一半以上的工夫都是在解决各种异常情况,所以代码中就会呈现大量的 try {…} catch {…} finally {…} 代码块,不仅有大量的冗余代码,而且还影响代码的可读性。比拟上面两张图,看看您当初编写的代码属于哪一种格调?而后哪种编码格调您更喜爱?
俊俏的 try catch 代码块
优雅的 Controller
下面的示例,还只是在 Controller 层,如果是在 Service 层,可能会有更多的 try catch 代码块。这将会重大影响代码的可读性、“好看性”。
所以如果是我的话,我必定偏差于第二种,我能够把更多的精力放在业务代码的开发,同时代码也会变得更加简洁。
既然业务代码不显式地对异样进行捕捉、解决,而异样必定还是解决的,不然零碎岂不是动不动就解体了,所以必须得有其余中央捕捉并解决这些异样。
那么问题来了,如何优雅的解决各种异样?
什么是对立异样解决
Spring 在 3.2 版本减少了一个注解 @ControllerAdvice,能够与 @ExceptionHandler、@InitBinder、@ModelAttribute 等注解注解配套应用,对于这几个注解的作用,这里不做过多赘述,若有不理解的,能够参考 Spring3.2 新注解 @ControllerAdvice,先大略有个理解。
不过跟异样解决相干的只有注解 @ExceptionHandler,从字面上看,就是 异样处理器 的意思,其理论作用也是:若在某个 Controller 类定义一个异样解决办法,并在办法上增加该注解,那么当呈现指定的异样时,会执行该解决异样的办法,其能够应用 springmvc 提供的数据绑定,比方注入 HttpServletRequest 等,还能够承受一个以后抛出的 Throwable 对象。
然而,这样一来,就必须在每一个 Controller 类都定义一套这样的异样解决办法,因为异样能够是各种各样。这样一来,就会造成大量的冗余代码,而且若须要新增一种异样的解决逻辑,就必须批改所有 Controller 类了,很不优雅。
当然你可能会说,那就定义个相似 BaseController 的基类,这样总行了吧。
这种做法尽管没错,但仍不尽如人意,因为这样的代码有肯定的侵入性和耦合性。简简单单的 Controller,我为啥非得继承这样一个类呢,万一曾经继承其余基类了呢。大家都晓得 Java 只能继承一个类。
那有没有一种计划,既不须要跟 Controller 耦合,也能够将定义的 异样处理器 利用到所有控制器呢?所以注解 @ControllerAdvice 呈现了,简略的说,该注解能够把异样处理器利用到所有控制器,而不是单个控制器。借助该注解,咱们能够实现:在独立的某个中央,比方独自一个类,定义一套对各种异样的解决机制,而后在类的签名加上注解 @ControllerAdvice,对立对 不同阶段的、不同异样 进行解决。这就是对立异样解决的原理。
留神到上面对异样按阶段进行分类,大体能够分成:进入 Controller 前的异样 和 Service 层异样,具体能够参考下图:
不同阶段的异样
指标
毁灭 95% 以上的 try catch 代码块,以优雅的 Assert(断言) 形式来校验业务的异常情况,只关注业务逻辑,而不必破费大量精力写冗余的 try catch 代码块。
对立异样解决实战
在定义对立异样解决类之前,先来介绍一下如何优雅的断定异常情况并抛异样。
用 Assert(断言) 替换 throw exception
想必 Assert(断言) 大家都很相熟,比方 Spring 家族的 org.springframework.util.Assert,在咱们写测试用例的时候常常会用到,应用断言能让咱们编码的时候有一种非个别丝滑的感觉,比方:
@Test
public void test1() {
...
User user = userDao.selectById(userId);
Assert.notNull(user, "用户不存在.");
...
}
@Test
public void test2() {
// 另一种写法
User user = userDao.selectById(userId);
if (user == null) {throw new IllegalArgumentException("用户不存在.");
}
}
有没有感觉第一种断定非空的写法很优雅,第二种写法令是绝对俊俏的 if {…} 代码块。那么神奇的 Assert.notNull() 背地到底做了什么呢?上面是 Assert 的局部源码:
public abstract class Assert {public Assert() { }
public static void notNull(@Nullable Object object, String message) {if (object == null) {throw new IllegalArgumentException(message);
}
}
}
能够看到,Assert 其实就是帮咱们把 if {…} 封装了一下,是不是很神奇。尽管很简略,但不可否认的是编码体验至多晋升了一个品位。那么咱们能不能模拟 org.springframework.util.Assert,也写一个断言类,不过断言失败后抛出的异样不是 IllegalArgumentException 这些内置异样,而是咱们本人定义的异样。上面让咱们来尝试一下。
Assert
public interface Assert {
/**
* 创立异样
* @param args
* @return
*/
BaseException newException(Object... args);
/**
* 创立异样
* @param t
* @param args
* @return
*/
BaseException newException(Throwable t, Object... args);
/**
* <p> 断言对象 <code>obj</code> 非空。如果对象 <code>obj</code> 为空,则抛出异样
*
* @param obj 待判断对象
*/
default void assertNotNull(Object obj) {if (obj == null) {throw newException(obj);
}
}
/**
* <p> 断言对象 <code>obj</code> 非空。如果对象 <code>obj</code> 为空,则抛出异样
* <p> 异样信息 <code>message</code> 反对传递参数形式,防止在判断之前进行字符串拼接操作
*
* @param obj 待判断对象
* @param args message 占位符对应的参数列表
*/
default void assertNotNull(Object obj, Object... args) {if (obj == null) {throw newException(args);
}
}
}
下面的 Assert 断言办法是应用接口的默认办法定义的,而后有没有发现当断言失败后,抛出的异样不是具体的某个异样,而是交由 2 个 newException 接口办法提供。因为业务逻辑中呈现的异样根本都是对应特定的场景,比方依据用户 id 获取用户信息,查问后果为 null,此时抛出的异样可能为 UserNotFoundException,并且有特定的异样码(比方 7001)和异样信息“用户不存在”。所以具体抛出什么异样,有 Assert 的实现类决定。
看到这里,您可能会有这样的疑难,依照下面的说法,那岂不是有多少异常情况,就得有定义等量的断言类和异样类,这显然是反人类的,这也没设想中高超嘛。别急,且听我细细道来。
善解人意的 Enum
自定义异样 BaseException 有 2 个属性,即 code、message,这样一对属性,有没有想到什么类个别也会定义这 2 个属性?没错,就是枚举类。且看我如何将 Enum 和 Assert 联合起来,置信我肯定会让你眼前一亮。如下:
public interface IResponseEnum {int getCode();
String getMessage();}
/**
* <p> 业务异样 </p>
* <p> 业务解决时,出现异常,能够抛出该异样 </p>
*/
public class BusinessException extends BaseException {
private static final long serialVersionUID = 1L;
public BusinessException(IResponseEnum responseEnum, Object[] args, String message) {super(responseEnum, args, message);
}
public BusinessException(IResponseEnum responseEnum, Object[] args, String message, Throwable cause) {super(responseEnum, args, message, cause);
}
}
public interface BusinessExceptionAssert extends IResponseEnum, Assert {
@Override
default BaseException newException(Object... args) {String msg = MessageFormat.format(this.getMessage(), args);
return new BusinessException(this, args, msg);
}
@Override
default BaseException newException(Throwable t, Object... args) {String msg = MessageFormat.format(this.getMessage(), args);
return new BusinessException(this, args, msg, t);
}
}
@Getter
@AllArgsConstructor
public enum ResponseEnum implements BusinessExceptionAssert {
/**
* Bad licence type
*/
BAD_LICENCE_TYPE(7001, "Bad licence type."),
/**
* Licence not found
*/
LICENCE_NOT_FOUND(7002, "Licence not found.")
;
/**
* 返回码
*/
private int code;
/**
* 返回音讯
*/
private String message;
}
看到这里,有没有眼前一亮的感觉,代码示例中定义了两个枚举实例:
BAD_LICENCE_TYPE、LICENCE_NOT_FOUND,别离对应了 BadLicenceTypeException、LicenceNotFoundException 两种异样。当前每减少一种异常情况,只需减少一个枚举实例即可,再也不必每一种异样都定义一个异样类了。而后再来看下如何应用,假如 LicenceService 有校验 Licence 是否存在的办法,如下:
/**
* 校验 {@link Licence} 存在
* @param licence
*/
private void checkNotNull(Licence licence) {ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
}
若不应用断言,代码可能如下:private void checkNotNull(Licence licence) {if (licence == null) {throw new LicenceNotFoundException();
// 或者这样
throw new BusinessException(7001, "Bad licence type.");
}
}
应用枚举类联合(继承)Assert,只需依据特定的异常情况定义不同的枚举实例,如下面的 BAD_LICENCE_TYPE、LICENCE_NOT_FOUND,就可能针对不同状况抛出特定的异样(这里指携带特定的异样码和异样音讯),这样既不必定义大量的异样类,同时还具备了断言的良好可读性,当然这种计划的益处远不止这些,请持续浏览后文,缓缓领会。
注:下面举的例子是针对特定的业务,而有局部异常情况是通用的,比方:服务器忙碌、网络异样、服务器异样、参数校验异样、404 等,所以有 CommonResponseEnum、ArgumentResponseEnum、ServletResponseEnum,其中 ServletResponseEnum 会在后文具体阐明。
定义对立异样处理器类
@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
@ConditionalOnMissingBean(UnifiedExceptionHandler.class)
public class UnifiedExceptionHandler {
/**
* 生产环境
*/
private final static String ENV_PROD = "prod";
@Autowired
private UnifiedMessageSource unifiedMessageSource;
/**
* 以后环境
*/
@Value("${spring.profiles.active}")
private String profile;
/**
* 获取国际化音讯
*
* @param e 异样
* @return
*/
public String getMessage(BaseException e) {String code = "response." + e.getResponseEnum().toString();
String message = unifiedMessageSource.getMessage(code, e.getArgs());
if (message == null || message.isEmpty()) {return e.getMessage();
}
return message;
}
/**
* 业务异样
*
* @param e 异样
* @return 异样后果
*/
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ErrorResponse handleBusinessException(BaseException e) {log.error(e.getMessage(), e);
return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
}
/**
* 自定义异样
*
* @param e 异样
* @return 异样后果
*/
@ExceptionHandler(value = BaseException.class)
@ResponseBody
public ErrorResponse handleBaseException(BaseException e) {log.error(e.getMessage(), e);
return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
}
/**
* Controller 上一层相干异样
*
* @param e 异样
* @return 异样后果
*/
@ExceptionHandler({
NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
// BindException.class,
// MethodArgumentNotValidException.class
HttpMediaTypeNotAcceptableException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
MissingServletRequestPartException.class,
AsyncRequestTimeoutException.class
})
@ResponseBody
public ErrorResponse handleServletException(Exception e) {log.error(e.getMessage(), e);
int code = CommonResponseEnum.SERVER_ERROR.getCode();
try {ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf(e.getClass().getSimpleName());
code = servletExceptionEnum.getCode();} catch (IllegalArgumentException e1) {log.error("class [{}] not defined in enum {}", e.getClass().getName(), ServletResponseEnum.class.getName());
}
if (ENV_PROD.equals(profile)) {
// 当为生产环境, 不适宜把具体的异样信息展现给用户, 比方 404.
code = CommonResponseEnum.SERVER_ERROR.getCode();
BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
String message = getMessage(baseException);
return new ErrorResponse(code, message);
}
return new ErrorResponse(code, e.getMessage());
}
/**
* 参数绑定异样
*
* @param e 异样
* @return 异样后果
*/
@ExceptionHandler(value = BindException.class)
@ResponseBody
public ErrorResponse handleBindException(BindException e) {log.error("参数绑定校验异样", e);
return wrapperBindingResult(e.getBindingResult());
}
/**
* 参数校验异样,将校验失败的所有异样组合成一条错误信息
*
* @param e 异样
* @return 异样后果
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseBody
public ErrorResponse handleValidException(MethodArgumentNotValidException e) {log.error("参数绑定校验异样", e);
return wrapperBindingResult(e.getBindingResult());
}
/**
* 包装绑定异样后果
*
* @param bindingResult 绑定后果
* @return 异样后果
*/
private ErrorResponse wrapperBindingResult(BindingResult bindingResult) {StringBuilder msg = new StringBuilder();
for (ObjectError error : bindingResult.getAllErrors()) {msg.append(",");
if (error instanceof FieldError) {msg.append(((FieldError) error).getField()).append(":");
}
msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
}
return new ErrorResponse(ArgumentResponseEnum.VALID_ERROR.getCode(), msg.substring(2));
}
/**
* 未定义异样
*
* @param e 异样
* @return 异样后果
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ErrorResponse handleException(Exception e) {log.error(e.getMessage(), e);
if (ENV_PROD.equals(profile)) {
// 当为生产环境, 不适宜把具体的异样信息展现给用户, 比方数据库异样信息.
int code = CommonResponseEnum.SERVER_ERROR.getCode();
BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
String message = getMessage(baseException);
return new ErrorResponse(code, message);
}
return new ErrorResponse(CommonResponseEnum.SERVER_ERROR.getCode(), e.getMessage());
}
}
能够看到,下面将异样分成几类,实际上只有两大类,一类是 ServletException、ServiceException,还记得上文提到的 按阶段分类 吗,即对应 进入 Controller 前的异样 和 Service 层异样;而后 ServiceException 再分成自定义异样、未知异样。对应关系如下:
- 进入 Controller 前的异样: handleServletException、handleBindException、handleValidException
- 自定义异样: handleBusinessException、handleBaseException
- 未知异样: handleException
接下来别离对这几种异样处理器做具体阐明。
异样处理器阐明
handleServletException
一个 http 申请,在达到 Controller 前,会对该申请的申请信息与指标控制器信息做一系列校验。这里简略说一下:
- NoHandlerFoundException:首先依据申请 Url 查找有没有对应的控制器,若没有则会抛该异样,也就是大家十分相熟的 404 异样;
- HttpRequestMethodNotSupportedException:若匹配到了(匹配后果是一个列表,不同的是 http 办法不同,如:Get、Post 等),则尝试将申请的 http 办法与列表的控制器做匹配,若没有对应 http 办法的控制器,则抛该异样;
- HttpMediaTypeNotSupportedException:而后再对申请头与控制器反对的做比拟,比方 content-type 申请头,若控制器的参数签名蕴含注解 @RequestBody,然而申请的 content-type 申请头的值没有蕴含 application/json,那么会抛该异样(当然,不止这种状况会抛这个异样);
- MissingPathVariableException:未检测到门路参数。比方 url 为:/licence/{licenceId},参数签名蕴含 @PathVariable(“licenceId”),当申请的 url 为 /licence,在没有明确定义 url 为 /licence 的状况下,会被断定为:短少门路参数;
- MissingServletRequestParameterException:短少申请参数。比方定义了参数 @RequestParam(“licenceId”) String licenceId,但发动申请时,未携带该参数,则会抛该异样;
- TypeMismatchException: 参数类型匹配失败。比方:接管参数为 Long 型,但传入的值确是一个字符串,那么将会呈现类型转换失败的状况,这时会抛该异样;
- HttpMessageNotReadableException:与下面的 HttpMediaTypeNotSupportedException 举的例子齐全相同,即申请头携带了 ”content-type: application/json;charset=UTF-8″,但接管参数却没有增加注解 @RequestBody,或者申请体携带的 json 串反序列化成 pojo 的过程中失败了,也会抛该异样;
- HttpMessageNotWritableException:返回的 pojo 在序列化成 json 过程失败了,那么抛该异样;
- HttpMediaTypeNotAcceptableException:未知;
- ServletRequestBindingException:未知;
- ConversionNotSupportedException:未知;
- MissingServletRequestPartException:未知;
- AsyncRequestTimeoutException:未知;
handleBindException
参数校验异样,后文具体阐明。
handleValidException
参数校验异样,后文具体阐明。
handleBusinessException、handleBaseException
解决自定义的业务异样,只是 handleBaseException 解决的是除了 BusinessException 意外的所有业务异样。就目前来看,这 2 个是能够合并成一个的。
handleException
解决所有未知的异样,比方操作数据库失败的异样。
注:下面的 handleServletException、handleException 这两个处理器,返回的异样信息,不同环境返回的可能不一样,认为这些异样信息都是框架自带的异样信息,个别都是英文的,不太好间接展现给用户看,所以对立返回 SERVER_ERROR 代表的异样信息。
异于常人的 404
上文提到,当申请没有匹配到控制器的状况下,会抛出 NoHandlerFoundException 异样,但其实默认状况下不是这样,默认状况下会呈现相似如下页面:
Whitelabel Error Page
这个页面是如何呈现的呢?实际上,当呈现 404 的时候,默认是不抛异样的,而是 forward 跳转到 /error 控制器,spring 也提供了默认的 error 控制器,如下:
BasicErrorController
那么,如何让 404 也抛出异样呢,只需在 properties 文件中退出如下配置即可:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
如此,就能够异样处理器中捕捉它了,而后前端只有捕捉到特定的状态码,立刻跳转到 404 页面即可
捕捉 404 对应的异样
对立返回后果
在验证对立异样处理器之前,顺便说一下对立返回后果。说白了,其实是对立一下返回后果的数据结构。code、message 是所有返回后果中必有的字段,而当须要返回数据时,则须要另一个字段 data 来示意。
所以首先定义一个 BaseResponse 来作为所有返回后果的基类;
而后定义一个通用返回后果类 CommonResponse,继承 BaseResponse,而且多了字段 data;
为了辨别胜利和失败返回后果,于是再定义一个 ErrorResponse;
最初还有一种常见的返回后果,即返回的数据带有分页信息,因为这种接口比拟常见,所以有必要独自定义一个返回后果类 QueryDataResponse,该类继承自 CommonResponse,只是把 data 字段的类型限度为 QueryDdata,QueryDdata 中定义了分页信息相应的字段,即 totalCount、pageNo、pageSize、records。
其中比拟罕用的只有 CommonResponse 和 QueryDataResponse,然而名字又贼鬼死长,何不定义 2 个名字超简略的类来代替呢?于是 R 和 QR 诞生了,当前返回后果的时候只需这样写:new R<>(data)、new QR<>(queryData)。
所有的返回后果类的定义这里就不贴出来了
验证对立异样解决
因为这一套对立异样解决能够说是通用的,所有能够设计成一个 common 包,当前每一个新我的项目 / 模块只需引入该包即可。所以为了验证,须要新建一个我的项目,并引入该 common 包。我的项目构造如下:
我的项目构造
当前只需这样引入即可
引入 common 包
次要代码
上面是用于验证的次要源码:
@Service
public class LicenceService extends ServiceImpl<LicenceMapper, Licence> {
@Autowired
private OrganizationClient organizationClient;
/**
* 查问{@link Licence} 详情
* @param licenceId
* @return
*/
public LicenceDTO queryDetail(Long licenceId) {Licence licence = this.getById(licenceId);
checkNotNull(licence);
OrganizationDTO org = ClientUtil.execute(() -> organizationClient.getOrganization(licence.getOrganizationId()));
return toLicenceDTO(licence, org);
}
/**
* 分页获取
* @param licenceParam 分页查问参数
* @return
*/
public QueryData<SimpleLicenceDTO> getLicences(LicenceParam licenceParam) {String licenceType = licenceParam.getLicenceType();
LicenceTypeEnum licenceTypeEnum = LicenceTypeEnum.parseOfNullable(licenceType);
// 断言, 非空
ResponseEnum.BAD_LICENCE_TYPE.assertNotNull(licenceTypeEnum);
LambdaQueryWrapper<Licence> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Licence::getLicenceType, licenceType);
IPage<Licence> page = this.page(new QueryPage<>(licenceParam), wrapper);
return new QueryData<>(page, this::toSimpleLicenceDTO);
}
/**
* 新增{@link Licence}
* @param request 申请体
* @return
*/
@Transactional(rollbackFor = Throwable.class)
public LicenceAddRespData addLicence(LicenceAddRequest request) {Licence licence = new Licence();
licence.setOrganizationId(request.getOrganizationId());
licence.setLicenceType(request.getLicenceType());
licence.setProductName(request.getProductName());
licence.setLicenceMax(request.getLicenceMax());
licence.setLicenceAllocated(request.getLicenceAllocated());
licence.setComment(request.getComment());
this.save(licence);
return new LicenceAddRespData(licence.getLicenceId());
}
/**
* entity -> simple dto
* @param licence {@link Licence} entity
* @return {@link SimpleLicenceDTO}
*/
private SimpleLicenceDTO toSimpleLicenceDTO(Licence licence) {// 省略}
/**
* entity -> dto
* @param licence {@link Licence} entity
* @param org {@link OrganizationDTO}
* @return {@link LicenceDTO}
*/
private LicenceDTO toLicenceDTO(Licence licence, OrganizationDTO org) {// 省略}
/**
* 校验 {@link Licence} 存在
* @param licence
*/
private void checkNotNull(Licence licence) {ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
}
}
ps: 这里应用的 DAO 框架是 mybatis-plus。
启动时,主动插入的数据为:
-- licence
INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (1, 1, 'user','CustomerPro', 100,5);
INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (2, 1, 'user','suitability-plus', 200,189);
INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (3, 2, 'user','HR-PowerSuite', 100,4);
INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (4, 2, 'core-prod','WildCat Application Gateway', 16,16);
-- organizations
INSERT INTO organization (id, name, contact_name, contact_email, contact_phone)
VALUES (1, 'customer-crm-co', 'Mark Balster', 'mark.balster@custcrmco.com', '823-555-1212');
INSERT INTO organization (id, name, contact_name, contact_email, contact_phone)
VALUES (2, 'HR-PowerSuite', 'Doug Drewry','doug.drewry@hr.com', '920-555-1212');
开始验证
捕捉自定义异样
获取不存在的 licence 详情:http://localhost:10000/licence/5。胜利响应的申请:licenceId=1
校验非空
捕捉 Licence not found 异样
Licence not found
2. 依据不存在的 licence type 获取 licence 列表:http://localhost:10000/licenc…。可选的 licence type 为:user、core-prod。
校验非空
捕捉 Bad licence type 异样
Bad licence type
捕捉进入 Controller 前的异样
拜访不存在的接口:http://localhost:10000/licenc…
捕捉 404 异样
http 办法不反对:http://localhost:10000/licence
PostMapping
捕捉 Request method not supported 异样
Request method not supported
3 校验异样 1:http://localhost:10000/licenc…
getLicences
LicenceParam
捕捉参数绑定校验异样
licence type cannot be empty
4. 校验异样 2:post 申请,这里应用 postman 模仿。
addLicence
LicenceAddRequest
申请 url 即后果
捕捉参数绑定校验异样
注:因为参数绑定校验异样的异样信息的获取形式与其它异样不一样,所以才把这 2 种状况的异样从 进入 Controller 前的异样 独自拆出来,上面是异样信息的收集逻辑:
异样信息的收集
捕捉未知异样
假如咱们当初轻易对 Licence 新增一个字段 test,但不批改数据库表构造,而后拜访:http://localhost:10000/licence/1。
减少 test 字段
捕捉数据库异样
Error querying database
小结
能够看到,测试的异样都可能被捕捉,而后以 code、message 的模式返回。每一个我的项目 / 模块,在定义业务异样的时候,只需定义一个枚举类,而后实现接口 BusinessExceptionAssert,最初为每一种业务异样定义对应的枚举实例即可,而不必定义许多异样类。应用的时候也很不便,用法相似断言。
扩大
在生产环境,若捕捉到 未知异样 或者 ServletException,因为都是一长串的异样信息,若间接展现给用户看,显得不够业余,于是,咱们能够这样做:当检测到以后环境是生产环境,那么间接返回“网络异样”。
生产环境返回“网络异样”
能够通过以下形式批改以后环境:
批改以后环境为生产环境
总结
应用 断言 和 枚举类 相结合的形式,再配合对立异样解决,根本大部分的异样都可能被捕捉。为什么说大部分异样,因为当引入 spring cloud security 后,还会有认证 / 受权异样,网关的服务降级异样、跨模块调用异样、近程调用第三方服务异样等,这些异样的捕捉形式与本文介绍的不太一样,不过限于篇幅,这里不做具体阐明,当前会有独自的文章介绍。
另外,当须要思考国际化的时候,捕捉异样后的异样信息个别不能间接返回,须要转换成对应的语言,不过本文已思考到了这个,获取音讯的时候曾经做了国际化映射,逻辑如下:
获取国际化音讯
最初总结,全局异样属于老成长谈的话题,心愿这次通过手机的我的项目对大家有点指导性的学习。大家依据理论状况自行批改。
也能够采纳以下的 jsonResult 对象的形式进行解决,也贴出来代码.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 没有登录
* @param request
* @param response
* @param e
* @return
*/
@ExceptionHandler(NoLoginException.class)
public Object noLoginExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
{log.error("[GlobalExceptionHandler][noLoginExceptionHandler] exception",e);
JsonResult jsonResult = new JsonResult();
jsonResult.setCode(JsonResultCode.NO_LOGIN);
jsonResult.setMessage("用户登录生效或者登录超时, 请先登录");
return jsonResult;
}
/**
* 业务异样
* @param request
* @param response
* @param e
* @return
*/
@ExceptionHandler(ServiceException.class)
public Object businessExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
{log.error("[GlobalExceptionHandler][businessExceptionHandler] exception",e);
JsonResult jsonResult = new JsonResult();
jsonResult.setCode(JsonResultCode.FAILURE);
jsonResult.setMessage("业务异样, 请分割管理员");
return jsonResult;
}
/**
* 全局异样解决
* @param request
* @param response
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public Object exceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
{log.error("[GlobalExceptionHandler][exceptionHandler] exception",e);
JsonResult jsonResult = new JsonResult();
jsonResult.setCode(JsonResultCode.FAILURE);
jsonResult.setMessage("零碎谬误, 请分割管理员");
return jsonResult;
}
}
起源:https://www.cnblogs.com/juren…