Spring MVC核心扩展点及使用技巧总结和使用案例

开发 前端
一种从基于资源的位置列表中提供静态资源的便捷方法。如下如果请求以 /resources 开头,则会使用相对路径查找并提供网络应用程序根目录下 /public 或类路径中 /static 下的静态资源。

环境:SpringBoot2.7.12

1. 启用Spring MVC功能

@Configuration
@EnableWebMvc
public class WebConfig {
}

2. 类型转换配置

如需要自定义数据类型的转换,可以通过如下方式注册

@Configuration
public class WebConfig implements WebMvcConfigurer {
  
  @Override
  public void addFormatters(FormatterRegistry registry) {
    registry.addConverterFactory(new ConverterFactory<String, Number>() {
      @Override
      public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
        return new Converter<String, T>() {
          public T convert(String source) {
            return (T) Integer.valueOf(source) ;
          }
        } ;
      }
    });
  }
  
}

以上添加了从String到Integer的转换(这里只是举例,系统默认已经有了从String到Number的转换器)。每种转换器最终被包装成ConvertersForPair对象,该对象中有个队列保存了所有的转换器。后添加的添加到首位,如下:

private static class ConvertersForPair {
    private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
    public void add(GenericConverter converter) {
      this.converters.addFirst(converter);
    }
}

所有如你有自定义的转换器,自定义的优先级比系统自带的要高。

3. 数据验证

默认情况下,如果类路径上存在 Bean Validation(例如 Hibernate Validator),则 LocalValidatorFactoryBean 会被注册为全局 Validator,与控制器方法参数上的 @Valid 和 Validated 一起使用。

@Configuration
public class WebConfig implements WebMvcConfigurer {
  public Validator getValidator() {
    return new LocalValidatorFactoryBean();
  }
}

4. 请求拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new HandlerInterceptor() {
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
          throws Exception {
        if (request.getHeader("token") == null) {
          return false ;
        }
        return true ;
      }
    }).addPathPatterns("/**") ;
  }
}

上面配置了一个拦截任意请求的拦截器,在请求到达时会先验证请求header中token是否为null。

拦截器并不适合作为安全层,因为它有可能与控制器Controller路径匹配不匹配,而Controller路径匹配还可以透明地匹配尾部斜线和路径扩展名以及其他路径匹配选项。其中许多选项已被弃用,但仍有可能出现不匹配。一般情况下,我们建议使用 Spring Security,它包含一个专用的 MvcRequestMatcher,可与 Spring MVC 路径匹配保持一致,还具有安全防火墙,可阻止 URL 路径中许多不需要的字符。

5. 请求内容类型

自定义Spring MVC 如何从请求中确定所请求的媒体类型(例如,接受头、URL 路径扩展、查询参数等)。

默认情况下,只选中"Accept" header。

@Configuration
public class WebConfig implements WebMvcConfigurer {


  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    // 这样配置后,视图技术就能够根据你请求的Accept输出指定的文件内容了
    configurer.mediaType("yaml", new MediaType("application", "yaml")) ;
  }
}

上面的配置最终是对ContentNegotiationManager对象进行添加MappingMediaTypeFileExtensionResolver文件扩展解析器。

@Bean
public ContentNegotiationManager mvcContentNegotiationManager() {
  if (this.contentNegotiationManager == null) {
    ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
    configurer.mediaTypes(getDefaultMediaTypes());
    configureContentNegotiation(configurer);
    this.contentNegotiationManager = configurer.buildContentNegotiationManager();
  }
  return this.contentNegotiationManager;
}
protected ContentNegotiationManager buildContentNegotiationManager() {
  this.factory.addMediaTypes(this.mediaTypes);
  return this.factory.build();
}

部分代码

public class ContentNegotiationManagerFactoryBean {
  public ContentNegotiationManager build() {
    if (!CollectionUtils.isEmpty(this.mediaTypes) && !this.favorPathExtension && !this.favorParameter) {
      this.contentNegotiationManager.addFileExtensionResolvers(
          new MappingMediaTypeFileExtensionResolver(this.mediaTypes));
    }
  }
}

有了MappingMediaTypeFileExtensionResolver解析器后,还需要Controller接口返回ModelAndView对象。如下接口

@GetMapping("/contentType")
public ModelAndView contentType() {
  return new ModelAndView("test") ;
}

在classpath下新建test.yaml文件,内容随意。有了这些还不够,我们需要能够解析处理*.yaml的文件。所以还需要视图解析器

