本文最后更新于:2024年9月8日 晚上
Spring MVC WebMvcConfigurer
WebMvcConfigurer配置类其实是Spring
内部的一种配置方式,采用JavaBean
的形式来代替传统的xml
配置文件形式进行针对框架个性化定制,基于java-based方式的Spring MVC配置,需要创建一个配置类并实现WebMvcConfigurer
接口。
WebMvcConfigurerAdapter
抽象类是对WebMvcConfigurer
接口的简单抽象(增加了一些默认实现),但在SpringBoot2.0及Spring5.0中WebMvcConfigurerAdapter已被废弃,官方推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public interface WebMvcConfigurer { void configurePathMatch (PathMatchConfigurer var1) ; void configureContentNegotiation (ContentNegotiationConfigurer var1) ; void configureAsyncSupport (AsyncSupportConfigurer var1) ; void configureDefaultServletHandling (DefaultServletHandlerConfigurer var1) ; void addFormatters (FormatterRegistry var1) ; void addInterceptors (InterceptorRegistry var1) ; void addResourceHandlers (ResourceHandlerRegistry var1) ; void addCorsMappings (CorsRegistry var1) ; void addViewControllers (ViewControllerRegistry var1) ; void configureViewResolvers (ViewResolverRegistry var1) ; void addArgumentResolvers (List<HandlerMethodArgumentResolver> var1) ; void addReturnValueHandlers (List<HandlerMethodReturnValueHandler> var1) ; void configureMessageConverters (List<HttpMessageConverter<?>> var1) ; void extendMessageConverters (List<HttpMessageConverter<?>> var1) ; void configureHandlerExceptionResolvers (List<HandlerExceptionResolver> var1) ; void extendHandlerExceptionResolvers (List<HandlerExceptionResolver> var1) ; Validator getValidator () ; MessageCodesResolver getMessageCodesResolver () ; }
addInterceptors
拦截器配置,此方法用来专门注册一个Interceptor,如HandlerInterceptorAdapter
1 2 3 4 5 6 @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/**" ).excludePathPatterns("/login" ,"/logout" ,"/js/**" ,"/css/**" ,"/images/**" ); }
addPathPatterns("/**")
对所有请求都拦截,但是排除了/toLogin
和/login
请求的拦截。
addViewControllers
1 2 3 4 @Override public void addViewControllers (ViewControllerRegistry registry) { registry.addViewController("/toLogin" ).setViewName("login" ); }
以前要访问一个页面需要先创建个Controller控制类,再写方法跳转到页面,在这里配置后就不需要那么麻烦了,直接访问http://localhost:8080/toLogin就跳转到login.jsp页面了。
注意 :在这里重写addViewControllers
方法,并不会覆盖WebMvcAutoConfiguration
中的addViewControllers
(在此方法中,Spring Boot将/
映射至index.html),这也就意味着我们自己的配置和Spring Boot的自动配置同时有效,这也是推荐添加自己的MVC配置的方式。
addResourceHandlers
自定义资源映射。
注意 :如果继承WebMvcConfigurationSupport类实现配置时必须要重写该方法。
1 2 3 4 @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/img/**" ).addResourceLocations("/usr/local/img/" ); }
1 2 3 4 5 @Override public void configureDefaultServletHandling (DefaultServletHandlerConfigurer configurer) { configurer.enable(); configurer.enable("defaultServletName" ); }
此时会注册一个默认的Handler:DefaultServletHttpRequestHandler
,这个Handler也是用来处理静态文件的,它会尝试映射/*
当DispatcherServelt映射/
时(/
和/*
是有区别的),并且没有找到合适的Handler来处理请求时,就会交给DefaultServletHttpRequestHandler
来处理。
注意 :这里的静态资源是放置在web根目录下,而非WEB-INF
下。
例如:在webroot目录下有一个图片1.png
我们知道Servelt规范中web根目录(webroot)下的文件可以直接访问的,但是由于DispatcherServlet
配置了映射路径是:/
,它几乎把所有的请求都拦截了,从而导致1.png
访问不到,这时注册一个DefaultServletHttpRequestHandler
就可以解决这个问题,其实可以理解为DispatcherServlet
破坏了Servlet的一个特性(根目录下的文件可以直接访问),DefaultServletHttpRequestHandler
是帮助回归这个特性的。
1 2 3 4 @Override public void enableContentNegotiation (View... defaultViews) { initContentNegotiatingViewResolver(defaultViews); }
1 2 3 4 5 6 7 8 @Override public UrlBasedViewResolverRegistration jsp (String prefix, String suffix) { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(prefix); resolver.setSuffix(suffix); this .viewResolvers.add(resolver); return new UrlBasedViewResolverRegistration(resolver); }
该方法会注册一个内部资源视图解析器InternalResourceViewResolver
显然访问的所有jsp都是它进行解析的,该方法参数用来指定路径的前缀和文件后缀,如:
1 registry.jsp("/WEB-INF/jsp/" , ".jsp" );
对于以上配置,假如返回的视图名称是example,它会返回/WEB-INF/jsp/example.jsp
给前端,找不到则报404,
beanName
1 2 3 4 public void beanName () { BeanNameViewResolver resolver = new BeanNameViewResolver(); this .viewResolvers.add(resolver); }
该方法会注册一个BeanNameViewResolver
视图解析器,它主要是将视图名称解析成对应的bean
假如返回的视图名称是example,它会到spring容器中找有没有一个叫example的bean,并且这个bean是View.class类型的?如果有,返回这个bean,
viewResolver
1 2 3 4 5 6 7 8 @Override public void viewResolver (ViewResolver viewResolver) { if (viewResolver instanceof ContentNegotiatingViewResolver) { throw new BeanInitializationException( "addViewResolver cannot be used to configure a ContentNegotiatingViewResolver. Please use the method enableContentNegotiation instead." ); } this .viewResolvers.add(viewResolver); }
configureContentNegotiation
上面我们讲了configureViewResolvers
方法,假如在该方法中我们启用了内容裁决解析器,那么configureContentNegotiation(ContentNegotiationConfigurer configurer)
这个方法是专门用来配置内容裁决的一些参数的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void configureContentNegotiation (ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(true ) .ignoreAcceptHeader(true ) .parameterName("mediaType" ) .defaultContentType(MediaType.TEXT_HTML) .mediaType("html" , MediaType.TEXT_HTML) .mediaType("json" , MediaType.APPLICATION_JSON); }
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override public void configureViewResolvers (ViewResolverRegistry registry) { registry.jsp("/WEB-INF/jsp/" , ".jsp" ); registry.enableContentNegotiation(new MappingJackson2JsonView()); }@Override public void configureContentNegotiation (ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(true ) .ignoreAcceptHeader(true ) .parameterName("mediaType" ) .defaultContentType(MediaType.TEXT_HTML) .mediaType("html" , MediaType.TEXT_HTML) .mediaType("json" , MediaType.APPLICATION_JSON); }
1 2 3 4 5 6 7 8 9 10 @Controller public class ExampleController { @RequestMapping("/test") public ModelAndView test () { Map<String, String> map = new HashMap(); map.put("test1" , "Hello" ); map.put("test2" , "world" ); return new ModelAndView("test" , map); } }
1 2 3 4 { "test1" :"Hello" , "test2" :"world" }
在浏览器输入http://localhost:8080/test 或者http://localhost:8080/test.html,内容返回如下:
显然,两次使用了不同的视图解析器,那么底层到底发生了什么?在配置里我们注册了两个视图解析器:ContentNegotiatingViewResolver
和 InternalResourceViewResolver
,还有一个默认视图:MappingJackson2JsonView
,controller执行完毕之后返回一个ModelAndView,其中视图的名称为example1
返回首先会交给ContentNegotiatingViewResolver
进行视图解析处理,而ContentNegotiatingViewResolver
会先把视图名example1交给它持有的所有ViewResolver
尝试进行解析(本实例中只有InternalResourceViewResolver
)
根据请求的mediaType
,再将example1.mediaType
(这里是example1.json
和example1.html
)作为视图名让所有视图解析器解析一遍,两步解析完毕之后会获得一堆候选的List<View>
再加上默认的MappingJackson2JsonView
根据请求的media type
从候选的List<View>
中选择一个最佳的返回,至此视图解析完毕。
现在就可以理解上例中为何请求链接加上.json
和不.json
结果会不一样。
当加上.json
时,表示请求的media type
为MediaType.APPLICATION_JSON
,而InternalResourceViewResolver
解析出来的视图的ContentType
与其不符,而与MappingJackson2JsonView
的ContentType
相符,所以选择了MappingJackson2JsonView
作为视图返回。
当不加.json
请求时,默认的media type
为MediaType.TEXT_HTML
,所以就使用了InternalResourceViewResolver
解析出来的视图作为返回值了。