Spring MVC核心擴展點及使用技巧總結(jié)
環(huán)境:SpringBoot2.7.12
1. 啟用Spring MVC功能
@Configuration
@EnableWebMvc
public class WebConfig {
}
2. 類型轉(zhuǎn)換配置
如需要自定義數(shù)據(jù)類型的轉(zhuǎn)換,可以通過如下方式注冊
@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的轉(zhuǎn)換(這里只是舉例,系統(tǒng)默認已經(jīng)有了從String到Number的轉(zhuǎn)換器)。每種轉(zhuǎn)換器最終被包裝成ConvertersForPair對象,該對象中有個隊列保存了所有的轉(zhuǎn)換器。后添加的添加到首位,如下:
private static class ConvertersForPair {
private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
public void add(GenericConverter converter) {
this.converters.addFirst(converter);
}
}
所有如你有自定義的轉(zhuǎn)換器,自定義的優(yōu)先級比系統(tǒng)自帶的要高。
3. 數(shù)據(jù)驗證
默認情況下,如果類路徑上存在 Bean Validation(例如 Hibernate Validator),則 LocalValidatorFactoryBean 會被注冊為全局 Validator,與控制器方法參數(shù)上的 @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路徑匹配還可以透明地匹配尾部斜線和路徑擴展名以及其他路徑匹配選項。其中許多選項已被棄用,但仍有可能出現(xiàn)不匹配。一般情況下,我們建議使用 Spring Security,它包含一個專用的 MvcRequestMatcher,可與 Spring MVC 路徑匹配保持一致,還具有安全防火墻,可阻止 URL 路徑中許多不需要的字符。
5. 請求內(nèi)容類型
自定義Spring MVC 如何從請求中確定所請求的媒體類型(例如,接受頭、URL 路徑擴展、查詢參數(shù)等)。
默認情況下,只選中"Accept" header。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 這樣配置后,視圖技術就能夠根據(jù)你請求的Accept輸出指定的文件內(nèi)容了
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文件,內(nèi)容隨意。有了這些還不夠,我們需要能夠解析處理*.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才能正確的輸出我們所需要的文件內(nèi)容。這個功能是不是太麻煩了,沒撒用??。
6. 自定義消息轉(zhuǎn)換器
現(xiàn)希望將對象轉(zhuǎn)換為YAML個數(shù)的數(shù)據(jù)進行輸出,我們可以配置自定義的HttpMessageConverter進行轉(zhuǎn)換輸出。
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()) ;
}
}
注冊上面的轉(zhuǎn)換器
@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") ;
}
輸出結(jié)果
圖片
7. 視圖控制器
一種快捷定義視圖Controller接口的方式
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 當訪問/index時將直接輸出test視圖內(nèi)容
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. 靜態(tài)資源配置
一種從基于資源的位置列表中提供靜態(tài)資源的便捷方法。如下如果請求以 /resources 開頭,則會使用相對路徑查找并提供網(wǎng)絡應用程序根目錄下 /public 或類路徑中 /static 下的靜態(tài)資源。
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public", "classpath:/static/");
}
}
以上是本篇文章的所有內(nèi)容,希望對你有幫助。
完畢?。?!