在当前rest-api的大行下,还有微服务的加持下。统一异常处理组件非常重要。那么怎么样来进行统一异常处理呢?首先异常按产生阶段分为进入controller前和service中;异常也会分类如自定义异常(UserNotFoundException\ParameterCheckException)和系统提供异常等;
指导原则:所以异常处理就是统一对不同阶段、不同异常进行处理。
目标:消灭95%以上的 try catch 代码块,以优雅的 Assert(断言) 方式来校验业务的异常情况,只关注业务逻辑,而不用花费大量精力写冗余的 try catch 代码块。
优雅抛出异常:用 Assert(断言) 替换 throw exception。使用断言进行异常抛出让我们写的代码有一种非一般丝滑的感觉。
陌生异常类:
controller上一层相关异常类:
NoHandlerFoundException:
HttpRequestMethodNotSupportedException:如后台需要get接收,前台axios调用使用post会出此异常
HttpMediaTypeNotSupportedException:
MissingPathVariableException:
MissingServletRequestParameterException:
TypeMismatchException:
HttpMessageNotReadableException:
HttpMessageNotWritableException:
BindException:
MethodArgumentNotValidException:
HttpMediaTypeNotAcceptableException:
ServletRequestBindingException:
ConversionNotSupportedException:
MissingServletRequestPartException:
AsyncRequestTimeoutException:
ClassCastException:http://localhost:8080/api/user/findUser/1 请求会出此异常
/**
* 验证异常
* @param id
* @return
*/
@GetMapping("findUser")
public R<User> findUser(Long id) {
return R.ok(userService.findUser(id));
}
自定义异常类:
UserNotFoundException:
ParameterCheckException:
ValidException:
统一异常处理相关:
package com.batsoft.common.exception;
import com.batsoft.common.core.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author: leo
* @data: 2022/4/4
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ParameterCheckException.class)
public R parameterCheckHandle(ParameterCheckException e) {
log.info("error:{}", e.getMessage());
return R.fail(e.getMessage());
}
@ExceptionHandler(UserNotFoundException.class)
public R userNotFoundHandle(UserNotFoundException e) {
log.info("error:{}", e.getMessage());
return R.fail(e.getMessage());
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R methodNotSupport(HttpRequestMethodNotSupportedException e) {
log.info("error:{}", e.getMessage());
return R.fail(e.getMessage());
}
}
package com.batsoft.common.exception;
/**
* @author: leo
* @data: 2022/4/4
*/
public class ParameterCheckException extends RuntimeException{
public ParameterCheckException(String message) {
super(message);
}
}
package com.batsoft.common.exception;
/**
* @author: leo
* @data: 2022/4/4
*/
public class UserNotFoundException extends RuntimeException{
public UserNotFoundException() {
super("user not found...");
}
public UserNotFoundException(String message) {
super(message);
}
}
业务模块中使用:
package com.batsoft.vip.service;
import com.batsoft.vip.entity.User;
/**
* @author: leo
* @data: 2022/3/12
*/
public interface UserService {
User findUserById(long id);
User create(long id);
User findUser(Long id);
}
package com.batsoft.vip.service.impl;
import cn.hutool.core.lang.Assert;
import com.batsoft.common.exception.ParameterCheckException;
import com.batsoft.common.exception.UserNotFoundException;
import com.batsoft.vip.entity.User;
import com.batsoft.vip.service.UserService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* @author: leo
* @data: 2022/3/12
*/
@Service
public class UserServiceImpl implements UserService {
@Override
public User findUserById(long id) {
return User.builder()
.id(id)
.age(30)
.name("yzj")
.build();
}
@Override
public User create(long id) {
User user = new User();
user.setId(id);
user.setAge(28);
user.setName("yzj");
return user;
}
@Override
public User findUser(Long id) {
Assert.isTrue(id>0,()->new ParameterCheckException("parameter check error..."));
User u1 = new User(1L, "u1", 1);
User u2 = new User(2L, "u2", 2);
User u3 = new User(3L, "u3", 3);
User u4 = new User(4L, "u4", 4);
User u5 = new User(5L, "u5", 5);
List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
Optional<User> userOptional = users.stream().filter(user -> user.getId().equals(id)).findFirst();
Assert.isTrue(userOptional.isPresent(),()->new UserNotFoundException("user not found..."));
return userOptional.get();
}
}
package com.batsoft.vip.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author: leo
* @data: 2022/3/12
*/
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private Long id;
private String name;
private Integer age;
}
package com.batsoft.vip.controller;
import com.batsoft.common.core.util.R;
import com.batsoft.common.core.util.ThreadPool;
import com.batsoft.mail.autoconfigure.MailService;
import com.batsoft.mail.autoconfigure.annotation.Log;
import com.batsoft.vip.entity.User;
import com.batsoft.vip.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @author: leo
* @data: 2022/3/12
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
@Autowired(required = false)
private MailService mailService;
@PostMapping("create")
public R<User> create(long id) {
User user = userService.create(id);
return R.ok(user);
}
@Log(content = "#id")
@GetMapping("find/{id}")
public R<User> find(@PathVariable long id) {
ThreadPool.exec(new Runnable() {
@Override
public void run() {
log.info("要查找的用户为:{}",id);
}
});
return R.ok(userService.findUserById(id));
}
@Log(content = "获取host")
@PostMapping("getHost")
public R<String> getHost(@RequestBody User user) {
return R.ok(mailService.getHost());
}
@GetMapping("getProtocol")
public R<String> getProtocol() {
return R.ok(mailService.getProtocol());
}
/**
* 验证异常
* @param id
* @return
*/
@GetMapping("findUser/{id}")
public R<User> findUser(@PathVariable Long id) {
return R.ok(userService.findUser(id));
}
}
小知识:如果接口要返回json对象,则需要在方法上添加@ResponseBody注解或在类上添加@RestControllerAdvice;同理如果要接收vue传来的json对象需要在方法参数添加@RequestBody注解;
4-5 logback-spring
<?xml version="1.0" encoding="UTF-8"?>
<!--
小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
<properties>
<log-path>/Users/batsoft</log-path>
</properties>
1. 其他模块加日志输出,直接copy本文件放在resources 目录即可
2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
-->
<configuration debug="false" scan="false">
<property name="log.path" value="logs"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
</appender>
<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<logger name="org.activiti.engine.impl.db" level="DEBUG">
<appender-ref ref="debug"/>
</logger>
<!--nacos 心跳 INFO 屏蔽-->
<logger name="com.alibaba.nacos" level="OFF">
<appender-ref ref="error"/>
</logger>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
</root>
</configuration>