Spring MVC 配置器:WebMvcConfigurer 详解、应用场景和示例代码

一、概述

1.1 什么是 WebMvcConfigurer?

WebMvcConfigurer 是 Spring MVC 提供的一个接口,允许开发者在不使用 XML 配置的情况下,通过 Java 配置方式自定义 Spring MVC 的行为。它提供了多个默认方法,用于配置各种 MVC 相关组件。

1.2 核心作用

  • 替代传统的 XML 配置(如 mvc-config.xml)

  • 以类型安全的方式配置 Spring MVC

  • 提供细粒度的 MVC 配置控制

  • 支持跨域、拦截器、视图解析器等配置

二、核心配置方法详解

2.1 跨域配置(CORS)

java 复制代码
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 全局 CORS 配置
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000", "https://example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
        
        // 多个 CORS 配置
        registry.addMapping("/public/**")
                .allowedOrigins("*")
                .allowedMethods("GET");
    }
}

2.2 拦截器配置

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private AuthenticationInterceptor authInterceptor;
    @Autowired
    private LoggingInterceptor loggingInterceptor;
    
    /**
     * 添加拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 1. 日志拦截器 - 拦截所有请求
        registry.addInterceptor(loggingInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/health", "/favicon.ico");
        
        // 2. 认证拦截器 - 拦截 API 请求
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/login", "/api/auth/register");
        
        // 3. 顺序配置(可选,按添加顺序执行)
        // registry.addInterceptor(new FirstInterceptor()).order(1);
        // registry.addInterceptor(new SecondInterceptor()).order(2);
    }
}

/**
 * 自定义拦截器示例
 */
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (!isValidToken(token)) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, 
                          HttpServletResponse response, 
                          Object handler, 
                          ModelAndView modelAndView) {
        // 请求处理后调用
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, 
                               Exception ex) {
        // 请求完成后的清理工作
    }
}

2.3 视图控制器和视图解析器

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 配置视图控制器(用于简单的页面映射)
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 将URL路径映射到视图名称
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/error/403").setViewName("error/403");
        
        // 设置状态码
        registry.addStatusController("/not-found", HttpStatus.NOT_FOUND);
    }
    
    /**
     * 配置视图解析器
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // 使用 Thymeleaf
        registry.jsp("/WEB-INF/views/", ".jsp");
        
        // 或者配置多个解析器
        // registry.enableContentNegotiation(new MappingJackson2JsonView());
        // registry.freeMarker().prefix("classpath:/templates/").suffix(".ftl");
    }
}

2.4 静态资源处理

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 配置静态资源映射
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 1. WebJars 资源
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
        
        // 2. 自定义静态资源
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/", "file:/opt/app/static/")
                .setCachePeriod(3600) // 缓存1小时
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS));
        
        // 3. 上传文件访问路径
        registry.addResourceHandler("/uploads/**")
                .addResourceLocations("file:/var/uploads/");
        
        // 4. 图标文件
        registry.addResourceHandler("/favicon.ico")
                .addResourceLocations("classpath:/static/favicon.ico");
    }
}

2.5 消息转换器配置

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 配置消息转换器
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 1. 添加 FastJson 转换器
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
            SerializerFeature.PrettyFormat,
            SerializerFeature.WriteMapNullValue,
            SerializerFeature.WriteDateUseDateFormat
        );
        fastConverter.setFastJsonConfig(fastJsonConfig);
        fastConverter.setSupportedMediaTypes(
            Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8)
        );
        converters.add(0, fastConverter);
        
        // 2. 添加 XML 转换器
        converters.add(new Jaxb2RootElementHttpMessageConverter());
    }
    
    /**
     * 扩展消息转换器(不覆盖默认转换器)
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 修改现有转换器的配置
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                MappingJackson2HttpMessageConverter jsonConverter = 
                    (MappingJackson2HttpMessageConverter) converter;
                ObjectMapper objectMapper = jsonConverter.getObjectMapper();
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
            }
        }
    }
}

2.6 参数解析器

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 添加自定义参数解析器
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        // 1. 自定义用户信息解析器
        resolvers.add(new CurrentUserArgumentResolver());
        
        // 2. Pageable 参数解析器
        resolvers.add(new PageableHandlerMethodArgumentResolver());
        
        // 3. 日期参数解析器
        resolvers.add(new DateArgumentResolver());
    }
}

/**
 * 自定义参数解析器示例
 */
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
    
    @Autowired
    private UserService userService;
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class) 
            && parameter.getParameterType().equals(User.class);
    }
    
    @Override
    public Object resolveArgument(MethodParameter parameter, 
                                 ModelAndViewContainer mavContainer,
                                 NativeWebRequest webRequest, 
                                 WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        String token = request.getHeader("Authorization");
        return userService.getUserByToken(token);
    }
}

