【Spring】统一事件的处理(拦截器、统一异常处理、统一数据格式返回)

news2024/11/26 16:47:29

文章目录

  • 前言
  • 一、Spring 拦截器
    • 1.1 用户登录权限校验案例
      • 1.1.1 最初的用户登录验证
      • 1.1.2 使用 Spring AOP 实现登录验证的问题
    • 1.2 Spring 拦截器的使用
      • 1.2.1 Spring 拦截器概念与使用步骤
      • 1.2.2 使用拦截器实现对用户登录权限的校验
    • 1.3 拦截器实现原理
    • 1.4 Spring 拦截器和 Spring AOP 的区别
  • 二、统一异常处理
    • 2.1 为什么要统一异常处理
    • 2.2 统一异常处理的使用
  • 三、统一数据返回格式
    • 3.1 为什么需要统一数据返回格式
    • 3.2 统一数据返回格式的实现
    • 3.3 针对 body 为 String 类型报错的问题


前言

在现代的 Web 应用程序开发中,往往需要处理用户权限、异常情况以及数据返回格式等诸多方面的问题。Spring 框架为我们提供了强大的工具和机制来应对这些挑战。本文将重点介绍 Spring 框架中的拦截器、统一异常处理以及统一数据返回格式等相关内容。

一、Spring 拦截器

在 Web 应用开发中,拦截器是一种非常有用的机制,它允许我们在请求处理的不同阶段执行一些特定的操作。这些操作可以是权限校验、日志记录、数据预处理等。下面将详细探讨 Spring 框架中的拦截器相关内容。

1.1 用户登录权限校验案例

首先介绍一个常见的应用案例:

  • 用户登录权限校验。通常,在Web应用中,某些资源或功能需要登录后才能访问,因此需要对用户进行登录状态的验证。在没有拦截器的情况下,我们可能会使用传统的方式来实现这个验证逻辑。让我们来看一下最初的用户登录验证方法

1.1.1 最初的用户登录验证

在使用拦截器之间,我们先来回顾一下最开始使用用户登录验证的方法:

@RestController
public class UserController {

    // 最开始实现用户登录权限验证的方式

    @RequestMapping("/method1")
    public Object method1(HttpServletRequest request){
        // 获取Session,没有不创建
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute("userinfo") == null){
            // 没有获取到 session, 此时说明用户未登录
            return false;
        }

        // 此时则说明已经登录了,执行后续业务逻辑

        // ...

        return true;
    }

    @RequestMapping("/method2")
    public Object method2(HttpServletRequest request){
        // 获取Session,没有不创建
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute("userinfo") == null){
            // 没有获取到 session, 此时说明用户未登录
            return false;
        }

        // 此时则说明已经登录了,执行后续业务逻辑

        // ...

        return true;
    }

	// 其他方法...
}

当使用最初的方式在每个需要用户登录权限验证的方法中加入相同的登录验证逻辑时,会导致一些问题:

  1. 代码重复性: 需要在每个需要验证的方法中复制粘贴相同的验证代码,增加了代码冗余,不利于维护和修改。

  2. 可读性差: 多个方法中都包含相同的验证逻辑,使得代码可读性降低,难以快速理解每个方法的实际功能。

  3. 维护困难: 如果需要修改验证逻辑或者增加新的验证条件,需要在多个地方进行修改,容易出错。

  4. 代码耦合: 将验证逻辑直接嵌入到每个方法中,导致业务逻辑和验证逻辑紧密耦合在一起,不利于代码的解耦和单元测试。

  5. 扩展性差: 如果未来需要添加更多的验证逻辑,或者对验证逻辑进行定制化,需要修改多个方法,增加了工作量和风险。

1.1.2 使用 Spring AOP 实现登录验证的问题

面对上述的问题,就需要考虑使用统一的用户登录验证来解决了。一提到统一登录验证,我们可以会想到使用 Spring AOP 的面向切面编程来实现,但是其他却存在很大的问题,首先来回顾一下 Spring AOP 代码的实现:

// 创建一个切面(类)
@Aspect
@Component
public class UserAspect{
    // 创建切点(方法)定义拦截规则
    @Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
    public void pointcut() {
    }

    // 前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("执行了前置通知:" + LocalDateTime.now());
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
        System.out.println("执行了后置通知:" + LocalDateTime.now());
    }

    // 返回后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        System.out.println("执行了返回后通知:" + LocalDateTime.now());
    }

    // 抛异常后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        System.out.println("抛异常后通知:" + LocalDateTime.now());
    }

    // 环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {

        Object proceed = null;
        System.out.println("Around 方法开始执行:" + LocalDateTime.now());
        try {
            // 执行拦截的方法
            proceed = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("Around 方法结束执行: " + LocalDateTime.now());
        return proceed;
    }
}

如果要使用上面 Spring AOP 的通知方法来实现用户登录验证,主要存在两个问题:

1. 获取 HttpSession 对象问题

  • 在 AOP 中,通知(Advice)是在切点(Pointcut)匹配的连接点(Join Point)上执行的,然而,这些连接点都是方法级别的,不能直接提供对HttpServletRequestHttpSession等相关对象的访问。因此,在 AOP 切面中无法直接访问 HttpSession 对象。

2. 选择性拦截问题

  • 在实际的应用程序中,我们可能只需要对部分功能进行登录权限验证,而需要排除一些其他方法,比如用户的注册和登录
  • 使用基于方法名的切点表达式则难以精确地实现这一目标,因为这可能会涉及到复杂的正则表达式匹配,而且如果有新的方法需要排除,就需要手动更新切点表达式。

为了解决这些问题,Spring 的拦截器机制更适合处理这类情况。拦截器可以访问 HttpServletRequestHttpSession 等请求相关对象,也可以更方便地配置哪些路径需要被拦截,哪些不需要。

1.2 Spring 拦截器的使用

1.2.1 Spring 拦截器概念与使用步骤

Spring 拦截器是 Spring 框架中的一种拦截机制,用于在请求进入控制器前后执行的特定的操作。拦截器可以用于实现登录权限校验、日志记录、数据预处理等功能。拦截器使用面向切向编程(AOP)的思想,可以在请求的不同阶段插入自己的逻辑,从而实现各种功能

下面是使用 Spring 拦截器的一般步骤:

1. 创建拦截器类

首先,创建一个类来实现 Spring 的 HandlerInterceptor接口,并重写其中的方法。通常包括下面这三种方法:

  • preHandle:在请求处理之前执行,可以用于权限校验等操作。如果返回false则中断请求处理。
  • postHandle:在请求处理之后执行,但在视图渲染之前执行。可以进行日志记录等操作。
  • afterCompletion:在视图渲染之后执行,可以进行一些资源清理等操作。

2. 添加并配置拦截器

在 Spring Boot 中配置添加拦截器大致可以分为以下两步:

1)将创建的拦截器类添加到 Spring 容器中: 在 Spring Boot 中,可以使用 @Component 注解来标记创建的拦截器类,将其交由 Spring 容器管理。这样,Spring Boot 会自动扫描并将这个拦截器类纳入到应用的上下文中。

例如,在拦截器类上添加 @Component 注解,如下所示:

import org.springframework.stereotype.Component;

@Component
public class AuthInterceptor implements HandlerInterceptor {
    // 拦截器的具体实现
}

2)实现 WebMvcConfigurer 接口并重写 addInterceptors 方法来添加拦截器到系统配置: 在 Spring Boot 中,可以通过实现 WebMvcConfigurer 接口,重写其中的 addInterceptors 方法来添加拦截器。这个方法会将创建的拦截器添加到系统中,使其在请求被处理时生效。

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 AuthInterceptor authInterceptor; // 注入你的拦截器类

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加拦截器,并指定拦截的路径
        registry.addInterceptor(authInterceptor)
        		.addPathPatterns("/**")
        		.excludePathPatterns("/user/login")
        		.excludePathPatterns("/user/reg")
        		;
    }
}

上述代码中,addInterceptors 方法会将拦截器类 authInterceptor 添加到系统配置中,并通过 addPathPatterns 指定要拦截的路径,然后可以使用excludePathPatterns方法来指定放弃拦截的指定接口,如登录、注册功能。这样,当程序运行时,配置好的拦截器就会自动生效。

3. 配置拦截器顺序
如果同时使用了多个拦截器,可以通过配置来定义它们的执行顺序。在 Spring MVC 中,拦截器的执行顺序与它们在配置文件中声明的顺序一致。
4. 使用拦截器
配置好拦截器后,它们会在请求被处理之前、之后或之后的渲染阶段执行相应的逻辑。这样就可以在拦截器中实现所需的功能,如权限校验、日志记录等。

1.2.2 使用拦截器实现对用户登录权限的校验

