Spring配置接口WebMvcConfigurer

一、概述

WebMvcConfigurerSpringMVC提供的一个配置回调接口,它定义了一系列用于自定义SpringMVC行为的方法,只需创建一个配置类实现该接口,并按需重写方法,即可轻松实现URL映射、拦截器注册、跨域配置等功能,无需编写繁琐的XML配置。

关键特性

  1. 无侵入性:仅需实现接口并重写需要的方法,不影响其他默认配置;
  2. 丰富的配置项:覆盖URL映射、拦截器、静态资源、视图解析器、跨域等核心场景;
  3. SpringBoot自动配置兼容:无需额外添加@EnableWebMvc注解(添加后会覆盖SpringBoot默认配置,需谨慎使用)。

二、接口源码

WebMvcConfigurer位于org.springframework.web.servlet.config.annotation包下,是一个基于Java8的接口,其中大部分方法都是default类型的,且都是空实现。因此只需要定义一个配置类实现WebMvcConfigurer接口,并重写相应的方法便可以定制SpringMVC的配置。

public interface WebMvcConfigurer {

    //HandlerMappings路径的匹配规则。
    default void configurePathMatch(PathMatchConfigurer configurer) {}
    
    //内容协商策略(一个请求路径返回多种数据格式)。
    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
    
    //处理异步请求。
    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
    
    //这个接口可以实现静态文件可以像Servlet一样被访问。
    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
    
    //添加格式化器或者转化器。
    default void addFormatters(FormatterRegistry registry) {}
    
    //添加 Spring MVC 生命周期拦截器,对请求进行拦截处理。
    default void addInterceptors(InterceptorRegistry registry) {}
    
    //添加或修改静态资源(例如图片,js,css等)映射;
    //Spring Boot 默认设置的静态资源文件夹就是通过重写该方法设置的。
    default void addResourceHandlers(ResourceHandlerRegistry registry) {}	
    
    //处理跨域请求。
    default void addCorsMappings(CorsRegistry registry) {}
    
    //主要用于实现无业务逻辑跳转,例如主页跳转,简单的请求重定向,错误页跳转等
    default void addViewControllers(ViewControllerRegistry registry) {}
    
    //配置视图解析器,将Controller返回的字符串(视图名称),转换为具体的视图进行渲染。
    default void configureViewResolvers(ViewResolverRegistry registry) {}
    
    //添加解析器以支持自定义控制器方法参数类型,实现该方法不会覆盖用于解析处理程序方法参数的内置支持;
    //要自定义内置的参数解析支持,同样可以通过RequestMappingHandlerAdapter直接配置RequestMappingHandlerAdapter。
    default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
    
    //添加处理程序来支持自定义控制器方法返回值类型。使用此选项不会覆盖处理返回值的内置支持;
    //要自定义处理返回值的内置支持,请直接配置RequestMappingHandlerAdapter。
    default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
    
    //用于配置默认的消息转换器(转换HTTP请求和响应)。
    default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
    
    //直接添加消息转换器,会关闭默认的消息转换器列表;
    //实现该方法即可在不关闭默认转换器的起提下,新增一个自定义转换器。
    default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
    
    //配置异常解析器。
    default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
    
    //扩展或修改默认的异常解析器列表。
    default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
    
}

SpringBoot项目中,可以通过以下2种形式定制SpringMVC

  • 扩展SpringMVC:如果SpringBootSpringMVC的自动配置不能满足当前的需要,可以通过自定义一个WebMvcConfigurer类型(实现WebMvcConfigurer接口)的配置类(标注@Configuration,但不标注@EnableWebMvc注解的类),来扩展SpringMVC。这样不但能够保留SpringBootSpringMVC的自动配置,享受SpringBoot自动配置带来的便利,还能额外增加自定义的SpringMVC配置。
  • 全面接管SpringMVC:在一些特殊情况下,可能需要抛弃SpringBootSpringMVC的全部自动配置,完全接管SpringMVC。此时可以自定义一个WebMvcConfigurer类型(实现WebMvcConfigurer接口)的配置类,并在该类上标注@EnableWebMvc注解,来实现完全接管SpringMVC

注意:完全接管SpringMVC后,SpringBootSpringMVC的自动配置将全部失效。

三、底层原理

