SpringBoot 统⼀功能处理

什么是拦截器?

拦截器是Spring框架提供的核⼼功能之⼀, 主要⽤来拦截⽤⼾的请求, 在指定⽅法前后, 根据业务需要执

⾏预先设定的代码

也就是说, 允许开发⼈员提前预定义⼀些逻辑, 在⽤⼾的请求响应前后执⾏. 也可以在⽤⼾请求前阻止其执行

流程图大概如下

拦截器的使⽤步骤分为两步:

  1. 定义拦截器;
  2. 注册配置拦截器

⾃定义拦截器:实现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, 再观察运⾏结果

我们可以看到拦截器拦截了请求没有进行响应

拦截器详解

拦截器的⼊⻔程序完成之后,接下来我们来介绍拦截器的使⽤细节。拦截器的使⽤细节我们主要介绍

两个部分:

  1. 拦截器的拦截路径配置
  2. 拦截器实现原理

拦截路径

拦截路径是指我们定义的这个拦截器, 对哪些请求生效

我们在注册配置拦截器的时候, 通过 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 等⽂件)

  1. 添加拦截器后,执行Controller的方法之前,请求会被拦截器拦住,执行preHandle() 方法,这个⽅法需要返回⼀个布尔类型的值. 如果返回true, 就表⽰放⾏本次操作, 继续访问controller中的方法. 如果返回false,则不会放⾏(controller中的方法也不会执行).
  2. 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

就看到了它的核心内容

进行了很多的初始化

里面的内容分别是

  1. 初始化⽂件上传解析器MultipartResolver:从应⽤上下⽂中获取名称为multipartResolver的
    Bean,如果没有名为multipartResolver的Bean,则没有提供上传⽂件的解析器
  2. 初始化区域解析器LocaleResolver:从应⽤上下⽂中获取名称为localeResolver的Bean,如果没
    有这个Bean,则默认使⽤AcceptHeaderLocaleResolver作为区域解析器
  3. 初始化主题解析器ThemeResolver:从应⽤上下⽂中获取名称为themeResolver的Bean,如果
    没有这个Bean,则默认使⽤FixedThemeResolver作为主题解析器
  4. 初始化处理器映射器HandlerMappings:处理器映射器作⽤,1)通过处理器映射器找到对应的
    处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx
    ⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取
    到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器
  5. 初始化处理器适配器HandlerAdapter:作⽤是通过调⽤具体的⽅法来处理具体的请求;如果在
    ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的
    HandlerAdapter,并进⾏排序;如果在ApplicationContext中没有发现处理器适配器,则默认
    SimpleControllerHandlerAdapter作为处理器适配器
  6. 初始化异常处理器解析器HandlerExceptionResolver:如果在ApplicationContext发现有
    handlerExceptionResolver,则从ApplicationContext中获取到所有的
    HandlerExceptionResolver,并进⾏排序;如果在ApplicationContext中没有发现异常处理器解
    析器,则不设置异常处理器
  7. 初始化RequestToViewNameTranslator:其作⽤是从Request中获取viewName,从
    ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使⽤
    DefaultRequestToViewNameTranslator
  8. 初始化视图解析器ViewResolvers:先从ApplicationContext中获取名为viewResolver的Bean,
    如果没有,则默认InternalResourceViewResolver作为视图解析器
  9. 初始化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异常");
    } 
   
}
相关推荐
rzl029 分钟前
java web5(黑马)
java·开发语言·前端
君爱学习15 分钟前
RocketMQ延迟消息是如何实现的?
后端
guojl29 分钟前
深度解读jdk8 HashMap设计与源码
java
Falling4232 分钟前
使用 CNB 构建并部署maven项目
后端
guojl34 分钟前
深度解读jdk8 ConcurrentHashMap设计与源码
java
程序员小假43 分钟前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文1 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端
A~taoker1 小时前
taoker的项目维护(ng服务器)
java·开发语言
萧曵 丶1 小时前
Rust 中的返回类型
开发语言·后端·rust