/**
 * 自定义注解
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}

2.7 返回值处理器

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 添加自定义返回值处理器
     */
    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        handlers.add(new GlobalResponseWrapper());
    }
}

/**
 * 全局响应包装器
 */
@Component
public class GlobalResponseWrapper implements HandlerMethodReturnValueHandler {
    
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return !returnType.getParameterType().equals(ResponseEntity.class);
    }
    
    @Override
    public void handleReturnValue(Object returnValue,
                                 MethodParameter returnType,
                                 ModelAndViewContainer mavContainer,
                                 NativeWebRequest webRequest) throws Exception {
        
        ApiResponse<Object> response = ApiResponse.success(returnValue);
        
        // 设置响应
        HttpServletResponse servletResponse = webRequest.getNativeResponse(HttpServletResponse.class);
        servletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        
        ObjectMapper mapper = new ObjectMapper();
        servletResponse.getWriter().write(mapper.writeValueAsString(response));
        mavContainer.setRequestHandled(true);
    }
}

/**
 * 统一响应格式
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;
    
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data, System.currentTimeMillis());
    }
}

2.8 路径匹配配置

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 配置路径匹配选项
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // 1. 是否使用后缀模式匹配
        configurer.setUseSuffixPatternMatch(false);
        
        // 2. 是否使用尾随斜杠匹配
        configurer.setUseTrailingSlashMatch(true);
        
        // 3. 注册自定义路径匹配器
        configurer.addPathPrefix("/api", 
            HandlerTypePredicate.forAnnotation(RestController.class));
        
        // 4. 配置 URL 路径帮助器
        configurer.setUrlPathHelper(new UrlPathHelper());
        
        // 5. 配置路径匹配器
        AntPathMatcher pathMatcher = new AntPathMatcher();
        pathMatcher.setCaseSensitive(false);
        configurer.setPathMatcher(pathMatcher);
    }
}

2.9 异步支持配置

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 配置异步请求支持
     */
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // 设置超时时间(毫秒)
        configurer.setDefaultTimeout(30000);
        
        // 设置线程池
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(100);
        taskExecutor.setThreadNamePrefix("async-");
        taskExecutor.initialize();
        configurer.setTaskExecutor(taskExecutor);
        
        // 设置可调用的拦截器
        configurer.registerCallableInterceptors(new TimeoutCallableProcessingInterceptor());
        
        // 设置 DeferredResult 拦截器
        configurer.registerDeferredResultInterceptors(new DeferredResultProcessingInterceptor() {
            @Override
            public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) {
                // 在并发处理前调用
            }
        });
    }
}

2.10 内容协商配置

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 配置内容协商
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 1. 基于 URL 后缀
        configurer.favorPathExtension(true)
                  .favorParameter(false)
                  .parameterName("format")
                  .ignoreAcceptHeader(false)
                  .useRegisteredExtensionsOnly(false)
                  .defaultContentType(MediaType.APPLICATION_JSON)
                  .mediaType("json", MediaType.APPLICATION_JSON)
                  .mediaType("xml", MediaType.APPLICATION_XML)
                  .mediaType("html", MediaType.TEXT_HTML);
        
        // 2. 基于请求头
        configurer.defaultContentTypeStrategy(new HeaderContentNegotiationStrategy());
    }
}

2.11 验证器和格式化

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    /**
     * 添加自定义验证器
     */
    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource());
        return validator;
    }
    
    /**
     * 添加格式化器
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 日期格式化
        registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
        registry.addFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
        
        // 自定义格式化器
        registry.addFormatterForFieldType(LocalDate.class, new TemporalAccessorFormatter());
        
        // 添加转换器
        registry.addConverter(new StringToUserConverter());
    }
    
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = 
            new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}

/**
 * 自定义转换器示例
 */