@Component
public class YamlViewResolver implements ViewResolver {
  @Override
  public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!viewName.endsWith(".yaml")) {
      return null ;
    }
    return new View() {
      // 支持的类型
      public String getContentType() {
        return "application/yaml" ;
      };
      @Override
      public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ClassPathResource resource = new ClassPathResource(viewName) ;
        InputStream is = resource.getInputStream() ;
        
        OutputStream outputStream = response.getOutputStream();  
        byte[] buffer = new byte[4096];  
        int bytesRead = -1;  
        while ((bytesRead = is.read(buffer)) != -1) {  
          outputStream.write(buffer, 0, bytesRead);  
        }  
        outputStream.flush() ;  
        is.close();  
        outputStream.close() ;
      }
    } ;
  }


}

有了这些我们配置Spring MVC才能正确的输出我们所需要的文件内容。这个功能是不是太麻烦了,没撒用😃。

6. 自定义消息转换器

现希望将对象转换为YAML个数的数据进行输出,我们可以配置自定义的HttpMessageConverter进行转换输出。

public class YamlHttpMessageConverter implements HttpMessageConverter<Object> {


  @Override
  public boolean canWrite(Class<?> clazz, MediaType mediaType) {
    return User.class.isAssignableFrom(clazz) ;
  }


  @Override
  public List<MediaType> getSupportedMediaTypes() {
    return Arrays.asList(new MediaType("application", "yaml")) ;
  }


  @Override
  public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage)
      throws IOException, HttpMessageNotWritableException {
    StreamUtils.copy(new org.yaml.snakeyaml.Yaml().dump(t), StandardCharsets.UTF_8, outputMessage.getBody()) ;
  }


}

注册上面的转换器

@Configuration
public class WebConfig implements WebMvcConfigurer {
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    // 注意这里已定义指定位置,不然就被json输出了
    converters.add(0, new YamlHttpMessageConverter()) ;
  }
}

测试接口

@GetMapping("/yaml")
public Object yaml() {
  return new User(10, "zhangsan") ;
}

输出结果

图片图片

7. 视图控制器

一种快捷定义视图Controller接口的方式

@Configuration
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    // 当访问/index时将直接输出test视图内容
    registry.addViewController("/index").setViewName("test") ;
  }
}

这里为了简单直接使用BeanNameViewReolver技术,自定义一个以test为名的View Bean对象

@Component("test")
public class PackView implements View {
  @Override
  public String getContentType() {
    return "text/html" ;
  }
  @Override
  public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    response.getWriter().print("View Controllers") ;
  }
}

输出

图片图片

8. 视图解析器

可以通过上面案例5中定义的YamlViewResolver注册方式,也可以通过如下方式注册

@Configuration
public class WebConfig implements WebMvcConfigurer {
  public void configureViewResolvers(ViewResolverRegistry registry) {
    registry.viewResolver(new YamlViewResolver()) ;
  }
}

这样注册的解析器,都会添加到ViewResolverComposite这个解析器集合中。

9. 静态资源配置

一种从基于资源的位置列表中提供静态资源的便捷方法。如下如果请求以 /resources 开头,则会使用相对路径查找并提供网络应用程序根目录下 /public 或类路径中 /static 下的静态资源。

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {


  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**").addResourceLocations("/public", "classpath:/static/");
  }
}

以上是本篇文章的所有内容,希望对你有帮助。

完毕!!!

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2009-12-04 15:43:03

PHP JSON扩展

2017-05-09 10:34:21

Spring BootDubbo Activ扩展

2017-04-28 08:32:40

Spring BootDubbo Activ使用

2020-01-16 18:30:07

技术SQL优化

2021-10-31 19:39:11

注解Spring 核心类

2011-12-05 13:44:34

JavaSpringMVC

2009-08-13 18:00:48

Eclipse重构功能扩展点

2015-08-24 08:59:13

Git技巧

2013-06-07 14:35:19

Mac OS X

2023-09-28 08:49:41

springBean

2009-06-04 20:45:11

Eclipse插件安装Eclipse插件使用

2013-07-10 15:52:17

fragmentAndroid

2009-12-04 14:40:43

Visual Stud

2019-10-28 14:37:10

MySQL 数据库收藏

2010-02-02 14:06:50

C++ const变量

2009-06-24 16:01:28

Spring MVC

2009-06-03 09:16:03

Hibernate工作原理使用

2023-01-30 11:35:14

SpringJava

2009-01-03 14:39:04

ibmdwDojoMVC

2022-09-05 10:03:55

MypyPython代码
点赞
收藏

51CTO技术栈公众号