📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 近期刚转战 CSDN,会严格把控文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍
文章目录
- 写在前面的话
- 定制 MVC 功能(前言)
- WebMvcConfigurerAdapter 废弃方式
- WebMvcConfigurationSupport 覆盖方式
- WebMvcConfigurer 推荐方式
- 定制 MVC 功能(正篇)
- 可以做什么?
- 静态资源配置
- 拦截器配置
- 参数解析器
- 跨域拦截器
- 消息转换器
- 格式化器&转换器
- 路径匹配规则
- 内容协商策略
- 异步调用支持
- 静态资源处理器
- 总结陈词
写在前面的话
使用SpringBoot
作为Java
后端开发框架,基本是大多数企业的标配,这边把实际企业开发中,一些常用的 SpringBoot
操作做一个整理,将持续更新。
定制 MVC 功能(前言)
Spring Boot 为 Spring MVC 提供了默认的配置主要包括视图解析器、静态资源处理、类型转化器与格式化器、HTTP消息转换器、静态主页支持等,可谓简单易用。但实践中,难免需要进行个性化的配置,因此自定义Web MVC配置在所难免。
Spring Boot 先后提供了 WebMvcConfigurerAdapter、WebMvcConfigurationSupport、WebMvcConfigurer、@EnableWebMvc 等形式来实现Web MVC的自定义配置。
WebMvcConfigurerAdapter 废弃方式
在SB1.x
,可使用WebMvcConfigurerAdapter
来扩展Spring MVC的功能,它是WebMvcConfigurer
的一个抽象实现类,该抽象类中所有的方法实现都为空,子类需要哪些功能就实现哪些功能。
在SB2.x
,基于Java8实现,可将接口的方法定义为default,接口中被定义为default的方法子类可以不进行实现。而接口WebMvcConfigurer
便运用了Java8的特性,因此WebMvcConfigurerAdapter
存在的意义没有了。
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
查看WebMvcConfigurerAdapter的实现,你会发现它就是把接口的所有方法实现为一个空的方法而已,Java8的default特性完全覆盖掉此功能。
WebMvcConfigurationSupport 覆盖方式
在SB2.x
,WebMvcConfigurerAdapter 被废弃了,那么我们还可以通过继承 WebMvcConfigurationSupport 来实现Spring MVC的拓展。
public class WebMvcConfigurationSupport
implements ApplicationContextAware, ServletContextAware {...}
这个类很特殊,实现了ApplicationContextAware和ServletContextAware接口, 提供了一些默认实现,同时提供了很多@Bean 方法,但是并没有提供@Configureation注解,因此这些@Bean并不会生效,所以我们需要继承这个类,并在提供的类上提供@Configureation注解才能生效。
WebMvcConfigurationSupport 中不仅定义了Bean,还提供了大量add、config开头的方法。
/**
* Override this method to add Spring MVC interceptors for
* pre- and post-processing of controller invocation.
* @see InterceptorRegistry
*/
protected void addInterceptors(InterceptorRegistry registry) {}
/**
* Override this method to add view controllers.
* @see ViewControllerRegistry
*/
protected void addViewControllers(ViewControllerRegistry registry) {}
继承 WebMvcConfigurationSupport之后,可以使用方法来添加自定义的拦截器、视图解析器等功能,如下:
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
}
}
这种方式有一个问题,其他没自定义实现的逻辑,也会无效,你可能会遇到比如静态资源访问不到、返回数据不成功等奇奇怪怪的问题。
那么,为什么继承WebMvcConfigurationSupport会顶替到Spring Boot默认的MVC配置呢?先来看一下Spring Boot中对WEB MVC相关组件自动装配的实现:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {...}
Spring Boot通过WebMvcAutoConfiguration配置类来对MVC的默认参数(约定)进行设置,但WebMvcAutoConfiguration生效是有限制条件的。@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})指定了,当Spring容器中不存在类型为WebMvcConfigurationSupport的bean的时候,才会进行默认配置。一定自定义了WebMvcConfigurationSupport,那么将导致WebMvcAutoConfiguration无法实例化,进而内部初始化配置将全部无法实例化。
这种情况下,相关的配置都需要自己去实现了,除非对代码有极好的把控能力,或者大量特殊化定制,才会考虑此种形式。否则,一些列的约定便不复存在,可能会出现一些莫名其妙的问题。
WebMvcConfigurer 推荐方式
为了解决上述问题,我们可以直接实现WebMvcConfigurer接口,这种方式不会影响未覆盖的方法逻辑,这也是推荐的稳妥方式。
@Configuration
public class MyMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/user").setViewName("success");
}
}
定制 MVC 功能(正篇)
Spring2.0版本后,推荐使用实现WebMvcConfigurer的方式
来全局定制SpringMVC
特性。
也可以继承WebMvcConfigurationSupport
实现,但会覆盖默认行为,具体参考前面分析专栏。
Tips:早期1.x版本是使用 extends WebMvcConfigurerAdapter 的方式,暂不需要了解。
@Configuration
public class WebMvcConfg implements WebMvcConfigurer {}
可以做什么?
/* 拦截器配置 */
void addInterceptors(InterceptorRegistry var1);
/* 视图跳转控制器 */
void addViewControllers(ViewControllerRegistry registry);
/* 静态资源处理 */
void addResourceHandlers(ResourceHandlerRegistry registry);
/* 默认静态资源处理器 */
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
/* 这里配置视图解析器 */
void configureViewResolvers(ViewResolverRegistry registry);
/* 配置内容裁决的一些选项*/
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
/** 解决跨域问题 **/
public void addCorsMappings(CorsRegistry registry) ;
静态资源配置
重写 addResourceHandlers 来配置路径访问等,SB 中默认使用 ResourceHttpRequestHandler 来映射类路径下的/static、/public、/resources 等路径中的静态文件直接映射为 /****。
/**
* 配置静态访问资源
* addResoureHandler:指的是对外暴露的访问路径
* addResourceLocations:指的是内部文件放置的目录
* 范例:http://localhost:8868/my/a1.jpg
* 等同:http://localhost:8868/img/a1.jpg
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/my/**").addResourceLocations("classpath:/static/img/");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//静态资源路径 css,js,img等
registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
//视图
registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
//mapper.xml
registry.addResourceHandler("/mapper/**").addResourceLocations("classpath:/mapper/");
super.addResourceHandlers(registry);
}
拦截器配置
重写addInterceptors() 方法来配置拦截器(实现了HandlerInterceptor接口)等。这里实现的addInterceptors方法对应的是xml文件中mvc:interceptors配置。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");
}
@Autowired
private MyInteceptor myInteceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器,添加拦截路径和排除拦截路径
registry.addInterceptor(myInteceptor) //添加拦截器
.addPathPatterns("/**") //添加拦截路径
.excludePathPatterns("/statics/**/*.*",);//排除拦截路径
super.addInterceptors(registry); //这句可不要
}
参数解析器
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserMethodArgumentResolver());
}
@Bean
CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
return new CurrentUserMethodArgumentResolver();
}
跨域拦截器
重写addCorsMappings方法实现配置cors跨域限制等。
/**
* 配置跨域拦截器
*
* @param registry 跨域拦截器
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedOrigins("*")
.allowedMethods("*");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")//配置允许跨域的路径
.allowedOrigins("*")//配置允许访问的跨域资源的请求域名
.allowedMethods("PUT,POST,GET,DELETE,OPTIONS")//配置允许访问该跨域资源服务器的请求方法,如:POST、GET、PUT、DELETE等
.allowedHeaders("*"); //配置允许请求header的访问,如 :X-TOKEN
super.addCorsMappings(registry);
}
/**
* 此种设置跨域的方式,在自定义拦截器的情况下可能导致跨域失效
* 原因:当跨越请求在跨域请求拦截器之前的拦截器处理时就异常返回了,那么响应的response报文头部关于跨域允许的信息就没有被正确设置,导致浏览器认为服务不允许跨域,而造成错误。
* 解决:自定义跨域过滤器解决跨域问题(该过滤器最好放在其他过滤器之前)
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowCredentials(true)
.maxAge(3600);
}
消息转换器
重写configureMessageConverters方法来对消息进行转换。MessageConverter用于对http请求的返回结果进行转换,以fastjon、编码格式application/json;charset=UTF-8进行转换。
/**
* 添加自定义消息转换器
* 自定义消息转化器的第二种方法
* 默认也会注册utf-8的该转换器
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
converters.add(converter);
}
格式化器&转换器
重写addFormatters方法来添加数据格式化器,比如将字符串转换为日期类型,可通过DateFormatter类来实现自动转换。
formatters和converters用于对日期格式进行转换,默认已注册了Number和Date类型的formatters,支持@NumberFormat和@DateTimeFormat注解,需要自定义formatters和converters可以实现addFormatters方法。
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
}
@Override
public void addFormatters(FormatterRegistry registry) {
//注册ConverterFactory(类型转换器工厂)
registry.addConverterFactory(new BaseEnumConverterFactory());
}
//具体实例参考上方博客
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(booleanFormatter());// 布尔格式化器
registry.addConverter(stringToDateConverter());// 字符串转日期转化器
}
@Bean
BooleanFormatter booleanFormatter() {
return new BooleanFormatter();
}
@Bean
StringToDateConverter stringToDateConverter() {
return new StringToDateConverter();
}
路径匹配规则
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 设置是否模糊匹配,默认真。例如/user是否匹配/user.*。如果真,也就是说"/user.html"的请求会被"/user"的Controller所拦截。
configurer.setUseSuffixPatternMatch(false);
// 设置是否自动后缀模式匹配,默认真。如/user是否匹配/user/。如果真,也就是说, "/user"和"/user/"都会匹配到"/user"的Controller。
configurer.setUseTrailingSlashMatch(true);
}
内容协商策略
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 自定义策略
configurer.favorPathExtension(true)// 是否通过请求Url的扩展名来决定mediaType,默认true
.ignoreAcceptHeader(true)// 不检查Accept请求头
.parameterName("mediaType")
.defaultContentType(MediaType.TEXT_HTML)// 设置默认的MediaType
.mediaType("html", MediaType.TEXT_HTML)// 请求以.html结尾的会被当成MediaType.TEXT_HTML
.mediaType("json", MediaType.APPLICATION_JSON)// 请求以.json结尾的会被当成MediaType.APPLICATION_JSON
.mediaType("xml", MediaType.APPLICATION_ATOM_XML);// 请求以.xml结尾的会被当成MediaType.APPLICATION_ATOM_XML
// 或者下面这种写法
Map<String, MediaType> map = new HashMap<>();
map.put("html", MediaType.TEXT_HTML);
map.put("json", MediaType.APPLICATION_JSON);
map.put("xml", MediaType.APPLICATION_ATOM_XML);
// 指定基于参数的解析类型
ParameterContentNegotiationStrategy negotiationStrategy = new ParameterContentNegotiationStrategy(map);
// 指定基于请求头的解析
configurer.strategies(Arrays.asList(negotiationStrategy));
}
异步调用支持
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 注册callable拦截器
configurer.registerCallableInterceptors(timeoutInterceptor());
// 注册deferredResult拦截器
configurer.registerDeferredResultInterceptors();
// 异步请求超时时间
configurer.setDefaultTimeout(1000);
// 设定异步请求线程池callable等, spring默认线程不可重用
configurer.setTaskExecutor(new ThreadPoolTaskExecutor());
}
@Bean
public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
return new TimeoutCallableProcessingInterceptor();
}
//测试接口
@GetMapping("test1")
public Callable<String> test1() {
Callable<String> callable = () -> {
Thread.sleep(60000);
return "test";
};
return callable;
}
静态资源处理器
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
configurer.enable("defaultServletName");
}
此时会注册一个默认的Handler:DefaultServerHttpRequestHandler,这个Handler也会用来处理静态文件的,它会尝试映射/*。当DispatcherServlet映射/时(/ 和/*是有区别的),并且没有找到合适的Handler来处理请求时,就会交给DefaultServletHttpRequestHandler来处理。注意:这里的静态资源是放置在web根目录下,而非WEB_INF下。
举例说明:在webroot目录下有一个图片a.png,我们知道Servelt规范中web根目录webroot下的文件可以直接访问的,但是由于DispatcherServlet配置了映射路径是:/,它几乎把所有的请求都拦截了,从而导致a.png访问不到,这时注册一个DefaultServletHttpRequestHandler就可以解决这个问题,其实可以理解为DispatchServlet破坏了Servler的一个特性(就是根目录下的文件可以直接访问),DefaultServletHttpRequestHandler是帮助回归这个特性的。
总结陈词
此篇文章介绍了SpringBoot
项目中如何常见的SpringMVC
定制能力,仅供学习参考。
💗 后续将持续更新,请多多支持!!