@Component
public class StringToUserConverter implements Converter<String, User> {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public User convert(String userId) {
        return userRepository.findById(Long.parseLong(userId))
                .orElseThrow(() -> new IllegalArgumentException("Invalid user ID"));
    }
}

三、实际应用场景

3.1 微服务 API 网关配置

java 复制代码
@Configuration
public class ApiGatewayConfig implements WebMvcConfigurer {
    
    @Autowired
    private JwtTokenInterceptor jwtInterceptor;
    @Autowired
    private RateLimitInterceptor rateLimitInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 1. 速率限制拦截器
        registry.addInterceptor(rateLimitInterceptor)
                .addPathPatterns("/api/**")
                .order(1);
        
        // 2. JWT 认证拦截器
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/**")
                .order(2);
        
        // 3. 日志拦截器
        registry.addInterceptor(new RequestLogInterceptor())
                .addPathPatterns("/**")
                .order(3);
    }
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .maxAge(3600);
    }
    
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 统一响应格式
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        converter.setObjectMapper(objectMapper);
        converters.add(converter);
    }
}

3.2 前后端分离项目配置

java 复制代码
@Configuration
public class FrontendBackendSeparationConfig implements WebMvcConfigurer {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 前端静态资源
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(31536000); // 一年缓存
        
        // 单页应用:所有未匹配的路径返回 index.html
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/index.html")
                .resourceChain(true)
                .addResolver(new PathResourceResolver() {
                    @Override
                    protected Resource getResource(String resourcePath, 
                                                  Resource location) throws IOException {
                        Resource requestedResource = location.createRelative(resourcePath);
                        return requestedResource.exists() && requestedResource.isReadable() 
                            ? requestedResource 
                            : new ClassPathResource("/static/index.html");
                    }
                });
    }
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:8080")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .maxAge(3600);
    }
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 重定向到前端路由
        registry.addViewController("/").setViewName("forward:/index.html");
        registry.addViewController("/login").setViewName("forward:/index.html");
        registry.addViewController("/dashboard/**").setViewName("forward:/index.html");
    }
}

3.3 文件上传下载配置

java 复制代码
@Configuration
public class FileUploadConfig implements WebMvcConfigurer {
    
    /**
     * 配置文件上传
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 文件访问路径映射
        registry.addResourceHandler("/files/**")
                .addResourceLocations("file:/var/uploads/")
                .setCachePeriod(3600);
    }
    
    /**
     * 配置文件上传解析器
     */
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(10485760); // 10MB
        resolver.setMaxUploadSizePerFile(5242880); // 5MB
        resolver.setDefaultEncoding("UTF-8");
        resolver.setResolveLazily(true); // 延迟解析
        return resolver;
    }
    
    /**
     * 添加参数解析器处理文件上传
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new MultipartFileArgumentResolver());
    }
}

/**
 * 文件上传参数解析器
 */
@Component
public class MultipartFileArgumentResolver implements HandlerMethodArgumentResolver {
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(UploadFile.class) 
            && (parameter.getParameterType().equals(MultipartFile.class) 
                || parameter.getParameterType().equals(MultipartFile[].class));
    }
    
    @Override
    public Object resolveArgument(MethodParameter parameter, 
                                 ModelAndViewContainer mavContainer,
                                 NativeWebRequest webRequest, 
                                 WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        if (request instanceof MultipartHttpServletRequest) {
            MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
            UploadFile uploadFile = parameter.getParameterAnnotation(UploadFile.class);
            String paramName = uploadFile.value();
            
            if (parameter.getParameterType().equals(MultipartFile.class)) {
                return multipartRequest.getFile(paramName);
            } else if (parameter.getParameterType().equals(MultipartFile[].class)) {
                return multipartRequest.getFiles(paramName);
            }
        }
        return null;
    }
}

四、高级配置和最佳实践

4.1 多配置类组合

java 复制代码
// 主配置类
@Configuration
@EnableWebMvc
@Import({ SecurityConfig.class, SwaggerConfig.class, CacheConfig.class })
public class MainWebConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 全局 CORS 配置
    }
}

// 安全相关配置
@Configuration
public class SecurityConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 安全拦截器
    }
}

// API 文档配置
@Configuration
public class SwaggerConfig implements WebMvcConfigurer {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // Swagger UI 资源
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // API 文档重定向
        registry.addRedirectViewController("/api-docs", "/swagger-ui.html");
    }
}

4.2 条件化配置

