SpringBoot如何扩展SpringMVC的配置

发布于:2025-08-11 ⋅ 阅读:(28) ⋅ 点赞:(0)

题目详细答案

方式一:使用@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 的组件,例如ViewResolverMessageConverter等。

自定义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;
    }
}

最佳实践建议

  1. 配置方式选择
    • 大多数情况下优先使用方式一(WebMvcConfigurer
    • 特定功能(如拦截器)使用方式二
    • 全局异常处理使用方式三
    • 需要精细控制组件时使用方式四
  1. 配置拆分原则
@Configuration
public class WebConfig implements WebMvcConfigurer {
    // 基础MVC配置
}

@Configuration
public class SecurityConfig implements WebMvcConfigurer {
    // 安全相关配置
}

@Configuration
public class ApiConfig implements WebMvcConfigurer {
    // API相关配置
}
  1. 性能考虑
    • 拦截器中的preHandle方法应尽量轻量
    • 静态资源配置启用缓存
    • 消息转换器使用共享的ObjectMapper实例
  1. 测试验证
@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应用。


网站公告

今日签到

点亮在社区的每一天
去签到