Spring Boot 拦截器 HandlerInterceptor的使用以及WebMvcConfigurer简单介绍

当我们使用Spring Boot构建Web应用程序时,HandlerInterceptor 是一个重要的组件,用于拦截请求的处理过程。HandlerInterceptor 接口定义了在请求处理的不同阶段执行的方法,允许我们在请求到达处理程序之前和之后执行自定义逻辑。

HandlerInterceptor

在Spring Boot中,我们通常通过实现HandlerInterceptor 接口来创建自定义的拦截器。以下是HandlerInterceptor 接口定义的主要方法:

  1. preHandle :在请求到达处理程序之前被调用。可以用于执行一些前置处理逻辑,例如身份验证、日志记录等。如果此方法返回true,则请求继续传递到处理程序;如果返回false,则请求处理终止。
  2. postHandle:在请求处理程序执行之后,视图渲染之前被调用。可以用于执行一些后置处理逻辑,例如修改模型数据、记录执行时间等。
  3. afterCompletion:在整个请求完成之后被调用。通常用于清理资源、记录最终日志等。可以在这里获取到处理程序的执行结果和可能的异常信息。

要使用HandlerInterceptor,我们需要在配置类中注册它。例如,在Spring Boot中,我们可以创建一个继承自WebMvcConfigurerAdapter(在Spring 5中使用WebMvcConfigurer)的配置类,并重写addInterceptors 方法。

