题目详细答案
方式一:使用@Configuration
类配置
可以通过创建一个带有@Configuration
注解的类并继承WebMvcConfigurer
接口来扩展 Spring MVC 的配置。WebMvcConfigurer
接口包含许多方法,可以用来定制 Spring MVC 的行为。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
// 配置跨域请求
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
// 配置静态资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
// 配置视图控制器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyCustomInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/static/**");
}
}
方式二:实现特定接口
除了WebMvcConfigurer
接口,Spring MVC 还提供了其他一些接口和类,可以用来定制特定的功能。可以实现HandlerInterceptor
接口来自定义拦截器。
自定义拦截器
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyCustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求处理之前执行的逻辑
System.out.println("Pre Handle method is Calling");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后但在视图渲染之前执行的逻辑
System.out.println("Post Handle method is Calling");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
// 在整个请求完成之后,即视图渲染之后执行的逻辑
System.out.println("Request and Response is completed");
}
}
然后在配置类中注册这个拦截器:
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Autowired
private MyCustomInterceptor myCustomInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myCustomInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/static/**");
}
}
方式三:使用注解
你还可以通过使用注解来定制 Spring MVC 的一些功能。使用@ControllerAdvice
和@ExceptionHandler
来全局处理异常。
全局异常处理
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", ex.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
方式四:使用@Bean
注解
你可以在配置类中使用@Bean
注解来注册一些 Spring MVC 的组件,例如ViewResolver
、MessageConverter
等。
自定义ViewResolver
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
public class MyViewResolverConfig {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Spring MVC 配置方式详解
Spring MVC 提供了多种灵活的配置方式,可以满足不同场景下的定制需求。下面我将详细介绍四种主要的配置方式及其应用场景。
方式一:使用 @Configuration
类配置(推荐)
这是最常用且功能最全面的配置方式,通过实现 WebMvcConfigurer
接口来定制 Spring MVC。
完整配置示例
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 跨域配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://example.com", "http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
// 静态资源配置
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/", "file:/opt/app/static/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new EncodedResourceResolver());
// 配置Swagger UI静态资源
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
}
// 视图控制器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/login").setViewName("login");
registry.addRedirectViewController("/docs", "/swagger-ui.html");
}
// 拦截器配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/error");
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/admin/**");
}
// 消息转换器
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(new JavaTimeModule());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
优势
- 集中管理所有 MVC 配置
- 类型安全,编译时检查
- 支持自动装配其他Bean
方式二:实现特定接口
适合针对特定功能进行定制,如拦截器、参数解析器等。
自定义拦截器完整实现
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final AuthService authService;
public AuthInterceptor(AuthService authService) {
this.authService = authService;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (!authService.validateToken(token)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid token");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 可添加请求处理后的通用逻辑
if (modelAndView != null) {
modelAndView.addObject("timestamp", Instant.now());
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 资源清理等收尾工作
Metrics.recordRequestCompletion(request, response);
}
}
注册拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
public InterceptorConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**");
}
}
方式三:使用注解配置
全局异常处理进阶版
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestControllerAdvice(basePackages = "com.example.api")
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleBusinessError(BusinessException ex) {
return new ErrorResponse("BUSINESS_ERROR", ex.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneralError(Exception ex) {
return new ErrorResponse("INTERNAL_ERROR", "系统繁忙,请稍后再试");
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidationError(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList());
return new ErrorResponse("VALIDATION_ERROR", errors);
}
record ErrorResponse(String code, Object message) {}
}
方式四:使用 @Bean
配置
完整视图解析器配置
@Configuration
public class ViewConfig {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer configurer = new TilesConfigurer();
configurer.setDefinitions("/WEB-INF/tiles/tiles-definitions.xml");
configurer.setCheckRefresh(true);
return configurer;
}
@Bean
public ViewResolver tilesViewResolver() {
TilesViewResolver resolver = new TilesViewResolver();
resolver.setOrder(1); // 在内部资源视图解析器之前
return resolver;
}
}
自定义消息转换器
@Configuration
public class MessageConverterConfig {
@Bean
public MappingJackson2HttpMessageConverter customJacksonConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
converter.setSupportedMediaTypes(Arrays.asList(
MediaType.APPLICATION_JSON,
MediaType.TEXT_PLAIN
));
return converter;
}
}
最佳实践建议
- 配置方式选择:
-
- 大多数情况下优先使用方式一(
WebMvcConfigurer
) - 特定功能(如拦截器)使用方式二
- 全局异常处理使用方式三
- 需要精细控制组件时使用方式四
- 大多数情况下优先使用方式一(
- 配置拆分原则:
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 基础MVC配置
}
@Configuration
public class SecurityConfig implements WebMvcConfigurer {
// 安全相关配置
}
@Configuration
public class ApiConfig implements WebMvcConfigurer {
// API相关配置
}
- 性能考虑:
-
- 拦截器中的
preHandle
方法应尽量轻量 - 静态资源配置启用缓存
- 消息转换器使用共享的ObjectMapper实例
- 拦截器中的
- 测试验证:
@SpringBootTest
@AutoConfigureMockMvc
public class WebConfigTest {
@Autowired
private MockMvc mockMvc;
@Test
void testCorsConfig() throws Exception {
mockMvc.perform(options("/api/data")
.header("Origin", "http://localhost:8080"))
.andExpect(header().exists("Access-Control-Allow-Origin"));
}
}
通过合理组合这些配置方式,可以构建出既灵活又易于维护的Spring MVC应用。