文章目录
- 官网阅读
- ContentNegotiatingViewResolver 内容协商视图解析器
- 转换器和格式化器
- 修改SpringBoot的默认配置
- 全面接管SpringMVC
官网阅读
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有把这些都搞清楚了,我们在之后使用才会更加得心应手。
途径一:源码分析!
途径二:官方文档!
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
ContentNegotiatingViewResolver 内容协商视图解析器
自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们去看看这里的源码:我们搜索ContentNegotiatingViewResolver。找到如下方法!
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
我们去看看有什么视图的方法
除了那么多getset方法,就几个init初始化,这种,很明显的一个resolveviewname的方法
我们可以点进这类看看!找到对应的解析视图的代码;
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象getCandidateViews,我们点进去看看怎么获得的,代码在下面
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
}
我们继续点进去看,他是怎么获得候选的视图的呢?
getCandidateViews中看到他是把所有的视图解析器拿来,进行foreach循环,挨个解析!
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
//new一个list
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
//遍历viewResolvers视图解析程序
for (ViewResolver viewResolver : this.viewResolvers) {
//提取名字
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
//有名字就add candidateViews:候选视图
candidateViews.add(view);
}
//遍历我们传过来的list requestedMediaTypes:获取媒体类型
for (MediaType requestedMediaType : requestedMediaTypes) {
//存在list
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
//又遍历
for (String extension : extensions) {
//在我们传过来的viewName和遍历的 extension 组合
String viewNameWithExtension = viewName + '.' + extension;
//组合后加载为另一个view
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
//添加到candidateViews:候选视图
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
resolveViewName:是添加候选视图,然后选出最适合的视图
所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!
这是我们刚刚看到的.这个类中为数不多的不是getset方法的
初始化initServletContext
protected void initServletContext(ServletContext servletContext) {
// 这里它是从beanFactory工具中获取容器中的所有视图解析器
// ViewRescolver.class 把所有的视图解析器来组合的
Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
ViewResolver viewResolver;
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
}
// ...............
}
就是说,先执行这个找到所有视图,然后resolveViewName,添加进去在选出最合适的
既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?
我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;我们去实现一下
1、我们在我们的主程序中去写一个视图解析器来试试;
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
可以添加自己的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。
扩展什么就实现什么接口,扩展视图解析器就实现viewresolver接口
@Bean //放到bean中
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
2、怎么看我们自己写的视图解析器有没有起作用呢?
我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下
因为所有的请求都会走到这个方法中
3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;找到this
找到视图解析器,我们看到我们自己定义的就在这里了;
所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!
转换器和格式化器
找到格式化转换器:
@Bean
@Override
public FormattingConversionService mvcConversionService() {
// 拿到配置文件中的格式化规则
WebConversionService conversionService =
new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
点击去:
public String getDateFormat() {
return this.dateFormat;
}
/**
* Date format to use. For instance, `dd/MM/yyyy`. 默认的
*/
private String dateFormat;
可以看到在我们的Properties文件中,我们可以进行自动配置它!
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
修改SpringBoot的默认配置
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;
@Configuration
public class MvcView implements WebMvcConfigurer {
重写里面的方法就可以扩展了,我们看看有什么方法可以重写的
配置路径匹配
配置内容协商
配置异步支持
配置默认Servlet处理
添加格式化程序
添加拦截器
添加资源处理程序
添加Cors映射
添加视图控制器
配置视图解析程序
添加参数解析器
添加返回值处理程序
配置消息转换器
扩展消息转换器
配置处理程序异常解决程序
扩展处理程序异常解决程序
获取验证程序
获取消息代码解析程序
我们就重写一个添加视图控制器
@Configuration
public class MvcView implements WebMvcConfigurer {
@Bean
public ViewResolver myViewResolver() {
return new MyViewResolver();
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送/test , 就会跳转到hello页面;
registry.addViewController("/test").setViewName("hello");
}
确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
我们可以去分析一下原理:
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware
这个父类中有这样一段代码:继承了WebMvcConfigurationSupport 记住这个,就是这个等会会搞事情
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
4、我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个addViewControllers()
这个不是我们自己重写的那个,是WebMvcConfigurationSupport 这个类中的addViewControllers()方法
他自己又调了个addViewControllers,这个又是哪个呢,是他自己还是有其他的
但是可以得出的是,最后到会加到configurers中
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
5.进.addViewControllers(registry);看一下
@Override
public void addViewControllers(ViewControllerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
foreach来回调用addViewControllers这个方法,这几个名字都一样的到底调的那个,我们点进去看看
default void addViewControllers(ViewControllerRegistry registry) {
}
调用的是WebMvcConfigurer的add
我们重写的也是这个,就是说明吧我们重写的那个视图controller也add进去了
所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
全面接管SpringMVC
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;
不加注解之前,访问首页:
给配置类加上注解:@EnableWebMvc
我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;
当然,我们开发中,不推荐使用全面接管SpringMVC
思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:
1、这里发现它是导入了一个类,我们可以继续进去看
@Configuration
@EnableWebMvc
public class MvcView implements WebMvcConfigurer
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc
2、它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ......
}
看到没有,又是这个WebMvcConfigurationSupport ,这个鬼
3、我们来回顾一下Webmvc自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
问题来了,我们刚刚自定义的时候也看到过这个WebMvcConfigurationSupport ,说是这个帮我们add了我们自己重写的viewcontroller
现在有这个了又说会导致自动配置不生效,咋回事
回头看看
1.我们是在WebMvcAutoConfigurationAdapter 这个类中点的EnableWebMvcConfiguration
2.这个WebMvcAutoConfigurationAdapter 是WebMvcAutoConfiguration 的静态内部类
3.所以,你在WebMvcAutoConfigurationAdapter 导入了DelegatingWebMvcConfiguration 才会生效我们自己配的
4.父类是不能识别子类的方法属性的
5.所以在父类中WebMvcAutoConfiguration 如果识别到了DelegatingWebMvcConfiguration
它导致自动配置不生效,不影响子类加载DelegatingWebMvcConfiguration
因为是需要自动配置类WebMvcAutoConfiguration 先生效
子类WebMvcAutoConfigurationAdapter 才能使用的,然后你导入DelegatingWebMvcConfiguration 就没事了
但是不能在父类WebMvcAutoConfiguration 就导入DelegatingWebMvcConfiguration ,导入就说明你想自己搞
自动配置类就不生效了
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
//这个EnableWebMvcConfiguration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware