代码
编写实体类:
@Data
public class Item {
private Integer id;
private String name;
private Long price;
}
编写业务:
service:
@Service
public class ItemService {
public Item saveItem(Item item){
int id = new Random().nextInt(100);
item.setId(id);
return item;
}
}
controller:
@RestController
public class ItemController {
@Autowired
private ItemService itemService;
@PostMapping("item")
public ResponseEntity<Item> saveItem(Item item){
// 如果价格为空,则抛出异常,返回400状态码,请求参数有误
if(item.getPrice() == null){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
Item result = itemService.saveItem(item);
return ResponseEntity.status(HttpStatus.CREATED).body(result);
}
}
统一异常处理
最基础的形态
@ControllerAdvice
@Slf4j
public class BasicExceptionAdvice {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleException(RuntimeException e) {
// 我们暂定返回状态码为400, 然后从异常中获取友好提示信息
return ResponseEntity.status(400).body(e.getMessage());
}
}
解读:
@ControllerAdvice:默认情况下,会拦截所有加了@Controller的类
@ExceptionHandler(RuntimeException.class):作用在方法上,声明要处理的异常类型,可以有多个,这里指定的是RuntimeException。被声明的方法可以看做是一个SpringMVC的Handler:
这里等于从新定义了返回结果,我们可以随意指定想要的返回类型。此处使用了String
要想在商品服务中扫描到这个advice,需要在service中引入lcommon依赖:
<dependency>
<groupId>com.zyf.common</groupId>
<artifactId>common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
刚才的处理看似完美,但是仔细想想,我们在通用的异常处理中,把返回状态码写死为400了:
这样显然不太合理。
但是,仅凭用户抛出的异常,我们根本无法判断到底该返回怎样的状态码,可能是参数有误、数据库异常、没有权限,等等各种情况。
因此,用户抛出异常时,就必须传递两个内容:
但是RuntimeException是无法接受状态码的,只能接受异常的消息,所以我们需要做两件事情:
我们定义一个枚举,用于封装异常状态码和异常信息:
@Getter
public enum ExceptionEnum {
PRICE_CANNOT_BE_NULL(400, "价格不能为空!");
;
private int status;
private String message;
ExceptionEnum(int status, String message) {
this.status = status;
this.message = message;
}
}
然后自定义异常,来获取枚举对象。
自定义异常类:
代码:
@Getter
public class LyException extends RuntimeException {
private int status;
public LyException(ExceptionEnum em) {
super(em.getMessage());
this.status = em.getStatus();
}
public LyException(ExceptionEnum em, Throwable cause) {
super(em.getMessage(), cause);
this.status = em.getStatus();
}
}
修改controller代码:
@PostMapping("item")
public ResponseEntity<Item> saveItem(Item item){
if(item.getPrice() == null){
throw new LyException(ExceptionEnum.PRICE_CANNOT_BE_NULL);
}
Item result = itemService.saveItem(item);
return ResponseEntity.ok(result);
}
在刚刚编写的BasicExceptionAdvice中,我们返回的结果是String,为了让异常结果更友好,我们返回定义异常结果对象:
@Getter
public class ExceptionResult {
private int status;
private String message;
private String timestamp;
public ExceptionResult(LyException e) {
this.status = e.getStatus();
this.message = e.getMessage();
this.timestamp = DateTime.now().toString("yyyy-MM-dd HH:mm:ss");
}
}
这里使用了日期工具类:JodaTime,我们要引入依赖在ly-common中:
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
修改异常处理逻辑:
@ControllerAdvice
@Slf4j
public class BasicExceptionAdvice {
@ExceptionHandler(LyException.class)
public ResponseEntity<ExceptionResult> handleLyException(LyException e) {
return ResponseEntity.status(e.getStatus()).body(new ExceptionResult(e));
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException e) {
return ResponseEntity.status(500).body(e.getMessage());
}
}
再次测试:
以后,我们无论controller还是service层的业务处理,出现异常情况,都抛出自定义异常,方便统一处理。