基于spring的异常一站式解决方案
https://segmentfault.com/a/1190000006749441#articleHeader4
https://lrwinx.github.io/2016/04/28/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%BE%E8%AE%A1java%E5%BC%82%E5%B8%B8/
1,异常分类
1,继承RuntimeException子类,比如nullPointException,称非受检异常,不要求写try/catch语句
2,其他异常都是,比如数据库连接异常,称受检异常,要求显示写try/catch语句
我们如何选择我们的异常种类,就一句,如果你的这个服务的编写者,你希望服务者显式调用try/catch语句,就抛出受检异常。
但异常一定要接受的,不管是受检异常还是非受检异常。当你的非受检异常没有接受,就会一直往上面抛出,最后都没有人接受,
如果应用是单线程的,整个应用就会停掉,在tomcat中不会停是因为tomcat有让这个应用恢复过来的功能。
2,入参约束
maven导入,@vaild是jsr303的标准,hibernate-validator是它的实现,在方法上的注解@validated是spring-context的注解
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.13.Final</version> </dependency>
之前一直在考虑,入参约束在controller做还是service做,最后发现service一定要做,因为service会互相调用,
在其他service调用是难免会有不正确的入参,但是我们仍然可以在controller做,多一重保险
controller
@PostMapping("/insertAccessories") public SuccessResponse<Object> insertAccessories(@RequestBody @Valid DataRequest<AccessoriesDO> dataRequest) { accessoriesService.insertAccessories(dataRequest.getBody()); return new SuccessResponse<Object>("0", "成功", null); }
service (上面有@validated
@Validated public interface AccessoriesService { /** * @author : kooing * @Date : 2018/4/22 13:24 * @Desription : 增加辅料 * @return : */ public void insertAccessories(@Valid AccessoriesDO accessoriesDO);
在实体里面或DTO加上你的约束
@NotEmpty(message="姓名不能为空") private String memberUsername; @NotEmpty(message="密码不能为空") private String memberPassword;
3,异常抛出和捕获
所有非检查异常的基类
@Data @NoArgsConstructor @AllArgsConstructor public abstract class BaseServiceException extends RuntimeException { private String code; private String message; }
后面继承他的要重写有参构造方法,可以一个模块一个异常类,也可以细分一点一个异常一个异常类
@Data public class AccessoriesException extends BaseServiceException { public AccessoriesException(String errorCode, String errorMsg) { super(errorCode, errorMsg); } }
如何捕获异常,在controller用@ExceptionHandler注解能捕获,但代码会冗余在一起,我是放在多个全局异常捕获,但是有个全局异常捕获会
捕获基类异常的,如果spring配到是这个异常(或者他的父类,过程像catch一样)就不会继续需要更加切合的异常了,所有这个全局异常捕获
有个优先级问题,最后我的方案是,最后业务的异常捕获不使用全局捕获,写在另外controller里面,再由业务的controller继承他,(勉强实现
了代码分离和优先级的问题)
@RestController @Slf4j public class AccessoriesExceptionHandler { @ExceptionHandler(AccessoriesException.class) public Object BaseServiceException(HttpServletRequest req, BaseServiceException e) { log.error("---AccessoriesException Handler---Host {} invokes url {} CODE:{} MESSAGE: {}", req.getRemoteHost() , req.getRequestURL() , e.getCode() , e.getMessage()); ExceptionResponse exceptionResponse = new ExceptionResponse(); exceptionResponse.setCode(e.getCode()); exceptionResponse.setMessage(e.getMessage()); return exceptionResponse; } }
@Slf4j @RestController @RequestMapping("accessoriesRecord") public class AccessoriesRecordController extends AccessoriesExceptionHandler{
全局异常捕获类,下面我分别捕获了404异常,controller入参异常,service入参异常,业务异常(没有对应的exceptionHandler),和Exception(避免应用关闭,但调试的时候注释掉,方便看报错)
@RestController @ControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(value = NoHandlerFoundException.class) public Object noHandlerFoundException(HttpServletRequest req, Exception e) throws Exception { log.error("---404 Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage()); return new ExceptionResponse(GlobalCode.CODE_404, GlobalCode.MSG_404); } @ExceptionHandler(value = MethodArgumentNotValidException.class) public Object bindException(HttpServletRequest req, Exception e) throws Exception { log.error("---controller---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage()); return new ExceptionResponse(GlobalCode.CODE_CONTROLLER, GlobalCode.MSG_CONTROLLER); } @ExceptionHandler(value = ConstraintViolationException.class) public Object methodArgumentNotValidException(HttpServletRequest req, Exception e) throws Exception { log.error("---service---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage()); return new ExceptionResponse(GlobalCode.CODE_SERVICE, GlobalCode.MSG_SERVICE); } @ExceptionHandler(BaseServiceException.class) public Object BaseServiceException(HttpServletRequest req, BaseServiceException e) { log.error("---service Exception Handler---Host {} invokes url {} CODE:{} MESSAGE: {}", req.getRemoteHost() , req.getRequestURL() , e.getCode() , e.getMessage()); ExceptionResponse exceptionResponse = new ExceptionResponse(); exceptionResponse.setCode(e.getCode()); exceptionResponse.setMessage(e.getMessage()); return exceptionResponse; } // @ExceptionHandler(value = Exception.class) // public Object defaultErrorHandler(HttpServletRequest req, Exception e) { // log.error("---DefaultException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage()); // return new ExceptionResponse(GlobalCode.CODE_UNKNOWN, GlobalCode.MSG_UNKNOWN); // } }
异常错误码,设计了两个string类的code和mssage,用总的异常码,和模块异常码
public class ErrorCodeBase { public static final long Global = 10000L; public static final long ACCESSION = 20000L; public static final long MATERIAL = 30000L; public static final long MEMBER = 40000L; public static final long PACKGE_IT = 50000L; public static final long PRODUCT = 60000L; }
public class GlobalCode { public static final String CODE_CONTROLLER = String.valueOf(ErrorCodeBase.Global + 1L); public static final String MSG_CONTROLLER = "控制层入参错误"; public static final String CODE_SERVICE = String.valueOf(ErrorCodeBase.Global + 2L); public static final String MSG_SERVICE = "服务入参错误"; public static final String CODE_404 = String.valueOf(ErrorCodeBase.Global + 3L); public static final String MSG_404 = "没有这个api接口"; public static final String CODE_UNKNOWN = String.valueOf(ErrorCodeBase.Global + 4L); public static final String MSG_UNKNOWN = "服务器未知错误"; }
public class ResultCode { public static final String CODE_NUMBER = String.valueOf(ErrorCodeBase.ACCESSION + 1L); public static final String MSG_NUMBER = "数量不够"; public static final String CODE_RECORD = String.valueOf(ErrorCodeBase.ACCESSION + 2L); public static final String MSG_RECORD = "没有这个辅料出入库纪录"; }
最后补上一个抛出异常的方法和一个服务的文件目录结构
if(accessoriesRecordDOTemp == null){ throw new AccessoriesException(ResultCode.CODE_RECORD, ResultCode.CODE_RECORD); }