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异常");
    } 
   
}
相关推荐
bugtraq202122 分钟前
Fyne ( go跨平台GUI )中文文档-绘图和动画(三)
开发语言·后端·golang
椅子哥29 分钟前
MyBatis操作数据库-XML实现
xml·java·数据库·spring boot·mybatis
Adolf_199333 分钟前
Flask-JWT-Extended登录验证
后端·python·flask
2402_857589361 小时前
基于Spring Boot的Java免税商品优选商城设计
java·spring boot·后端
L_cl1 小时前
数据结构与算法——Java实现 7.习题——反转链表
java·开发语言·链表
我明天再来学Web渗透1 小时前
【java面经】微服务架构速记
java·开发语言·微服务·云原生·架构
秦淮渔火1 小时前
单例模式(饿汉式-懒汉式)
java·开发语言·单例模式
蔚一1 小时前
Java设计模式(单例模式)——单例模式存在的问题(完整详解,附有代码+案例)
java·开发语言·单例模式·设计模式
ღ᭄陽先生꧔ꦿ᭄2 小时前
Java异常架构与异常关键字
java·开发语言·架构
繁依Fanyi2 小时前
【问题随记】在使用 AuthenticationManager 的时候,出现循环依赖问题 —— `java.lang.StackOverflowError`
java·大数据·开发语言