要彻底掌握WebMvcConfigurer的使用,需理解其底层设计逻辑和Spring加载配置的核心流程。本节从核心类关系、加载流程、关键源码拆解三个维度,还原WebMvcConfigurer的生效机制。

3.1 非侵入式配置

SpringMVC的核心配置逻辑封装在WebMvcConfigurationSupport类中(MVC配置的“底层骨架”),而WebMvcConfigurerSpring为开发者预留的配置扩展接口——它采用「回调模式」设计,开发者通过实现接口定义自定义规则,Spring初始化MVC容器时会自动回调这些规则,将自定义配置合并到默认配置中。

3.1.1 核心类关系

类/接口 核心定位 关键作用
WebMvcConfigurer 配置扩展接口 定义URL映射、拦截器、静态资源等配置方法,无默认实现,仅作为扩展点
WebMvcConfigurerComposite 配置组合类 聚合容器中所有WebMvcConfigurer实现类,统一回调所有自定义配置方法
WebMvcConfigurationSupport MVC配置基类 实现SpringMVC核心组件(HandlerMapping、HandlerAdapter、ViewResolver)的创建逻辑
DelegatingWebMvcConfiguration 配置代理类 继承WebMvcConfigurationSupport,注入WebMvcConfigurerComposite,将自定义配置融入核心配置
WebMvcAutoConfiguration SpringBoot自动配置类 未加@EnableWebMvc时生效,自动加载默认MVC配置,并聚合自定义WebMvcConfigurer

3.1.2 优势

  • 非侵入性:无需修改SpringMVC核心代码,仅通过实现接口即可扩展;
  • 灵活性:按需重写方法,无需覆盖全部配置;
  • 兼容性:与SpringBoot“约定大于配置”理念兼容,默认配置与自定义配置可共存。

3.2 加载流程

SpringBoot启动时,WebMvcConfigurer的配置会经历「自动配置触发→配置类扫描→配置聚合→核心组件初始化」四个阶段,以下结合源码拆解关键步骤:

步骤1:自动配置触发

SpringBoot启动时,WebMvcAutoConfigurationMVC自动配置类)会被@EnableAutoConfiguration扫描并加载,其核心条件注解决定了默认配置的生效规则:

// WebMvcAutoConfiguration核心源码(简化版)
@AutoConfiguration(
    after = {DispatcherServletAutoConfiguration.class, 
             TaskExecutionAutoConfiguration.class,
             ValidationAutoConfiguration.class}
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
// 关键条件:容器中不存在WebMvcConfigurationSupport时生效(即未加@EnableWebMvc)
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebMvcAutoConfiguration {

    // 注入容器中所有自定义的WebMvcConfigurer实现类
    @Autowired(required = false)
    private List<WebMvcConfigurer> mvcConfigurers = Collections.emptyList();

    // 创建WebMvcConfigurerComposite,聚合所有自定义配置
    @Bean
    public WebMvcConfigurerComposite mvcConfigurerComposite() {
        WebMvcConfigurerComposite composite = new WebMvcConfigurerComposite();
        composite.addMvcConfigurers(this.mvcConfigurers);
        return composite;
    }

    // 内部配置类:将自定义配置融入默认配置
    @Configuration(proxyBeanMethods = false)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

        private final WebMvcConfigurerComposite configurers;

        // 注入聚合后的配置类
        public WebMvcAutoConfigurationAdapter(WebMvcConfigurerComposite configurers) {
            this.configurers = configurers;
        }

        // 回调自定义配置方法(如addInterceptors、addResourceHandlers)
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            this.configurers.addInterceptors(registry);
        }

        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            this.configurers.addResourceHandlers(registry);
        }
    }
}

关键说明:

  • @ConditionalOnMissingBean(WebMvcConfigurationSupport.class):若容器中存在WebMvcConfigurationSupport(如加了@EnableWebMvc),则WebMvcAutoConfiguration失效,默认配置被覆盖;
  • mvcConfigurers:自动注入所有加了@ConfigurationWebMvcConfigurer实现类(开发者自定义的配置类);
  • WebMvcConfigurerComposite:将多个自定义配置类聚合,避免多配置类冲突。

步骤2:配置类扫描与聚合

WebMvcConfigurerCompositeSpring处理多WebMvcConfigurer的核心,其核心逻辑是聚合所有配置类,并统一回调方法:

