目录
1.6、MVC的自动配置
我们知道SpringMVC有很多相关的配置,如:DispatcherServlet、拦截器执行链、消息转换机制、视图、视图解析器等等,而SpringBoot 也对Springmvc的配置做了很多的封装。
其中WebMvcConfigurer 配置接口是Spring 内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式,它内部提供了很多方法让我们来定制很多SpringMVC的配置:处理器、拦截器、视图解析器、转换器、设置跨域等。
public interface WebMvcConfigurer {
//HandlerMappings 路径的匹配规则。
default void configurePathMatch(PathMatchConfigurer configurer) {
}
//内容协商策略(一个请求路径返回多种数据格式)。
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
//配置异步请求处理选项
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
//以实现静态文件可以像 Servlet 一样被访问。
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
//添加格式化器或者转化器
default void addFormatters(FormatterRegistry registry) {
}
//添加拦截器,对请求进行拦截处理。
default void addInterceptors(InterceptorRegistry registry) {
}
//添加或修改静态资源(例如图片,js,css 等)映射; Spring Boot默认设置的静态资源文件夹就是通过重写该方法设置的。
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
//处理跨域请求。
default void addCorsMappings(CorsRegistry registry) {
}
//主要用于实现无业务逻辑跳转,例如主页跳转,简单的请求重定向,错误页跳转等
default void addViewControllers(ViewControllerRegistry registry) {
}
//配置视图解析器,将 Controller 返回的字符串(视图名称),转换为具体的视图进行渲染。
default void configureViewResolvers(ViewResolverRegistry registry) {
}
//添加解析器以支持自定义控制器方法参数类型,实现该方法不会覆盖用于解析处理程序方法参数的内置支持; 要自定义内置的参数解析支持, 同样可以通过 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
//添加处理程序来支持自定义控制器方法返回值类型。使用此选项不会覆盖处理返回值的内置支持; 要自定义处理返回值的内置支持,请直接配置 RequestMappingHandlerAdapter。
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
//用于配置默认的消息转换器(转换 HTTP 请求和响应)。
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
//直接添加消息转换器,会关闭默认的消息转换器列表;实现该方法即可在不关闭默认转换器的起提下,新增一个自定义转换器。
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
//配置异常解析器。
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
//扩展或修改默认的异常解析器列表。
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
1.6.1、SprinBoot配置
在SpringBoot中通过 WebMvcAutoConfiguration 类来定制一些默认的配置,来供SpringBoot 启动时自动加载,所以我们的项目在创建完成后不使用任何配置就可以完成页面的跳转等工作
在该类内部定义了一个 WebMvcAutoConfigurationAdapter ,它是WebMvcConfigurer 配置接口的一个实现类。
1、配置路径规则
//模式匹配到请求时是否使用后缀模式匹配(“.*”)。如果启用,映射到“/users”的方法也匹配到“/users.*”。
private boolean useSuffixPattern = false;
//后缀模式匹配是否只适用于使用“spring.mvc.contentnegotiation.media-types.*”注册的扩展
private boolean useRegisteredSuffixPattern = false;
public void configurePathMatch(PathMatchConfigurer configurer) {
//使用使用PathPatternParser风格实现。(ANT_PATH_MATCHER 或 PATH_PATTERN_PARSER)
if (this.mvcProperties.getPathmatch()
.getMatchingStrategy() == WebMvcProperties.MatchingStrategy.PATH_PATTERN_PARSER) {
configurer.setPatternParser(pathPatternParser);
}
//设置使用后缀模式匹配
configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
//设置使用注册后缀模式匹配
configurer.setUseRegisteredSuffixPatternMatch(
this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
//servlet 网址映射
String servletUrlMapping = dispatcherPath.getServletUrlMapping();
//DispatcherServlet为单例,且路径为 /
if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setAlwaysUseFullPath(true);
configurer.setUrlPathHelper(urlPathHelper);
}
});
}
2、内容协商策略
就是客户端向服务端发送一个请求,然后服务端给客户端返回什么格式的数据的。
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//获取内容协商
WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
//支持路径扩展
configurer.favorPathExtension(contentnegotiation.isFavorPathExtension());
configurer.favorParameter(contentnegotiation.isFavorParameter());
if (contentnegotiation.getParameterName() != null) {
//参数名称
configurer.parameterName(contentnegotiation.getParameterName());
}
//将文件扩展名映射到媒体类型以进行内容协商。
Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes();
mediaTypes.forEach(configurer::mediaType);
}
3、异步调用支持
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
//是否包含应用程序任务执行器 Bean 名称
if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
Object taskExecutor = this.beanFactory
.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
//如果是异步任务执行器
if (taskExecutor instanceof AsyncTaskExecutor) {
//设置任务执行器
configurer.setTaskExecutor(((AsyncTaskExecutor) taskExecutor));
}
}
//请求超时
Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
if (timeout != null) {
configurer.setDefaultTimeout(timeout.toMillis());
}
}
4、静态资源处理器
- 自定义配置
- 导入了webjars,以jar包的形式引入静态资源
- 默认的静态资源路径
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//不使用默认的资源处理,按自定义资源路径(spring.mvc.static-path-pattern 设置)
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//如果使用了webjars管理
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//静态资源的位置。默认为类路径:[/META-INF/resources/、/resources/、/static/、/public/]。
//this.mvcProperties.getStaticPathPattern() 为 /**
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource =
new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
//欢迎页配置
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
5、格式化转换器
@Override
public void addFormatters(FormatterRegistry registry) {
ApplicationConversionService.addBeans(registry, this.beanFactory);
}
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
Set<Object> beans = new LinkedHashSet<>();
//通用转换器
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
//转换器
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
//打印
beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
//解析器
beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
for (Object bean : beans) {
if (bean instanceof GenericConverter) {
registry.addConverter((GenericConverter) bean);
}
else if (bean instanceof Converter) {
registry.addConverter((Converter<?, ?>) bean);
}
else if (bean instanceof Formatter) {
registry.addFormatter((Formatter<?>) bean);
}
else if (bean instanceof Printer) {
registry.addPrinter((Printer<?>) bean);
}
else if (bean instanceof Parser) {
registry.addParser((Parser<?>) bean);
}
}
}
6、信息转化器
如果自定义信息转化器会将默认的覆盖
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//如果可供使用的话,添加所有转换器
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
其中还有一个方法为信息转化器扩展,使用它来自定义信息转化器不会将原有的信息转发器覆盖
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
if (ClassUtils.isPresent("com.jayway.jsonpath.DocumentContext", context.getClassLoader())
&& ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", context.getClassLoader())) {
ObjectMapper mapper = getUniqueBean(ObjectMapper.class, context, ObjectMapper::new);
ProjectingJackson2HttpMessageConverter converter = new ProjectingJackson2HttpMessageConverter(mapper);
converter.setBeanFactory(context);
forwardBeanClassLoader(converter);
converters.add(0, converter);
}
if (ClassUtils.isPresent("org.xmlbeam.XBProjector", context.getClassLoader())) {
converters.add(0, context.getBeanProvider(XmlBeamHttpMessageConverter.class) //
.getIfAvailable(() -> new XmlBeamHttpMessageConverter()));
}
}
7、异常解析器
其实现方法为空,有默认的实现,使用 configureHandlerExceptionResolvers 自定义实现异常解析,会覆盖默认的异常解析器,所以建议使用 extendHandlerExceptionResolvers 方法进行扩展。
8、语言环境解析器
容器中的组件,负责获取区域信息对象,这个解析器是用来配置国际化的重要方法
- 该方法默认向容器中添加了一个区域信息解析器(LocaleResolver)组件根据请求头中携带的“Accept-Language”参数,获取相应区域信息(Locale)对象。
- @ConditionalOnMissingBean 注解,参数 name 的取值为 localeResolver(与该方法注入到容器中的组件名称一致),当容器中不存在名称为 localResolver 组件时,该方法才会生效。当我们手动向容器中添加一个名为“localeResolver”的组件时,Spring Boot 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
//获取语言环境解析器
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
//设置默认语言环境
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
9、视图解析器
//BeanNameViewResolver 是 ViewResolver的一个简单实现,它将视图名称解释为当前应用程序上下文中的 bean 名称,即通常在执行DispatcherServlet的 XML 文件中或在相应的配置类中。
@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
//ContentNegotiatingViewResolver 是 ViewResolver的实现,它根据请求文件名或Accept标头解析视图。ContentNegotiatingViewResolver本身不解析视图,而是委托给其他ViewResolvers 。默认情况下,这些其他视图解析器会自动从应用程序上下文中获取,但也可以使用viewResolvers属性显式设置它们
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
//设置ContentNegotiationManager以用于确定请求的媒体类型。
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
//ContentNegotiatingViewResolver 使用所有其他视图解析器来定位视图,因此它应该具有高优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
WebMvcAutoConfiguration 通过配置 ViewResolver 的实现类 ContentNegotiatingViewResolver 来实现视图的跳转,ContentNegotiatingViewResolver它可以将所有的视图解析器组合,如果
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
//解析视图名称
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
//获取请求属性
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//获取请求的类型
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
//获取候选视图
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
//获取视图
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
}
//用来获取候选视图
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
//解析视图名称
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
//将视图加入到 candidateViews 中
candidateViews.add(view);
}
//请求的媒体类型
for (MediaType requestedMediaType : requestedMediaTypes) {
//解析文件扩展名
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
//查看带扩展名的名称
String viewNameWithExtension = viewName + '.' + extension;
//解析视图名称
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
}
1.6.2、自定义配置
如果 Spring Boot 对 Spring MVC 的自动配置不能满足我们的需要(WebMvcAutoConfiguration有很多空实现),我们还可以通过自定义一个 实现 WebMvcConfigurer 接口的配置类(标注 @Configuration,但不标注 @EnableWebMvc 注解的类),来扩展 Spring MVC。这样不但能够保留 Spring Boot 对 Spring MVC 的自动配置,享受 Spring Boot 自动配置带来的便利,还能额外增加自定义的 Spring MVC 配置。
1、视图控制器
创建一个MyMvcConfig类,实现WebMvcConfigurer接口,让访问 “/” 或 “/index.html” 时,直接跳转到登录页面
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//当访问 “/” 或 “/index.html” 时,都直接跳转到登录页面
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
}
2、拦截器
-
先自定义一个拦截器
public class MyInterceptorTest implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("====== 前置执行了 ======"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("====== 后置执行了 ======"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("====== afterCompletion执行了 ======"); } }
-
使用自定义的配置类添加拦截器
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public MyInterceptorTest myInterceptorTest(){ return new MyInterceptorTest(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myInterceptorTest()) .addPathPatterns("/test1").excludePathPatterns("/test2"); } }
-
添加controller
@Controller public class PageController { @RequestMapping("/test1") @ResponseBody public void test1(){ } @RequestMapping("/test2") @ResponseBody public void test2(){ } }
3、静态资源处理
除了默认静态资源路径、webjars管理外还可以自己设置静态资源路径(也可以在配置文件中修改)
spring:
mvc:
static-path-pattern: /myData/**
resources:
static-locations: classpath:/myData/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/myData/**").addResourceLocations("classpath:/myData/");
}
}
4、视图解析器
SpringBoot 推荐使用的是Thymeleaf 模板引擎,它为Thymeleaf提供了默认的视图解析器,但是我们也可以通过自定义视图解析器,解析 .jsp页面的文件
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(internalResourceViewResolver());
}
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
//视图文件的前缀地址
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
//视图文件的后缀
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
}
5、异常解析器
-
自定义异常
public class MyException implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ModelAndView mv = new ModelAndView(); // 判断不同异常类型,做不同视图跳转 return mv; } }
-
配置异常解析器
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { resolvers.add(-999,new MyException()); } }