java 复制代码
@Configuration
public class ConditionalWebConfig implements WebMvcConfigurer {
    
    @Value("${app.cors.enabled:false}")
    private boolean corsEnabled;
    
    @Value("${app.cache.static-resources:true}")
    private boolean cacheStaticResources;
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        if (corsEnabled) {
            registry.addMapping("/api/**")
                    .allowedOrigins("*")
                    .allowedMethods("*");
        }
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        ResourceHandlerRegistration registration = registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
        
        if (cacheStaticResources) {
            registration.setCachePeriod(31536000) // 一年
                        .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
        }
    }
}

4.3 性能优化配置

java 复制代码
@Configuration
public class PerformanceConfig implements WebMvcConfigurer {
    
    /**
     * 配置 GZip 压缩
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorParameter(false)
                  .ignoreAcceptHeader(false)
                  .defaultContentType(MediaType.APPLICATION_JSON);
    }
    
    /**
     * 启用 HTTP 缓存
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(31536000)
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic())
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }
    
    /**
     * 配置异步处理
     */
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(30000);
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-executor-");
        executor.initialize();
        configurer.setTaskExecutor(executor);
    }
}

五、常见问题与解决方案

5.1 配置冲突问题

java 复制代码
@Configuration
public class ConflictResolutionConfig implements WebMvcConfigurer {
    
    /**
     * 解决多个配置类冲突
     * 使用 @Order 注解指定优先级
     */
    @Configuration
    @Order(1)
    public static class PrimaryConfig implements WebMvcConfigurer {
        // 高优先级配置
    }
    
    @Configuration
    @Order(2)
    public static class SecondaryConfig implements WebMvcConfigurer {
        // 低优先级配置
    }
}

5.2 静态资源访问404

java 复制代码
@Configuration
public class StaticResourceConfig implements WebMvcConfigurer {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 正确配置静态资源路径
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("classpath:/static/", "file:/opt/app/static/")
                .setCachePeriod(3600);
        
        // 确保没有拦截器拦截静态资源
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/resources/**", "/static/**");
    }
}

5.3 跨域配置不生效

java 复制代码
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 确保路径匹配正确
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(true)  // 如果需要凭证
                .maxAge(3600);
    }
    
    /**
     * 同时配置 Filter 级别的 CORS(如果需要)
     */
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:3000");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(0); // 设置最高优先级
        return bean;
    }
}

六、总结

6.1 WebMvcConfigurer 的优势

  1. 类型安全:Java 配置相比 XML 更加类型安全

  2. 灵活性强:可以根据条件动态配置

  3. 可维护性高:配置逻辑集中,便于管理

  4. 功能全面:覆盖 Spring MVC 所有配置需求

  5. 易于测试:配置类易于单元测试

6.2 选择建议

  • 简单项目 :使用 @EnableWebMvc + WebMvcConfigurer

  • 复杂项目:拆分多个配置类,按功能模块组织

  • Spring Boot 项目 :使用 WebMvcConfigurer 而非 WebMvcConfigurationSupport

  • 需要完全控制 :继承 WebMvcConfigurationSupport

6.3 注意事项

  1. 避免在多个配置类中重复配置同一功能

  2. 注意配置的加载顺序

  3. 生产环境需要合理配置缓存策略

  4. 安全相关的配置要谨慎

  5. 定期审查和优化配置

相关推荐
大阿明4 小时前
Spring Boot(快速上手)
java·spring boot·后端
bearpping4 小时前
Java进阶,时间与日期,包装类,正则表达式
java
邵奈一4 小时前
清明纪念·时光信笺——项目运行指南
java·实战·项目
sunwenjian8864 小时前
Java进阶——IO 流
java·开发语言·python
sinat_255487815 小时前
读者、作家 Java集合学习笔记
java·笔记·学习
皮皮林5515 小时前
如何画出一张优秀的架构图?(老鸟必备)
java
百锦再5 小时前
Java 并发编程进阶,从线程池、锁、AQS 到并发容器与性能调优全解析
java·开发语言·jvm·spring·kafka·tomcat·maven
森林猿5 小时前
java-modbus-读取-modbus4j
java·网络·python
tobias.b5 小时前
计算机基础知识-数据结构
java·数据结构·考研
reembarkation5 小时前
光标在a-select,鼠标已经移出,下拉框跟随页面滚动
java·数据库·sql