// WebMvcConfigurerComposite核心源码
public class WebMvcConfigurerComposite implements WebMvcConfigurer {
    private final List<WebMvcConfigurer> delegates = new ArrayList<>();

    // 添加所有自定义WebMvcConfigurer
    public void addMvcConfigurers(@Nullable List<WebMvcConfigurer> configurers) {
        if (configurers != null) {
            this.delegates.addAll(configurers);
        }
    }

    // 回调所有自定义配置类的addInterceptors方法
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addInterceptors(registry);
        }
    }

    // 回调所有自定义配置类的addResourceHandlers方法
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addResourceHandlers(registry);
        }
    }

    // 其他方法(addViewControllers、addCorsMappings等)同理
}

关键说明:

  • 多配置类场景下,Spring会遍历所有WebMvcConfigurer实现类,依次执行其重写方法;
  • 可通过@Order注解指定配置类优先级,优先级高的配置类先执行(如@Order(1)比@Order(2)先执行)。

步骤3:核心组件初始化

若添加了@EnableWebMvcWebMvcAutoConfiguration失效,此时DelegatingWebMvcConfiguration会接管配置流程:

// @EnableWebMvc核心源码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 导入DelegatingWebMvcConfiguration,覆盖默认配置
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {}

// DelegatingWebMvcConfiguration核心源码
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    // 注入所有自定义 WebMvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addMvcConfigurers(configurers);
        }
    }

    // 重写WebMvcConfigurationSupport的方法,融入自定义配置
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        this.configurers.addInterceptors(registry);
    }

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        this.configurers.addResourceHandlers(registry);
    }
}

关键说明:

  • @EnableWebMvc会导入DelegatingWebMvcConfiguration,而DelegatingWebMvcConfiguration继承WebMvcConfigurationSupport
  • 由于WebMvcAutoConfiguration@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)条件,此时自动配置类失效,SpringBoot默认的静态资源路径、视图解析器等配置全部被覆盖。

步骤4:自定义配置生效

无论是否加@EnableWebMvcSpring最终都会通过WebMvcConfigurationSupport创建核心组件,并将自定义配置融入其中:

  • 拦截器:通过InterceptorRegistry注册的拦截器,最终被添加到RequestMappingHandlerMapping(处理@RequestMapping的核心HandlerMapping);
  • 静态资源:ResourceHandlerRegistry的配置会生成ResourceHandlerMapping,处理静态资源请求;
  • 视图解析器:ViewResolverRegistry的配置会合并到ViewResolver列表,优先级由order属性决定。

3.3 源码落地

以最常用的addInterceptors为例,拆解自定义拦截器如何被Spring加载并生效:

第一步:开发者自定义配置类

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
    }
}

第二步:WebMvcConfigurerComposite聚合配置

SpringWebMvcConfig注入WebMvcConfigurerCompositedelegates列表,调用addInterceptors时遍历执行:

// WebMvcConfigurerComposite的addInterceptors方法
@Override
public void addInterceptors(InterceptorRegistry registry) {
    for (WebMvcConfigurer delegate : this.delegates) {
        delegate.addInterceptors(registry); // 执行开发者自定义的addInterceptors
    }
}

第三步:WebMvcConfigurationSupport初始化拦截器

// WebMvcConfigurationSupport核心源码
protected final Object[] getInterceptors() {
    if (this.interceptors == null) {
        InterceptorRegistry registry = new InterceptorRegistry();
        // 回调addInterceptors方法,加载自定义拦截器
        addInterceptors(registry);
        // 添加Spring内置拦截器(如ConversionServiceExposingInterceptor)
        registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
        registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
        // 封装拦截器为HandlerInterceptor对象
        this.interceptors = registry.getInterceptors().toArray();
    }
    return this.interceptors;
}

// 将拦截器注册到RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
    // 设置拦截器
    mapping.setInterceptors(getInterceptors());
    // 其他配置...
    return mapping;
}

第四步:拦截器生效

DispatcherServlet处理请求时,会通过RequestMappingHandlerMapping获取拦截器链,依次执行preHandlepostHandleafterCompletion方法。

四、核心方法实战

下面通过“配置类+代码示例”的形式,讲解开发中最常用的6个核心方法。