1. 创建用户登录验证的拦截器:

/**
 * Spring MVC拦截器(LoginInterceptor),用于检查用户是否已登录。
 * 如果用户未登录,则重定向到登录页面。
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断用户登录
        HttpSession session = request.getSession(false); // 默认值是true
        if (session != null && session.getAttribute(ApplicationVariable.SESSION_KEY_USERINFO) != null) {
            // 用户已经登录了
            return true;
        }

        // 当代码执行到此次,表示用户未登录
        response.sendRedirect("/login.html");
        return false;
    }
}

2. 添加并配置拦截规则

/**
 * 配置拦截规则
 */
@Configuration
public class MyConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有的 url
                // 放开拦截的内容
                .excludePathPatterns("/**/*.js")
				.excludePathPatterns("/**/*.css")
				.excludePathPatterns("/**/*.jpg")
				.excludePathPatterns("/login.html")
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/user/login")
                // ...
        ;
    }
}

1.3 拦截器实现原理

拦截器的实现原理涉及到 Spring 框架的核心概念:面向切面编程(AOP)。在 AOP 的帮助下,拦截器可以被织入到方法调用链中,从而在请求处理的不同阶段执行相应的操作。下面将介绍拦截器的实现原理和工作流程。

1. AOP 概念回顾

AOP 是一种编程范式,旨在解决横切关注点(Cross-cutting Concerns)的问题。它通过将横切关注点与主要业务逻辑分离,使得我们能够更好地管理和维护代码。在 Spring 中,AOP 是通过代理模式和动态代理来实现的。

在 AOP 中,有两个重要的概念:切点(Pointcut)和通知(Advice)。

  • 切点(Pointcut): 切点定义了在哪些连接点(Join Point)上应用通知。连接点可以是方法调用、方法执行、字段访问等等。切点使用表达式来匹配连接点,以确定哪些连接点会被通知所影响。

  • 通知(Advice): 通知定义了在切点处执行的代码。在 Spring 中,有多种类型的通知,包括前置通知(在方法调用之前执行)、后置通知(在方法调用之后执行)、环绕通知(在方法调用前后执行)、异常通知(在方法抛出异常时执行)、最终通知(在方法调用结束后执行)。

2. 拦截器的工作流程

拦截器实际上是一种 AOP 的应用,通过 AOP 的方式实现对请求处理过程的干预。以下是拦截器的工作流程:

  1. 当请求到达 DispatcherServlet(前端控制器)时,拦截器首先会根据配置的拦截规则(切点)来判断是否要拦截该请求。

  2. 如果拦截器决定要拦截该请求,它会在切点前执行通知的逻辑,比如前置通知。这样,拦截器可以在请求处理之前执行一些预处理操作,如权限校验、日志记录等。

  3. 接下来,请求会继续被传递到相应的控制器进行处理。拦截器不会中断请求的流程,只是在处理前插入了自己的逻辑。

  4. 当控制器处理完请求后,拦截器会再次执行通知的逻辑,比如后置通知。这样,拦截器可以在请求处理之后执行一些后续操作,如数据封装、日志记录等。

  5. 最终,拦截器会在切点后执行通知的逻辑,比如最终通知。这样,拦截器可以在请求处理结束后执行一些清理操作。

通过拦截器,我们可以实现对请求处理过程中不同阶段的干预,从而达到一些横切关注点的处理。这种机制使得我们能够更好地管理和维护代码,提高了代码的可维护性和重用性。

1.4 Spring 拦截器和 Spring AOP 的区别

Spring 拦截器(Interceptor)和 Spring AOP(面向切面编程)是两种在 Spring 框架中用于处理横切关注点的机制,它们有些相似,但也有一些关键的区别。以下是它们之间的主要区别:

1. 目标领域不同

  • Spring 拦截器: 主要用于处理 Web 请求。它在请求处理的不同阶段执行特定的操作,如权限校验、日志记录等。拦截器主要关注于对 Web 请求的处理流程进行干预,以及在请求前后插入自定义的逻辑。

  • Spring AOP: 用于处理应用程序中的横切关注点,不仅限于 Web 请求。AOP 可以在方法调用、方法执行、字段访问等不同连接点上执行通知,从而实现一些与主要业务逻辑解耦的功能,如事务管理、性能监控等。

