SpringBoot拦截异常

如果你想拦截异常,你可以在你的Springboot应用程序中定义一个统一的异常处理器来实现。

例如,你可以使用@ControllerAdvice注解来定义一个异常处理器,然后使用@ExceptionHandler注解来指定拦截哪些异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

/**
* 全局异常处理器
*
* @author wxl
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

/**
* 基础异常
*/
@ExceptionHandler(BaseException.class)
public AjaxResult baseException(BaseException e) {
return AjaxResult.error(e.getDefaultMessage());
}

/**
* 业务异常
*/
@ExceptionHandler(CustomException.class)
public AjaxResult businessException(CustomException e) {
if (StringUtils.isNull(e.getCode())) {
return AjaxResult.error(e.getMessage());
}
return AjaxResult.error(e.getCode(), e.getMessage());
}

@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e) {
log.error(e.getMessage(), e);
return AjaxResult.error(e.getMessage());
}

/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e) {
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}

/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}

/**
* 权限异常
*/
@ExceptionHandler(PreAuthorizeException.class)
public AjaxResult preAuthorizeException() {
return AjaxResult.error("没有权限,请联系管理员授权");
}

/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult demoModeException() {
return AjaxResult.error("演示模式,不允许操作");
}

@ExceptionHandler(DuplicateKeyException.class)
public AjaxResult duplicateKeyHandler() {
return AjaxResult.error("数据已存在,请勿重复提交");
}

@ExceptionHandler(DataAccessException.class)
public AjaxResult dataAccessException(DataAccessException e) {
log.error("JDBC异常:" + e.getMessage(), e);
return AjaxResult.error("系统错误,请联系管理员!");
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public AjaxResult httpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("Http请求参数转换异常:" + e.getMessage(), e);
return AjaxResult.error("系统错误,请联系管理员!");
}
}

在这个例子中,我们定义了一个名为GlobalExceptionHandler的异常处理器,并使用@ExceptionHandler注解来指定要拦截的异常类型。这样,当应用程序中发生Exception类型的异常时,该异常处理器就会被调用。

在异常处理器的方法中,你可以自定义如何处理这些异常。例如,你可以将异常信息记录到日志中,或者向用户展示一个错误消息,或者返回一个错误页面等。

总之,使用异常处理器是一种比较简单和优雅的方式来拦截异常,并对其进行统一处理。

@ControllerAdvice和@RestControllerAdvice的区别

@ControllerAdvice注解和@RestControllerAdvice注解都可以用于定义一个异常处理器,但它们之间有一些区别:

@ControllerAdvice注解可以用于处理所有带有@Controller注解的控制器中的异常。这种类型的异常处理器通常用于处理Web应用程序中的异常,并向用户展示一个错误页面。
@RestControllerAdvice注解可以用于处理所有带有@RestController注解的控制器中的异常。这种类型的异常处理器通常用于处理RESTful接口中的异常,并将错误信息以JSON格式返回给客户端。
总之,@ControllerAdvice注解和@RestControllerAdvice注解都可以用于定义一个异常处理器,但它们的使用场景不同,应根据实际情况来选择合适的注解。

如果你在微服务应用程序中定义了一个带有@ControllerAdvice注解的异常处理器,但它并没有被调用,那么可能是因为以下几种原因

  1. 异常处理器类上没有使用@ControllerAdvice注解:如果你的异常处理器类上没有使用@ControllerAdvice注解,那么它就不会被识别为一个异常处理器,因此也不会被调用。
  2. 异常处理方法上没有使用@ExceptionHandler注解:如果你的异常处理方法上没有使用@ExceptionHandler注解,那么它就不会被识别为一个异常处理方法,因此也不会被调用。
  3. 异常处理方法没有指定要处理的异常类型:如果你的异常处理方法没有指定要处理的异常类型,那么它就不会被调用。例如,如果你的异常处理方法只处理IOException类型的异常,那么如果应用程序中发生的异常不是IOException类型,那么这个方法就不会被调用。

总之,如果你定义的异常处理器没有被调用,那么最可能的原因就是你的异常处理器类没有使用@ControllerAdvice

日志异步线程池入库

线程池配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 异步线程池配置
* 注1:不指定此线程池时,@Async调用默认线程池,应注意默认线程池易引发OOM
*/

@Configuration
public class AsyncConfig {

private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);

@Bean("asyncTaskExecutor")
public AsyncTaskExecutor asyncTaskExecutor() {

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数
executor.setCorePoolSize(10);
//最大线程数
executor.setMaxPoolSize(50);
//配置队列大小
executor.setQueueCapacity(500);
//活跃时间
executor.setKeepAliveSeconds(60);
//配置线程池前缀
executor.setThreadNamePrefix("async-task-thread-pool-");
//核心线程闲置超时则销毁
executor.setAllowCoreThreadTimeOut(true);
//子线程装饰器,用于将主线程数据传递给子线程
executor.setTaskDecorator(new ContextCopyingDecorator());
//拒绝策略:当线程池达到MaxPoolSize,不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}

static class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {

log.info("父线程:{}", Thread.currentThread());
//将主线程请求头内容赋值 threadLocal中的数据拿出并清除
AsyncSecurityUtils.setThreadLocal();
ThreadContextHolder parentContextHolder = ThreadContextHolder.getInstance();
Map<String, String> cloneMap = cloneMap(parentContextHolder.getContext());
parentContextHolder.clear();
return () -> {
ThreadContextHolder childContextHolder = ThreadContextHolder.getInstance();
try {
log.info("子线程:{}", Thread.currentThread());
childContextHolder.setContext(cloneMap);
runnable.run();
} finally {
//清除线程池线程threadLocal
childContextHolder.clear();
}
};
}
}

private static Map<String, String> cloneMap(Map<String, String> map) {
if (map != null) {
return new HashMap<>(map);
} else {
return null;
}
}
}

线程绑定信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class ThreadContextHolder {

public Logger log = LoggerFactory.getLogger(ThreadContextHolder.class);

private final ThreadLocal<Map<String, String>> threadLocal;

private ThreadContextHolder() {
this.threadLocal = new ThreadLocal<>();
}

/**
* 获取实例
*/
public static ThreadContextHolder getInstance() {
return SingletonHolder.S_INSTANCE;
}

/**
* 静态MJC类单例模式
* 单例初使化
*/
private static class SingletonHolder {
private static final ThreadContextHolder S_INSTANCE = new ThreadContextHolder();
}

/**
* 当前线程放置共享信息(用完执行clear(),防止内存泄漏)
*
* @param map 共享信息
*/
public void setContext(Map<String, String> map) {
threadLocal.set(map);
}

/**
* 获取当前线程共享信息
*
* @return 共享信息
*/
public Map<String, String> getContext() {
return threadLocal.get();
}

/**
* 清空上下文:(防止内存泄漏)
*/
public void clear() {
threadLocal.remove();
}
}

异步调用日志服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class AsyncLogService
{
@Autowired
private RemoteLogService remoteLogService;

/**
* 保存系统日志记录
*/
@Async("asyncTaskExecutor")
public void saveSysLog(SysOperLog sysOperLog)
{
//todo 设置新线程池
remoteLogService.saveLog(sysOperLog);
}
}

Logback输出日志到自定义MySQL数据库

在你的项目中添加Logback的依赖,并在classpath下创建一个名为”logback.xml”的配置文件,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://localhost:3306/logging?useSSL=false</url>
<user>username</user>
<password>password</password>
</connectionSource>
</appender>
<root level="info">
<appender-ref ref="DB" />
</root>
</configuration>