4.1 视图映射

当需要将某个URL直接映射到指定视图(无需通过Controller)时,可使用addViewControllers方法,简化无业务逻辑的页面跳转配置。

示例:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration // 标记为配置类,Spring会自动扫描并加载
public class WebMvcConfig implements WebMvcConfigurer {

   // 配置URL与视图的直接映射
   @Override
   public void addViewControllers(ViewControllerRegistry registry) {
       // 访问"/home"直接跳转到"home.html"视图
       registry.addViewController("/home").setViewName("home");
       // 访问"/login"直接跳转到"login.html",并设置HTTP状态码为200
       registry.addViewController("/login").setViewName("login").setStatus(200);
       // 访问根路径"/"跳转到"index.html"
       registry.addViewController("/").setViewName("index");
   }
}

场景:适合登录页、首页、404页面等无业务逻辑的静态页面跳转,避免创建空的Controller

4.2 自定义拦截器

拦截器是SpringMVC中实现“请求前置处理、后置处理”的核心组件(如登录校验、日志记录)。通过addInterceptors方法可注册自定义拦截器,并指定拦截/排除的URL规则。

步骤1:自定义拦截器

先创建一个实现HandlerInterceptor接口的拦截器类:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

// 登录校验拦截器
public class LoginInterceptor implements HandlerInterceptor {

   // 请求处理前执行(如校验登录状态)

   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                  Object handler) throws Exception {
       // 从Session中获取用户信息
       Object user = request.getSession().getAttribute("loginUser");
       if (user == null) {
           // 未登录,重定向到登录页
           response.sendRedirect("/login");
           return false; // 拦截请求,不继续向下执行
       }
       // 已登录,放行请求
       return true;
   }
}

步骤2:注册拦截器

WebMvcConfig中重写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 WebMvcConfig implements WebMvcConfigurer {

   // 注册拦截器
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(new LoginInterceptor()) // 注册自定义拦截器
               .addPathPatterns("/**") // 拦截所有URL
               .excludePathPatterns( // 排除不需要拦截的URL
                       "/login", "/register", // 登录、注册页
                       "/static/**", // 静态资源(CSS/JS/图片)
                       "/error" // 错误页
               );
   }
}