2. 应用范围不同

  • Spring 拦截器: 主要应用于 Web 层,处理 Web 请求。它可以控制请求的处理流程,但只作用于 Web 层,不会影响到业务层的方法调用。

  • Spring AOP: 可以应用于多个层面,包括业务层、持久层、Web 层等。AOP 可以跨足不同的层面,通过拦截连接点实现对不同层之间的通用关注点的处理。

3. 处理方式不同

  • Spring 拦截器: 拦截器是基于 Java 动态代理的机制,它通过织入在请求处理流程中的方式来实现对请求的干预。拦截器主要关注于请求处理的前后环节,可以进行预处理、后处理、资源释放等操作。

  • Spring AOP: AOP 是通过代理模式来实现的,可以使用 JDK 动态代理或者 CGLIB 来生成代理对象。AOP 可以在方法调用前后、异常抛出时、方法执行结束等连接点上执行通知,从而实现不同类型的横切关注点。

4. 原理和灵活性

  • Spring 拦截器: 拦截器是一种 Spring MVC 提供的机制,更加针对 Web 层的处理。它具有一定的灵活性,但在处理跨层面的通用关注点时相对较为有限。

  • Spring AOP: AOP 是 Spring 框架的核心特性之一,可以在多个层面应用,包括对方法调用、字段访问等各种连接点的处理。AOP 更具通用性,适用于处理各种横切关注点。

总之,Spring 拦截器和 Spring AOP 都是用于处理横切关注点的重要机制,但在应用范围、处理方式和灵活性等方面存在一些区别。选择合适的机制取决于实际需求以及在哪个层面需要处理横切关注点。

二、统一异常处理

2.1 为什么要统一异常处理

在开发过程中,应用程序难免会遇到各种异常情况,如数据库连接失败、空指针异常等。为了提供更好的用户体验和更好的错误信息管理,采用统一异常处理机制是很有必要的。以下是为什么要采用统一异常处理的一些原因:

  1. 友好的用户体验: 统一异常处理可以捕获各种异常,并返回统一的错误响应。这样,用户可以获得更友好、更易于理解的错误提示,而不会直接暴露应用程序内部的错误细节。

  2. 减少代码重复性: 在应用程序中,同一类型的异常可能会在多个地方发生。通过统一异常处理,可以将相同的错误处理逻辑抽取到一个地方,减少代码的重复性,提高代码的可维护性。

  3. 集中式错误管理: 统一异常处理可以将错误信息集中管理,方便进行日志记录、监控和错误分析。这有助于快速定位问题,提高应用程序的稳定性。

2.2 统一异常处理的使用

在 Spring 框架中,可以使用 @ControllerAdvice@ExceptionHandler 注解来实现统一异常处理。

步骤如下:

  1. 创建一个异常处理类,并使用 @ControllerAdvice 注解标记该类,表示它是一个控制器通知类,用于处理全局的异常情况。

  2. 在异常处理类中,使用 @ExceptionHandler 注解定义异常处理方法。可以针对不同类型的异常编写不同的处理方法,或者使用一个通用的方法处理所有异常。

  3. 在异常处理方法中,可以自定义错误信息,构建错误响应,并返回适当的视图或 JSON 响应。

以下是一个示例代码,展示了如何使用统一异常处理:


/**
 * 统一异常处理
 */
@ControllerAdvice
@ResponseBody
public class MyExceptionAdvice {