java 复制代码
@Configuration
public class MyWebMvcConfigurer extends WebMvcConfigurerAdapter {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/**");
    }
}

在这个例子中,MyHandlerInterceptor 是我们自定义的拦截器类,它需要实现HandlerInterceptor 接口。

java 复制代码
public class MyHandlerInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在请求处理前执行的逻辑
        return true; // 返回 true 允许请求继续,返回 false 则终止请求
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在请求处理后、视图渲染前执行的逻辑
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个请求完成后执行的逻辑
    }
}

通过使用HandlerInterceptor,我们可以实现对请求处理过程的精细控制,例如添加全局的身份验证、记录请求日志等功能。

HandlerInterceptorAdapter

HandlerInterceptorAdapter 是 Spring 提供的一个适配器类,实现了 HandlerInterceptor 接口,并且提供了默认的空实现。它的存在使得我们在实现自定义拦截器时,只需要关注我们感兴趣的方法,而不需要实现所有方法。

具体来说,HandlerInterceptorAdapter 实现了 HandlerInterceptor 接口的三个方法:preHandlepostHandle、和 afterCompletion。默认情况下,这些方法的实现为空,允许我们只重写需要的方法,而不用关心其他方法。

以下是一个简单的例子,展示了如何使用 HandlerInterceptorAdapter

java 复制代码
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在请求处理前执行的逻辑
        return true; // 返回 true 允许请求继续,返回 false 则终止请求
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在请求处理后、视图渲染前执行的逻辑
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个请求完成后执行的逻辑
    }
}

在这个例子中,MyInterceptor 类继承了 HandlerInterceptorAdapter,并重写了 preHandlepostHandleafterCompletion 方法,实现了自定义的拦截逻辑。

然后,我们可以将这个拦截器注册到 Spring MVC 的配置中,就像之前提到的方式一样:

java 复制代码
@Configuration
public class MyWebMvcConfigurer extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }
}

使用 HandlerInterceptorAdapter 可以让拦截器的实现更加简洁,只需关注自己感兴趣的方法即可。

AsyncHandlerInterceptor

AsyncHandlerInterceptorHandlerInterceptor 接口的一种扩展,专门用于处理异步请求。与同步请求不同,异步请求在处理过程中可能涉及到线程的切换,因此需要专门的拦截器来支持这种场景。

与普通的 HandlerInterceptor 相比,AsyncHandlerInterceptor 接口增加了对异步请求的支持,它定义了额外的方法来处理异步场景:

  1. preHandle :与同步请求的 preHandle 方法类似,在异步请求的开始阶段被调用。可以用于执行一些前置处理逻辑。返回 true 表示请求继续进行,返回 false 则中断异步请求的处理。
  2. postHandle :与同步请求的 postHandle 方法类似,但是在异步请求的主处理线程完成处理之后调用。可以用于执行一些后置处理逻辑。
  3. afterCompletion :与同步请求的 afterCompletion 方法类似,但是在整个异步请求完成之后调用。可以用于清理资源、记录最终日志等。
  4. afterConcurrentHandlingStarted:在异步请求处理开始时被调用。此时,主处理线程已经处理完请求,而异步请求的处理可能在其他线程中进行。可以用于执行一些异步处理开始时的逻辑。

要使用 AsyncHandlerInterceptor,我们可以通过实现该接口并将其注册到配置中,就像使用普通的 HandlerInterceptor 一样。以下是一个简单的例子:

java 复制代码
public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在异步请求开始前执行的逻辑
        return true; // 返回 true 允许请求继续,返回 false 则中断异步请求的处理
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在异步请求的主处理线程完成处理后执行的逻辑
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个异步请求完成后执行的逻辑
    }

    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在异步请求处理开始时执行的逻辑
    }
}

然后,将拦截器注册到配置中:

java 复制代码
@Configuration
public class MyWebMvcConfigurer extends WebMvcConfigurerAdapter {
    
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.registerCallableInterceptors(new MyAsyncHandlerInterceptor());
    }
}

在这个例子中,MyAsyncHandlerInterceptor 是实现了 AsyncHandlerInterceptor 接口的自定义异步拦截器。

AsyncHandlerInterceptor使用场景

AsyncHandlerInterceptor 主要用于处理异步请求场景,其中异步请求是指在请求处理过程中,主处理线程可能会在处理的不同阶段释放出来,允许其他线程继续处理请求。以下是一些适合使用 AsyncHandlerInterceptor 的场景:

  1. 长时间任务处理:
  • 当应用需要处理长时间运行的任务时,可以使用异步请求,而 AsyncHandlerInterceptor 可以用于处理异步请求的不同阶段。
  • 例如,如果某个请求触发了一个需要很长时间才能完成的任务,你可以在异步处理的开始和结束阶段执行一些逻辑,例如记录日志、通知用户等。
  1. 非阻塞I/O操作:
  • 异步请求通常用于处理非阻塞 I/O 操作,例如读取或写入大量数据、调用外部服务等。
  • 在异步处理的开始和结束阶段,AsyncHandlerInterceptor 可以用于执行相关的逻辑,例如记录性能指标、处理异常情况等。
  1. 推送通知:
  • 当需要实现服务器端推送通知给客户端时,异步请求是一个常见的选择。
  • 使用 AsyncHandlerInterceptor 可以在推送开始和结束的时候执行逻辑,例如记录推送日志、处理异常情况等。
  1. 资源处理和清理:
  • 异步请求在整个处理过程中可能涉及到多个线程,因此可以使用 AsyncHandlerInterceptor 在异步请求的不同阶段进行资源的处理和清理。
  • 例如,可以在异步处理开始时分配某些资源,在异步请求结束时进行清理。
  1. 异步任务执行前后的逻辑:
  • 如果应用中使用了异步任务执行框架(如 Spring 的 @Async),AsyncHandlerInterceptor 可以在异步任务开始执行前和执行完成后执行一些逻辑。
  • 这可以用于记录任务执行时间、处理异常等。

总的来说,使用 AsyncHandlerInterceptor 的场景通常涉及到异步请求的处理,而这些请求在执行过程中可能涉及到线程的切换和长时间运行的任务。在这些场景中,AsyncHandlerInterceptor 提供了一种机制,允许我们在异步请求的不同阶段执行自定义逻辑,以满足特定需求。

Configurer

WebMvcConfigurer

WebMvcConfigurer 接口是 Spring MVC 提供的用于配置 Web MVC 的接口。通过实现这个接口,可以对 Spring MVC 进行一些自定义配置,例如添加拦截器、修改视图解析器、配置消息转换器等。以下是一些常见的用法:

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    // 1. 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }
    // 这里的 MyInterceptor 是你自定义的拦截器,可以在其中实现需要的拦截逻辑。

    // 2. 添加自定义静态资源处理
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
    // 这样配置后,访问路径 /static/** 将会映射到 classpath:/static/ 目录下的资源。

    // 3. 修改视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // 这里可以进行视图解析器的自定义配置,例如:
        // registry.jsp("/WEB-INF/views/", ".jsp");
        // 表示使用 JSP 视图解析器,视图文件位于 /WEB-INF/views/ 目录下,后缀为 .jsp。
    }

    // 4. 配置消息转换器
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 这里可以配置消息转换器,例如添加自定义的 JSON 转换器。
        // converters.add(new MyJsonConverter());
    }

    // 5. 配置跨域资源共享(CORS)
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 配置跨域资源共享,允许 http://localhost:8080 发起的跨域请求访问 /api/**
        registry.addMapping("/api/**").allowedOrigins("http://localhost:8080");
    }
    // 这是一个简单的例子,允许来自 http://localhost:8080 的请求跨域访问 /api/** 路径。
}

这个配置类通过实现 WebMvcConfigurer 接口,为 Spring MVC 提供了一些自定义配置。具体来说:

  • 添加拦截器: 通过 addInterceptors 方法添加拦截器,可以在请求处理的各个阶段执行自定义逻辑。
  • 添加自定义静态资源处理: 使用 addResourceHandlers 方法配置静态资源的映射,使得这些资源可以通过指定的路径访问。
  • 修改视图解析器: 使用 configureViewResolvers 方法配置视图解析器,允许自定义视图解析器的行为。
  • 配置消息转换器: 使用 configureMessageConverters 方法配置消息转换器,可以添加或修改默认的消息转换器,例如 JSON 转换器。
  • 配置跨域资源共享(CORS): 使用 addCorsMappings 方法配置跨域资源共享,定义允许跨域访问的路径和来源。

在实际应用中,你需要根据具体需求填充这些方法的具体实现,以满足项目的要求。在配置完成后,确保这个配置类被 Spring Boot 应用程序正确地扫描到,以确保这些配置生效。

WebMvcConfigurationSupport

WebMvcConfigurationSupport 是 Spring MVC 提供的用于进行更深层次自定义配置的基类。

WebMvcConfigurer 不同,WebMvcConfigurationSupport 提供的配置更为底层,允许你对 Spring MVC 的默认配置进行更彻底的改变。这里解释一下示例中的各个重要方法:

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
@EnableWebMvc
public class MyWebMvcConfig extends WebMvcConfigurationSupport {

    // 1. 添加拦截器
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }
    // 这里的 MyInterceptor 是自定义的拦截器,你可以在其中实现需要的拦截逻辑。

    // 2. 添加自定义静态资源处理
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
    // 这样配置后,访问路径 /static/** 将会映射到 classpath:/static/ 目录下的资源。

    // 3. 修改视图解析器
    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        // 在这里可以进行视图解析器的自定义配置,例如:
        // registry.jsp("/WEB-INF/views/", ".jsp");
        // 表示使用 JSP 视图解析器,视图文件位于 /WEB-INF/views/ 目录下,后缀为 .jsp。
    }

    // 4. 配置消息转换器
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 在这里可以配置消息转换器,例如添加自定义的 JSON 转换器。
        // converters.add(new MyJsonConverter());
    }

    // 5. 配置跨域资源共享(CORS)
    @Override
    protected void addCorsMappings(CorsRegistry registry) {
        // 配置跨域资源共享,允许 http://localhost:8080 发起的跨域请求访问 /api/**
        registry.addMapping("/api/**").allowedOrigins("http://localhost:8080");
    }
    // 这是一个简单的例子,允许来自 http://localhost:8080 的请求跨域访问 /api/** 路径。


    //------------------------- 其它方法 -----------------------------

    // 配置路径匹配
    @Override
    protected void configurePathMatch(PathMatchConfigurer configurer) {
        // 在这里可以设置路径匹配的行为,例如是否在路径末尾添加斜杠
        // configurer.setUseTrailingSlashMatch(false);
    }

    // 配置异步请求的支持
    @Override
    protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // 在这里可以设置异步请求的超时时间、线程池等
        // configurer.setDefaultTimeout(30000);
    }

    // 配置内容协商策略
    @Override
    protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 在这里可以设置默认的内容类型、请求参数中的内容类型等
        // configurer.defaultContentType(MediaType.APPLICATION_JSON);
    }

    // 扩展消息转换器
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 在这里可以在已有的转换器列表上进行扩展
        // converters.add(new MyAdditionalConverter());
    }

    // 配置默认的 Servlet 处理
    @Override
    protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        // 在这里可以启用或禁用对静态资源的默认 Servlet 处理
        // configurer.enable();
    }

    // 添加简单的视图控制器
    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
        // 在这里可以添加简单的视图控制器,将某个 URL 直接映射到某个视图
        // registry.addViewController("/home").setViewName("home");
    }

    // 扩展异常处理器
    @Override
    protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 在这里可以在已有的异常处理器列表上进行扩展
        // exceptionResolvers.add(new MyAdditionalExceptionResolver());
    }

    // 配置异常处理器
    @Override
    protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        // 在这里可以设置异常处理器的顺序和是否开启默认异常处理器
        // exceptionResolvers.order(1);
        // exceptionResolvers.setUseDefaultExceptionHandler(false);
    }

    // 配置消息代码解析器
    @Override
    protected void configureMessageCodesResolver(MessageCodesResolver codesResolver) {
        // 在这里可以设置消息代码的解析策略
        // codesResolver.setMessageCodeFormatter(new MyMessageCodeFormatter());
    }

    // 配置视图解析器
    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        // 在这里可以设置视图解析器的顺序和是否开启默认视图解析器
        // registry.order(1);
        // registry.enableContentNegotiation(new MappingJackson2JsonView());
    }

    // 添加自定义的参数解析器
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        // 在这里可以添加自定义的参数解析器,可以在已有的解析器列表上进行添加
        // argumentResolvers.add(new MyAdditionalArgumentResolver());
    }

    // 添加自定义的返回值处理器
    @Override
    protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        // 在这里可以添加自定义的返回值处理器,可以在已有的处理器列表上进行添加
        // returnValueHandlers.add(new MyAdditionalReturnValueHandler());
    }
    // .........
}
  • **addInterceptors**** 方法:** 通过这个方法,可以添加拦截器,允许在请求处理的不同阶段执行自定义逻辑。这与 WebMvcConfigurer 中的方法类似。
  • **addResourceHandlers**** 方法:** 这个方法用于添加自定义静态资源处理。配置后,指定路径的静态资源将会映射到指定的资源位置。
  • **configureViewResolvers**** 方法:** 通过这个方法可以修改视图解析器。在这里,你可以配置自定义的视图解析器,定义视图文件的存放位置和文件后缀等。
  • **configureMessageConverters**** 方法:** 这个方法用于配置消息转换器。可以在这里添加或修改默认的消息转换器,例如添加自定义的 JSON 转换器。
  • **addCorsMappings**** 方法:** 通过这个方法可以配置跨域资源共享(CORS)。这个例子中允许了来自 http://localhost:8080 的请求跨域访问 /api/** 路径。

需要注意的是,在使用 WebMvcConfigurationSupport 时,Spring Boot 的默认配置可能会失效,因此需要手动配置一些默认行为。此外,虽然 WebMvcConfigurationSupport 提供更灵活的配置,但也更为底层,可能会增加配置的复杂性。因此,一般情况下推荐使用 WebMvcConfigurer 进行轻量级的配置。只有在需要更深层次的自定义时,才考虑使用 WebMvcConfigurationSupport

可能会失效的配置

使用WebMvcConfigurationSupport时,Spring Boot的一些默认配置可能会失效,因为WebMvcConfigurationSupport提供了更高级别的自定义配置,可能会覆盖或替代Spring Boot的默认配置。以下是一些可能会受影响的默认配置:

  1. 自动配置的 **WebMvcAutoConfiguration**可能失效:
    WebMvcAutoConfiguration是Spring Boot自动配置Web MVC的类,当你使用WebMvcConfigurationSupport时,它可能不再生效,因为你自己提供了更高级别的配置。
  2. 静态资源的默认处理可能失效:
    Spring Boot默认提供了静态资源的处理,例如/static/路径下的资源会被映射到classpath下的/static/目录。使用WebMvcConfigurationSupport时,你可能需要手动配置静态资源处理,因为默认的可能会失效。
  3. 默认的 **ContentNegotiationStrategy**可能被替代:
    WebMvcConfigurationSupport中的configureContentNegotiation方法可以配置内容协商策略,这可能会替代Spring Boot默认的协商策略。
  4. 默认的 **MessageConverter**可能会失效:
    使用configureMessageConvertersextendMessageConverters方法可能会替代或扩展默认的消息转换器。如果你没有显式地配置自己的转换器列表,可能会丢失Spring Boot默认配置的一些消息转换器。
  5. 默认的 **HandlerExceptionResolver**可能会失效:
    configureHandlerExceptionResolversextendHandlerExceptionResolvers方法可以配置异常处理器,这可能会替代或扩展默认的异常处理器。
  6. 默认的 **ViewResolver**可能会失效:
    configureViewResolvers方法可以配置视图解析器,这可能会覆盖Spring Boot默认的视图解析器配置。

总的来说,使用WebMvcConfigurationSupport时,你需要更加谨慎地配置,确保你自己提供的配置不会完全覆盖Spring Boot的默认配置,以免造成意外的问题。通常情况下,建议在必要时使用WebMvcConfigurer接口进行轻量级的配置,而不是直接使用WebMvcConfigurationSupport

如何选择

WebMvcConfigurerWebMvcConfigurationSupport 都是用于配置 Spring MVC 的接口/类,但它们有不同的使用场景和适用范围。

WebMvcConfigurer:
  1. 轻量级配置:
  • WebMvcConfigurer 接口提供了一种轻量级的方式来进行 Spring MVC 的配置。如果你只需要进行一些简单的配置,比如添加拦截器、设置视图解析器、配置资源处理等,那么使用 WebMvcConfigurer 更为合适。
  1. 保留默认配置:
  • 使用 WebMvcConfigurer 时,Spring Boot 的默认配置仍然会生效,你可以在现有的配置基础上进行定制,而不是覆盖所有默认配置。
  1. 扩展特定功能:
  • 适用于只需要扩展某个特定功能而不是替代全部配置的场景。你可以选择性地实现 WebMvcConfigurer 接口中的方法,根据需要进行配置。
WebMvcConfigurationSupport:
  1. 深度定制:
  • WebMvcConfigurationSupport 提供了更为深度的自定义配置,你可以完全替代 Spring MVC 的默认配置。如果你需要对 Spring MVC 进行更深层次的定制,比如替换默认的异常处理器、配置全局消息转换器、完全自定义路径匹配规则等,那么可以使用 WebMvcConfigurationSupport
  1. 覆盖默认配置:
  • 使用 WebMvcConfigurationSupport 时,需要注意可能会覆盖 Spring Boot 默认的配置,因此需要谨慎地配置,确保你提供的配置不会意外地影响到默认配置。
  1. 全面替代:
  • 适用于需要全面替代 Spring MVC 配置的场景,特别是当你需要做一些非常定制化的配置时,可能需要使用 WebMvcConfigurationSupport

选择建议:

  • 如果只是进行一些轻量级的配置,或者是在现有的 Spring Boot 默认配置基础上进行定制,推荐使用 WebMvcConfigurer
  • 如果需要进行更深层次的自定义,甚至是完全替代 Spring MVC 的默认配置,才考虑使用 WebMvcConfigurationSupport

通常情况下,绝大多数项目可以通过实现 WebMvcConfigurer 接口来满足配置需求,而无需使用 WebMvcConfigurationSupport。只有在需要非常深度和全面的自定义时,才考虑使用 WebMvcConfigurationSupport

相关推荐
2401_857610038 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
java—大象1 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计
ApiHug2 小时前
ApiSmart x Qwen2.5-Coder 开源旗舰编程模型媲美 GPT-4o, ApiSmart 实测!
人工智能·spring boot·spring·ai编程·apihug
魔道不误砍柴功2 小时前
探秘Spring Boot中的@Conditional注解
数据库·spring boot·oracle
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
背水2 小时前
初识Spring
java·后端·spring
晴天飛 雪3 小时前
Spring Boot MySQL 分库分表
spring boot·后端·mysql
weixin_537590453 小时前
《Spring boot从入门到实战》第七章习题答案
数据库·spring boot·后端