注意:

  • addPathPatterns:指定需要拦截的URL(支持通配符,如/**表示所有路径);
  • excludePathPatterns:指定无需拦截的URL(避免拦截静态资源或公开页面)。

4.3 静态资源访问规则

SpringBoot默认将classpath:/static/classpath:/public/等目录作为静态资源根目录,但实际开发中可能需要自定义静态资源路径(如映射到外部磁盘目录),此时可通过addResourceHandlers实现。

示例1:自定义类路径下的静态资源映射

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   // 访问"/assets/**"映射到"classpath:/my-static/"目录
   registry.addResourceHandler("/assets/**")
           .addResourceLocations("classpath:/my-static/")
           // 缓存控制(可选):设置静态资源缓存时间为3600秒
           .setCachePeriod(3600);
}

示例2:映射外部磁盘目录的静态资源

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   // 访问"/upload/**"映射到"D:/file-upload/"本地磁盘目录
   registry.addResourceHandler("/upload/**")
           .addResourceLocations("file:D:/file-upload/"); // 注意前缀"file:"
}

场景:适合文件上传后预览(如图片、文档),或需要将静态资源放在外部磁盘的场景。

4.4 跨域请求

前后端分离项目中,前端(如VueReact)与后端(SpringBoot)通常运行在不同端口,会遇到跨域问题。通过addCorsMappings可快速配置跨域规则,允许指定域名的请求访问后端接口。

示例

@Override
public void addCorsMappings(CorsRegistry registry) {
   registry.addMapping("/api/**") // 对"/api"前缀的所有接口生效
           .allowedOrigins("http://localhost:8080") // 允许前端域名(如Vue项目运行地址)
           .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的HTTP方法
           .allowedHeaders("*") // 允许的请求头(如Token)
           .allowCredentials(true) // 是否允许携带Cookie
           .maxAge(3600); // 预检请求(OPTIONS)的缓存时间(秒)
}

说明

  • allowedOrigins("*"):允许所有域名跨域(生产环境不推荐,需指定具体域名);
  • allowCredentials(true):若需要前端携带Cookie,需设置为true,且allowedOrigins不能为*

4.5 视图解析器

视图解析器用于将Controller返回的“视图名”解析为实际的视图文件(如JSPHTMLThymeleaf模板)。通过configureViewResolvers可自定义视图解析器的前缀、后缀或优先级。

示例:配置Thymeleaf视图解析器

import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
    // 配置JSP视图解析器(若使用JSP)
    InternalResourceViewResolver jspResolver = new InternalResourceViewResolver();
    jspResolver.setPrefix("/WEB-INF/views/"); // 视图文件前缀
    jspResolver.setSuffix(".jsp"); // 视图文件后缀
    jspResolver.setViewClass(JstlView.class);
    jspResolver.setOrder(1); // 优先级(数字越小优先级越高)
    // 注册视图解析器
    registry.viewResolver(jspResolver);
    // 若使用Thymeleaf,SpringBoot会自动配置,无需额外代码
    // 若需自定义Thymeleaf前缀,可通过application.properties配置:
    // spring.thymeleaf.prefix=classpath:/templates/my-pages/
}

场景:适合使用JSP作为视图模板的项目,或需要多视图解析器共存的场景。

4.6 默认Servlet处理

当请求无法被SpringMVCDispatcherServlet处理时(如静态资源),可通过configureDefaultServletHandling将请求交给容器默认的Servlet处理,避免404错误。

示例

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {

   // 启用默认Servlet处理:未被Spring MVC处理的请求交给容器默认Servlet
   configurer.enable();
}

注意:该方法通常与addResourceHandlers配合使用,确保静态资源能被正确访问。

五、注意事项

  1. 避免滥用@EnableWebMvc注解

若在配置类上添加@EnableWebMvc,会完全覆盖SpringBoot的默认MVC配置(如默认静态资源路径、Thymeleaf自动配置),导致自动配置失效。仅当需要完全自定义MVC配置时才使用。

  1. 配置类需加@Configuration注解

必须在实现WebMvcConfigurer的类上添加@Configuration注解,否则Spring无法识别该配置类。

  1. 多配置类的优先级

若存在多个实现WebMvcConfigurer的配置类,可通过@Order注解指定优先级(如@Order(1)@Order(2)优先级高),优先级高的配置会先生效。

  1. 拦截器与跨域的顺序问题

跨域预检请求(OPTIONS)会先于拦截器执行,若拦截器中未排除OPTIONS请求,可能导致跨域配置失效。需在addInterceptors中通过excludePathPatterns排除OPTIONS请求。

六、总结

WebMvcConfigurer是SpringMVC中灵活且强大的配置工具,通过实现该接口并按需重写方法,可轻松完成URL映射、拦截器、静态资源、跨域等核心配置,替代传统的XML配置方式。其核心优势在于“按需配置、无侵入性、与SpringBoot自动配置兼容”,是前后端分离、传统MVC项目开发中的必备技能。

如果在使用过程中遇到配置不生效、拦截器冲突等问题,可优先检查:配置类是否加@Configuration、是否误加@EnableWebMvc、URL规则是否正确(通配符是否匹配)。

七、常见问题

问题1:为什么加了@EnableWebMvc后静态资源访问失效?

答:@EnableWebMvc导入DelegatingWebMvcConfiguration(继承WebMvcConfigurationSupport),导致WebMvcAutoConfiguration失效——而SpringBoot默认的静态资源配置(如classpath:/static/)是在WebMvcAutoConfiguration中定义的,因此默认静态资源路径失效,需手动通过addResourceHandlers配置。

问题2:多WebMvcConfigurer配置类的执行顺序?

答:Spring注入List时,会根据@Order注解或Ordered接口排序(数字越小优先级越高),WebMvcConfigurerComposite会按此顺序遍历执行addInterceptors等方法。

问题3:为什么拦截器排除OPTIONS请求才能解决跨域问题?

答:跨域预检请求(OPTIONS)由CorsFilter处理,而拦截器执行优先级高于CorsFilter——若拦截器未排除OPTIONS请求,会在预检请求阶段拦截请求,导致CorsFilter无法生效,跨域配置失效。

posted @ 2025-12-04 14:45  夏尔_717  阅读(10)  评论(0)    收藏  举报