    /**
     * 处理空指针异常
     * @param e
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public HashMap<String, Object> doNullPointerException(NullPointerException e){
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "空指针: " + e.getMessage());
        result.put("data", null);
        return result;
    }

    /**
     * 默认异常处理(当具体的异常处匹配不到时,会执行此方法)
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public HashMap<String, Object> doException(Exception e){
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "Exception: " + e.getMessage());
        result.put("data", null);
        return result;
    }

}

在这个代码示例中,创建了一个名为 MyExceptionAdvice 的全局异常处理类,通过 @ControllerAdvice 注解来标记它。这个类中包含了两个异常处理方法:

  1. doNullPointerException 方法,用于处理空指针异常(NullPointerException)。当应用程序抛出空指针异常时,这个方法会被调用,然后构建一个包含错误信息的 HashMap 并返回。

  2. doException 方法,用于处理其他类型的异常。如果应用程序抛出的异常类型不匹配前面定义的处理方法,就会执行这个默认异常处理方法。它也会构建一个错误响应并返回。

通过这样的方式,实现了针对不同类型异常的定制处理,提供了统一的错误响应格式,从而提升了用户体验和代码的可维护性。

三、统一数据返回格式

3.1 为什么需要统一数据返回格式

在开发中,不同接口可能返回不同的数据格式,这可能导致前端处理数据时需要根据不同接口的返回格式进行不同的处理逻辑,增加了代码的复杂性和维护成本。为了简化前端处理逻辑,提高代码的可维护性,可以考虑统一数据返回格式。

统一数据返回格式的优点包括:

  1. 减少前端逻辑复杂性: 前端不需要针对不同接口编写不同的处理逻辑,可以统一处理数据格式,降低代码复杂性。

  2. 提高前后端协作效率: 前后端通过明确的数据格式约定,可以更快地进行接口开发和联调,减少沟通成本。

  3. 规范错误处理: 统一的数据返回格式可以规范错误处理方式,使得前端可以更方便地判断请求是否成功,以及获取错误信息。

3.2 统一数据返回格式的实现

在实现统一数据格式返回的时候,主要借助 Spring 框架提供的 ResponseBodyAdvice 接口和@ControllerAdvice注解,并且需要重写接口中的supportsbeforeBodyWrite方法,其中supports方法表示是否要使用统一数据格式返回,而beforeBodyWrite则是实现对数据格式的统一。

以下是一个示例,展示了如何实现统一数据返回格式,其中规定返回的数据格式为:

{
	"cede":code,
	"msg":msg,
	"data":data
}

在返回的时候可以使用HashMap来组织。下面是具体的实现代码:

/**
 * 统一数据格式处理
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    /**
     * 返回数据之前进行处理
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //规定标准格式为:HashMap<String, Object> -> code,msg.data

        if (body instanceof HashMap) {
            // 如果已经是标准格式
            return body;
        }

        // 重写返回结果,让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");

        return result;
    }
}

上述示例代码很清晰地演示了如何使用 @ControllerAdvice 注解和 ResponseBodyAdvice 接口来实现统一数据返回格式。通过重写 supportsbeforeBodyWrite 方法,实现了对返回数据格式的统一处理。

在这个示例中,规定了统一的数据格式,即返回的 JSON 包含了 “code”、“msg” 和 “data” 字段。如果返回的数据已经是标准格式(HashMap<String, Object>),则直接返回;否则,就构建一个标准格式的返回对象,并将原始数据放入其中。

3.3 针对 body 为 String 类型报错的问题

当针对处理基本数据类型的时候,例如:

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    public int login() {
        return 1;
    }
}

浏览器访问结果:

此时发现没有任何问题,并且成功实现了对数据格式的统一返回。但是如果返回的是 String 类型呢?


@RestController
@RequestMapping("/user")
public class UserController {
	@RequestMapping("/hello")
	public String hello(){
	    return "hello world";
	}
}

再次通过浏览器访问:

1. 返回 String 类型出错原因分析:

其报错的内容大致是HashMap类型不能够转换成String类型。为什么会这样呢?首先需要了解数据在返回过程中的执行流程

  1. 控制器方法返回 String 类型;
  2. 针对于 String 类型进行统一数据格式处理,即把为 String 类型的 body 作为 value 设置到 keydataHashMap 中;
  3. HashMap 转换成 application/json 字符串并通过网络传输给前端。

值得注意的是:

  • 其中,如果 body 的类型如果是 String 类型,则会使用 StringHttpMessageConverter 进行类型的转换;
  • 否则使用 HttpMessageConverter进行类型转换。

但是使用针对于第三步,由于 body 是 String 类型的,因此使用的是StringHttpMessageConverter类,它只能够将 String 类型数据转换为 JSON 字符串,如果转换 HashMap 就会出错。

2. 解决方法:

解决方法大致可以分为两种:

  1. 解决方法一: 如果body类型是 String,就直接返回 String 类型,其中可以使用字符串拼接,也可以使用 jackson 中的ObjectMapper进行转换成 JSON 格式。
    1)拼接形成 String 进行返回:
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

    if (body instanceof String) {
        // 返回一个 String 字符串
        return "{\"code\" : 200, \"msg\": \"\", \"data\":\"" + body + "\"}";
    }

    if (body instanceof HashMap) {
        // 如果已经是标准格式
        return body;
    }

    // 重写返回结果,让其返回一个统一的数据格式
    HashMap<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("data", body);
    result.put("msg", "");
    return result;
}

2)使用 jackson 中的 ObjectMapper

@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

    if (body instanceof HashMap) {
        // 如果已经是标准格式
        return body;
    }

    // 重写返回结果,让其返回一个统一的数据格式
    HashMap<String, Object> result = new HashMap<>();
    result.put("code", 200);
    result.put("data", body);
    result.put("msg", "");

    if(body instanceof String){
        // 返回一个 String 字符串
        return objectMapper.writeValueAsString(result);
    }
    return result;
}

2. 解决方法二: 直接禁用 StringHttpMessageConverter 类。

可以在配置类中通过重写 configureMessageConverters 方法来实现禁用 StringHttpMessageConverter

@Configuration
public class MyConfig implements WebMvcConfigurer {
    /**
     * 移除 StringHttpMessageConverter
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}

其中,converter -> converter instanceof StringHttpMessageConverter 是一个 Lambda 表达式,用于检查 converter 是否是 StringHttpMessageConverter 的实例。如果是,则移除该实例。

当选择任意一个解决方法之后,再次访问返回 String 类型的控制器方法,其结果如下:


此时便能够正确返回统一的数据格式了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/881219.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI绘画 | 一文学会Midjourney绘画,创作自己的AI作品(快速入门+参数介绍)

一、生成第一个AI图片 首先&#xff0c;生成将中文描述词翻译成英文 然后在输入端输入&#xff1a;/imagine prompt:Bravely running boy in Q version, cute head portrait 最后&#xff0c;稍等一会即可输出效果 说明&#xff1a; 下面的U1、U2、U3、U4代表的第一张、第二张…

Revit SDK: FindColumns 找到和墙相交的柱子 MeasureHeight 计算天窗到最近楼板的位置

前言 本文的主要内容是基于 ReferenceIntersector 的两个个应用。ReferenceIntersector 的主要作用是找到一条与给定射线相交的各个元素。 FindColumns 内容 ReferenceIntersector namespace Autodesk.Revit.DB {public class ReferenceIntersector : IDisposable{public R…

trollcave靶场

配置 第一步&#xff1a;启动靶机时按下 shift 键&#xff0c; 进入以下界面 第二步&#xff1a;选择第二个选项&#xff0c;然后按下 e 键&#xff0c;进入编辑界面 将这里的ro修改为rw single init/bin/bash&#xff0c;然后按ctrlx&#xff0c;进入一个相当于控制台的界面…

用于构建生成式 AI 应用程序备忘单的最佳 Python 工具

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑器的3D应用场景 新一代的声音 KDnuggets发布了一份富有洞察力的新备忘单&#xff0c;重点介绍了用于构建生成AI应用程序的顶级Python库。 毫无疑问&#xff0c;读者都知道&#xff0c;生成式人工智能是目前数据科…

小红书运营 变现方法总结(精)

大家好&#xff0c;我是网媒智星&#xff0c;今天跟大家分享一下小红书运营方面的知识&#xff0c;怎样利用小红书变现&#xff1f;全篇倾情干货输出&#xff0c;认真学习&#xff0c;保证您收获多多。 首先&#xff0c;让我们来分析一下小红书平台的优势。关于卖东西&#xff…

政务中心站至政务中心东站右线盾构本月始发

本报记者 赵鹏 实习记者 池阳 通讯员 董浩程 立秋已过&#xff0c;平谷线“瓜熟蒂落”的日子指日可待。在左线隧道刚刚顺利贯通后&#xff0c;平谷线政务中心站至政务中心东站区间右线隧道已展开盾构组装施工&#xff0c;右线盾构即将于本月内始发&#xff0c;被誉为“地下蛟龙…

游戏类APP如何提升用户的活跃度?

移动游戏行业&#xff0c;追求使用率的营销能发挥强大的功效&#xff0c;可帮助减少玩家流失、追回流失的玩家、提高活跃玩家所带来的价值以及增加付费玩家贡献的收入。 一、了解玩家需求 想要提升玩家的活跃&#xff0c;首先要知道&#xff0c;玩家喜欢玩哪些平台的游戏&…

LLM-2-ChatGLM2

1 训练 1.1 训练参数配置理解 训练的输入长度source的长度target的长度 –pre_seq_len&#xff1a;pre_seq_len的取值范围一般是1到512&#xff0c;它表示自然语言指令的长度&#xff0c;即输入序列中的前pre_seq_len个token&#xff0c;具体的值需要根据自然语言指令的长度…

最新Win10离线安装.NET Framework 3.5的方法(附离线包2022/3/22)

win10系统安装软件时&#xff0c;可能需要.net framework3.5的运行环境&#xff0c;当我们安装某些软件的时候会提示“你的电脑上的应用需要使用以下Windows功能:.NET Framework 3.5(包括.NET 2.0和3.0)。如果系统默认的是4.0以上的版本&#xff0c;当软件需要.net framework3.…

基于Java+SpringBoot+vue前后端分离疫情下图书馆管理系统设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

OpenZFS 2.2 发布 RC3,支持 Linux 6.4

导读之前的 OpenZFS 2.2 候选版本已致力于实现与 Linux 6.4 内核的兼容性&#xff0c;而在 2.2-rc3 中&#xff0c;Linux 6.4 支持的元跟踪器已标记为已完成。 OpenZFS 2.2 发布了第 3 个 RC 版本。 之前的 OpenZFS 2.2 候选版本已致力于实现与 Linux 6.4 内核的兼容性&#x…

手把手教你 Vue3 + vite + Echarts 5 +TS 绘制中国地图,看完就会

废话不多说&#xff0c;看图&#xff01; 本篇文章介绍 Vue3 vite TS 项目内使用 Echarts 5 绘制中国地图&#xff0c;标记分布点&#xff01;之前没有接触过 Echarts 的&#xff0c;可以先去官方示例看看&#xff0c;里面图形特别齐全。但是官方文档看着费劲的&#xff0c;太…

Calcite使用外部自定义函数

在calcite中可以通过自定义函数来实现丰富的算子,但是往往需要将自定义的函数包与Calcite集成在一起&#xff0c;才能使用相应的算子功能&#xff0c;由于业务需求多变&#xff0c;函数包可能面临频繁的更改&#xff0c;如果需要更改生效&#xff0c;则需要将Calcite的代码重新…

DatenLord X Segmentfault直播预告 l CURP协议的工业化实践

CURP协议 传统单数据中心解决方案无法满足跨数据中心的场景对性能和一致性的需求。DatenLord推出开源的分布式KV存储Xline&#xff0c;针对多数据中心场景&#xff0c;实现数据的高性能跨云、跨数据中心共享访问&#xff0c;并且保证数据的一致性&#xff0c;方便业务系统实现…

量子论公众号是如何半年做到10000粉的?

有不少人问我&#xff0c;“量子论”公众号是怎么运营的&#xff1f;“量子论”公众号是如何涨到1万粉的&#xff1f; 用这篇文章作为参考答案吧。 我是2月份开始落笔写这个号的&#xff0c;那个时候我已经在玩ChatGPT了。 随着AI火出圈&#xff0c;我打算分享一些ChatGPT使用经…

【LeetCode每日一题】——628.三个数的最大乘积

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 排序 二【题目难度】 简单 三【题目编号】 628.三个数的最大乘积 四【题目描述】 给你一个…

2023最新版本Activiti7系列-源码篇-初始化过程

源码分析 1.设计模式 1.1 命令模式 https://dpb-bobokaoya-sm.blog.csdn.net/article/details/89115420 1.2 责任链模式 https://dpb-bobokaoya-sm.blog.csdn.net/article/details/89077040 2.初始化过程 2.1 入口代码 我们在SpringBoot项目中来看Activiti7的源码。首先要…

DTCC 2023即将启幕 明天见!

8月16日-18日&#xff0c;由IT168联合旗下ITPUB、ChinaUnix两大技术社区主办的第14届中国数据库技术大会&#xff08;DTCC2023&#xff09;将在北京举行 作为国内云原生数据仓库代表厂商&#xff0c;酷克数据受邀亮相DTCC 2023&#xff0c;与广大数据库领域从业人士共同分享云…

财务人员学python有意义吗,财务会python好找工作吗

这篇文章主要介绍了财务方面python需要什么水平&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 财务是一个比较特殊的工作岗位&#xff0c;每天需要接触各种各样的数据&#x…

【数理知识】三维空间旋转矩阵的欧拉角表示法,四元数表示法,两者之间的转换,Matlab 代码实现

序号内容1【数理知识】自由度 degree of freedom 及自由度的计算方法2【数理知识】刚体 rigid body 及刚体的运动3【数理知识】刚体基本运动&#xff0c;平动&#xff0c;转动4【数理知识】向量数乘&#xff0c;内积&#xff0c;外积&#xff0c;matlab代码实现5【数理知识】最…