什么是拦截器?
拦截器是Spring框架提供的核⼼功能之⼀, 主要⽤来拦截⽤⼾的请求, 在指定⽅法前后, 根据业务需要执
⾏预先设定的代码
也就是说, 允许开发⼈员提前预定义⼀些逻辑, 在⽤⼾的请求响应前后执⾏. 也可以在⽤⼾请求前阻止其执行
流程图大概如下
拦截器的使⽤步骤分为两步:
- 定义拦截器;
- 注册配置拦截器
⾃定义拦截器:实现HandlerInterceptor接⼝,并重写其所有⽅法
java
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");
}
}
preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true: 继续执⾏后续操作; 返回false: 中断后续操作.
postHandle()⽅法:⽬标⽅法执⾏后执⾏
afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏
注册配置拦截器:
实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求)
}
}
启动服务 发出访问请求然后观察日志
可以看到preHandle ⽅法执⾏之后就放⾏了, 开始执⾏⽬标⽅法, ⽬标⽅法执⾏完成之后执⾏
postHandle和afterCompletion⽅法
我们把拦截器中preHandle⽅法的返回值改为false, 再观察运⾏结果
我们可以看到拦截器拦截了请求没有进行响应
拦截器详解
拦截器的⼊⻔程序完成之后,接下来我们来介绍拦截器的使⽤细节。拦截器的使⽤细节我们主要介绍
两个部分:
- 拦截器的拦截路径配置
- 拦截器实现原理
拦截路径
拦截路径是指我们定义的这个拦截器, 对哪些请求生效
我们在注册配置拦截器的时候, 通过 addPathPatterns() ⽅法指定要拦截哪些请求. 也可以通过
excludePathPatterns() 指定不拦截哪些请求
上面的代码中配置的是(/**) 表示拦截所有的请求
比如⽤户登录校验, 我们希望可以对除了登录之外所有的路径⽣效
例如:
java
package com.example.book.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求)
.excludePathPatterns("/user/login");//设置拦截器拦截的请求路径(/** 表⽰拦截所有请求)
}
}
除了可以设置/**拦截所有路径资源外,还有一些常见拦截路径
拦截路径 | 含义 | 举例 |
---|---|---|
/* | ⼀级路径 | 能匹配/user,/book,/login,不能匹配 /user/login |
/** | 任意级路径 | 能匹配/user,/user/login,/user/reg |
/book/* | /book下的⼀级路径 | 能匹配/book/addBook,不能匹配/book/addBook/1,/book |
/book/** | /book下的任意级路径 | 能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login |
以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件, JS 和 CSS 等⽂件)
- 添加拦截器后,执行Controller的方法之前,请求会被拦截器拦住,执行preHandle() 方法,这个⽅法需要返回⼀个布尔类型的值. 如果返回true, 就表⽰放⾏本次操作, 继续访问controller中的方法. 如果返回false,则不会放⾏(controller中的方法也不会执行).
- controller当中的⽅法执⾏完毕后,再回过来执行 postHandle() 这个⽅法以及
afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据
登录校验
定义拦截器:
java
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session!=null&&session.getAttribute(Constants.SESSION_USER_KEY)!=null){
return true;
}
response.setStatus(401);
return false;
}
}
:
未经过认证. 指⽰⾝份验证是必需的, 没有提供⾝份验证或⾝份验证失败. 如果请求已经包含授权凭据,那么401状态码表⽰不接受这些凭据
注册配置拦截器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
private List<String> excludePaths = Arrays.asList(
"user/login",
"/**/*.js",
"/**/*.css",
"/**/*.png",
"/**/*.html"
);
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求)
.excludePathPatterns(excludePaths);//设置拦截器拦截的请求路径(/** 表⽰拦截所有请求)
}
}
我们发出图书列表请求就会发现
返回的状态码是401
这时我们登录 返回的就是200了
然后在获取图书列表
DispatcherServlet 源码、初始化分析
当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet, 它来控制程序的执⾏顺序
所有请求都会先进到DispatcherServlet,执⾏doDispatch 调度⽅法. 如果有拦截器, 会先执⾏拦截器
preHandle() ⽅法的代码, 如果 preHandle() 返回true, 继续访问controller中的⽅法.
controller 当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 和 afterCompletion()
,返回给DispatcherServlet,最终给浏览器响应数据
DispatcherServlet继承了FrameworkServlet
而FrameworkServlet继承了HttpServletBean类实现了ApplicationContextAware的接口
生命周期
init 初始化
service
destroy
然后它的初始化方法是写在HttpServletBean里面的
跳转之后发现是空的
这是因为在子类FrameworkServlet里面对它进行了重写
我们可以看到除了打印日志之外核心代码是
java
try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
}
我们可以看到webApplicationContext 有点眼熟跟启动类里面的SpringApplication类似ApplicationContext(一个spring的运行容器)
我们点进initWebApplicationContext
我们可以从源码看出
主要定义了一个
WebApplicationContext的wac
若它为空这去寻找这个wac
还是为空则创建这个wac
最后刷新wac
这时我们点进
onRefresh
继续点击
就到了DispatcherServlet类下的onRefresh
就看到了它的核心内容
进行了很多的初始化
里面的内容分别是
- 初始化⽂件上传解析器MultipartResolver:从应⽤上下⽂中获取名称为multipartResolver的
Bean,如果没有名为multipartResolver的Bean,则没有提供上传⽂件的解析器- 初始化区域解析器LocaleResolver:从应⽤上下⽂中获取名称为localeResolver的Bean,如果没
有这个Bean,则默认使⽤AcceptHeaderLocaleResolver作为区域解析器- 初始化主题解析器ThemeResolver:从应⽤上下⽂中获取名称为themeResolver的Bean,如果
没有这个Bean,则默认使⽤FixedThemeResolver作为主题解析器- 初始化处理器映射器HandlerMappings:处理器映射器作⽤,1)通过处理器映射器找到对应的
处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx
⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取
到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器- 初始化处理器适配器HandlerAdapter:作⽤是通过调⽤具体的⽅法来处理具体的请求;如果在
ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的
HandlerAdapter,并进⾏排序;如果在ApplicationContext中没有发现处理器适配器,则默认
SimpleControllerHandlerAdapter作为处理器适配器- 初始化异常处理器解析器HandlerExceptionResolver:如果在ApplicationContext发现有
handlerExceptionResolver,则从ApplicationContext中获取到所有的
HandlerExceptionResolver,并进⾏排序;如果在ApplicationContext中没有发现异常处理器解
析器,则不设置异常处理器- 初始化RequestToViewNameTranslator:其作⽤是从Request中获取viewName,从
ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使⽤
DefaultRequestToViewNameTranslator- 初始化视图解析器ViewResolvers:先从ApplicationContext中获取名为viewResolver的Bean,
如果没有,则默认InternalResourceViewResolver作为视图解析器- 初始化FlashMapManager:其作⽤是⽤于检索和保存FlashMap(保存从⼀个URL重定向到另⼀
个URL时的参数信息),从ApplicationContext发现有flashMapManager的Bean,如果没有,则
默认使⽤DefaultFlashMapManager
在initStrategies()中进⾏spring9⼤组件的初始化,
我们点开this.initMultipartResolver(context);查看
其中关键的是
java
this.multipartResolver = (MultipartResolver)context.getBean("multipartResolver", MultipartResolver.class);
这部分是从应用上下文(context
)中获取一个名为 "multipartResolver"
的 bean,并将其强制转换为 MultipartResolver
类型。
在一个 Web 应用中,如果需要处理文件上传等与多部分数据相关的操作,可能会配置一个 MultipartResolver
的 bean 来处理相关逻辑。通过这段代码,就能够获取到这个已经配置好的 MultipartResolver
对象,并在后续的代码中使用它来执行相应的文件处理任务
以上就是初始化的大概的过程
处理请求
DispatcherServlet 接收到请求后, 执⾏doDispatch 调度⽅法, 再将请求转给Controller.
我们来看doDispatch ⽅法的具体实现
里面的具体核心代码是
java
mappedHandler = this.getHandler(processedRequest);
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
上面的初始化的定义就在这里用到
doDispatch具体代码
java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//1. 获取执⾏链
//遍历所有的 HandlerMapping 找到与请求对应的Handler
mappedHandler = this.getHandler(processedRequest);
//2. 获取适配器
//遍历所有的 HandlerAdapter,找到可以处理该 Handler 的HandlerAdapter
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
//3. 执⾏拦截器preHandle⽅法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4. 执⾏⽬标⽅法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//5. 执⾏拦截器postHandle⽅法
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//6. 处理视图, 处理之后执⾏拦截器afterCompletion⽅法
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
//7. 执⾏拦截器afterCompletion⽅法
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
HandlerAdapter 在 Spring MVC 中使⽤了适配器模式, 下⾯详细再介绍
适配器模式, 也叫包装器模式. 简单来说就是⽬标类不能直接使⽤, 通过⼀个新类进⾏包装⼀下, 适配
调⽤⽅使⽤.
把两个不兼容的接⼝通过⼀定的⽅式使之兼容.
HandlerAdapter 主要⽤于⽀持不同类型的处理器(如 Controller、HttpRequestHandler 或者
Servlet 等),让它们能够适配统⼀的请求处理流程。这样,Spring MVC 可以通过⼀个统⼀的接⼝
来处理来⾃各种处理器的请求
我们点击applyPreHandle跳转就会发现
这个方法执行了拦截器,这个拦截器执行了preHandle方法
如果说拦截器返回的是一个false就直接执行,triggerAfterCompletion
triggerAfterCompletion里又执行了afterCompletion方法,直接返回false,代表结束了,后面的程序不执行了,就被拦截了,这正是我们自定义的拦截器里面继承HandlerInterceptor类的方法
拦截器返回false然后if语句为ture直接return;不执行任何业务了,也就代表被拦截了
如果拦截器返回的是ture呢?就代表放行然后程序就直接执行各种响应的请求,如getListBook
如果拦截器返回true, 整个发放就返回true, 继续执⾏后续逻辑处理 如果拦截器返回fasle, 则中断后续操作
我们也可以通过debug调试查看源码
就可以看到在源码中的执行流程
适配器模式
HandlerAdapter 在 Spring MVC 中使⽤了适配器模式
什么是适配器模式?
适配器模式, 也叫包装器模式. 将⼀个类的接⼝,转换成客⼾期望的另⼀个接⼝, 适配器让原本接⼝不兼容的类可以合作⽆间
简单来说目标类不能直接使用,通过一个新类进行包装一下,适配调用方法使用,把两个不兼容的接口通过一定方式使其兼容
例如:
java
// 目标接口,定义了客户端期望的接口
interface Target {
void request();
}
// 被适配的类,具有与目标接口不兼容的接口
class Adaptee {
public void specificRequest() {
System.out.println("被适配类的特定方法被调用");
}
}
// 适配器类,将 Adaptee 的接口适配为 Target 接口
class Adapter extends Adaptee implements Target {
@Override
public void request() {
// 调用被适配类的方法进行适配
specificRequest();
}
}
public class AdapterPatternExample {
public static void main(String[] args) {
Target target = new Adapter();
target.request();
}
}
Target 是客户端期望的接口。
Adaptee 是具有不兼容接口的类。
Adapter 类继承自 Adaptee 并实现了 Target 接口,在 request 方法中调用了 Adaptee 的 specificRequest 方法,完成了接口的适配。
在 main 方法中,客户端通过 Target 接口来使用 Adapter 对象,实现了对不兼容接口的适配和使用。
适配器模式⻆⾊
• Target: ⽬标接⼝ (可以是抽象类或接⼝), 客⼾希望直接⽤的接⼝
• Adaptee: 适配者, 但是与Target不兼容
• Adapter: 适配器类, 此模式的核⼼. 通过继承或者引⽤适配者的对象, 把适配者转为⽬标接⼝
• client: 需要使⽤适配器的对象
slf4j 就使⽤了适配器模式, slf4j提供了⼀系列打印⽇志的api, 底层调⽤的是log4j 或者logback来打⽇志, 我们作为调⽤者, 只需要调⽤slf4j的api就⾏了
例如:
java
/**
* log4j 接⼝
*/
public class Log4j {
void log4jLog(String message){
System.out.println("Log4j打印:"+message);
}
}
/**
* slf4j接⼝
* */
public interface Slf4jApi {
void log(String message);
}
/**
* slf4j和log4j适配器
*/
public class Slf4jLog4JAdapter implements Slf4jApi{
private Log4j log4j;
public Slf4jLog4JAdapter(Log4j log4j){
this.log4j = log4j;
}
@Override
public void log(String message) {
log4j.log4jLog(message);
}
}
/**
* 客⼾端调⽤
*/
public class Slf4jDemo {
public static void main(String[] args) {
Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
slf4jApi.log("使⽤slf4j打印⽇志");
}
}
可以看出, 我们不需要改变log4j的api,只需要通过适配器转换下, 就可以更换⽇志框架, 保障系统的平稳运⾏.
统⼀数据返回格式
统一数据返回格式是指在项目中规定一种固定的数据结构,用于所有接口返回数据的格式。这样可以使前端人员能够以一致的方式获取和处理后端返回的数据,增强了接口的规范性和可维护性。
统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现
@ControllerAdvice 表⽰控制器通知类
添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接⼝, 并在类上添加
@ControllerAdvice 注解
我们创建一个类进行统一管理
ResponseAdvice去实现ResponseBodyAdvice 接口
java
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//在返回之前要做的事情
return Result.success(body);
}
}
supports⽅法 : 判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏. 通过该⽅法可以选择哪些类或哪些⽅法的response要进⾏处理, 其他的不进⾏处理.
> beforeBodyWrite⽅法: 对response⽅法进⾏具体操作处理
我们接下来测试一下
发出请求:
http://localhost:8080/book/queryBookById?bookId=9
我们可以看到对返回的结果进行了一个包装
测试请求:
http://localhost:8080/book/getListByPage
我们看到它的结果封装了两次
这是因为我们 对原本返回的结果就进行了封装
测试:
http://localhost:8080/book/updateBook?id=96\&bookName=人性的弱点(全译本)15
我们发现又报了新的错误
但是我们在数据库中可以发现它其实是进行了修改的
我们到控制台可以发现他说这个结果不能和String类型进行匹配
可以看到update返回的是String类型的结果
为了测试还有没有别的问题我们写一个TestController来测试其他问题
java
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public Boolean t1(){
return true;
}
@RequestMapping("/t2")
public Integer t2(){
return 1233;
}
@RequestMapping("/t3")
public String t3(){
return "hello";
}
@RequestMapping("/t4")
public BookInfo t4(){
return new BookInfo();
}
@RequestMapping("/t5")
public Result t5(){
return Result.success("success");
}
}
我们对其进行测试
http://localhost:8080/test/t1正确返回
http://localhost:8080/test/t2正确返回
http://localhost:8080/test/t3返回了一个403并和之前报的是一个错误
http://localhost:8080/test/t4正确返回
http://localhost:8080/test/t5正确返回但是由于本身返回的就是Result结果所以进行了嵌套
以上我们就遇到了两个问题
问题1:
请求返回类型为Result时,不需要再进行处理了
问题2:
返回结果为String时,不能正确进行处理
面对问题一我们可以在beforeBodyWrite方法加入
java
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//在返回之前要做的事情
//如果返回的是Result类型就不再进行任何处理
if(body instanceof Result){
return body;
}
return Result.success(body);
}
表示如果返回的是Result类型就不再进行任何处理
面对问题二
我们对其进行json包装
java
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows//加上这个注解可以处理objectMapper.writeValueAsString的异常和throw一样
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//在返回之前要做的事情
//如果返回的是Result类型就不再进行任何处理
if(body instanceof Result){
return body;
}
if(body instanceof String){
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
继续测试就会发现正常了
String类型
Result类型
我们通过查看源码发现出错的原因
就在RequestMappingHandlerAdapter(RequestMapping的适配器)
SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为
ByteArrayHttpMessageConverter ,
StringHttpMessageConverter , SourceHttpMessageConverter ,
SourceHttpMessageConverter , AllEncompassingFormHttpMessageConverter )
它在做初始化的时候
如果返回的结果是String类型就会用到
StringHttpMessageConverter进行初始化
在AbstractMessageConverterMethodProcessor处理器里有一个这个方法writeWithMessageConverters下面有一个
Body = this.getAdvice()这个就是获取上面创建的
ResponseAdvice里面的beforeBodyWrite
注意!此时我们的body已经从String类型变成了一个Result类型
然后就会执行到下面
点击write查看源码
继续点击 注意这里的父类的addDefaultHeaders的第二个参数是泛型里面装着的是Result类型的body
这里又加了一些默认的headers 继续点击
我们应该查看的是子类StringHttpMessageConverter 实现的addDefaultHeaders
我们之前已经把String类型的body变成课Result类型但是这里的子类StringHttpMessageConverter 这里的body却是String类型的所以这里就会报错
统⼀异常处理
统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,
@ControllerAdvice 表⽰控制器通知类, @ExceptionHandler 是异常处理器,两个结合表
示当出现异常的时候执⾏某个通知,也就是执⾏某个方法事件
例如:
java
@Slf4j
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
log.error("发生异常,e:{}",e);
return Result.fail("内部错误");
}
}
类名, ⽅法名和返回值可以⾃定义, 重要的是注解
接⼝返回为数据时, 需要加 @ResponseBody 注解
我们自己手动制造一个错误
java
@RequestMapping("/t1")
public Boolean t1(){
int i = 10/0;
return true;
}
发出请求
可以看到捕获了异常
我们可以针对不同的异常对其设置
java
@Slf4j
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
log.error("发生异常,e:{}",e);
return Result.fail("内部错误");
}
@ExceptionHandler
public Object handler(NullPointerException e) {
log.error("发生异常,e:{}",e);
return Result.fail("NullPointerException异常");
} @ExceptionHandler
public Object handler(ArithmeticException e) {
log.error("发生异常,e:{}",e);
return Result.fail("ArithmeticException异常");
}
}