文章目录

  • 官网阅读
  • ContentNegotiatingViewResolver 内容协商视图解析器
  • 转换器和格式化器
  • 修改SpringBoot的默认配置
  • 全面接管SpringMVC


官网阅读

在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。

只有把这些都搞清楚了,我们在之后使用才会更加得心应手。
途径一:源码分析!
途径二:官方文档!

地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

// 初始化数据绑定器:帮我们把请求数据绑定到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的方法

spring boot 转义 springboot自定义转换器_spring boot 转义


我们可以点进这类看看!找到对应的解析视图的代码;

@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方法 加个断点进行调试一下

因为所有的请求都会走到这个方法中

spring boot 转义 springboot自定义转换器_mvc_02


3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;找到this

spring boot 转义 springboot自定义转换器_mvc_03


找到视图解析器,我们看到我们自己定义的就在这里了;

spring boot 转义 springboot自定义转换器_视图解析器_04


所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情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中生效,我们可以在配置文件中配置日期格式化的规则:

spring boot 转义 springboot自定义转换器_java_05

修改SpringBoot的默认配置

这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。

SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;

@Configuration
public class MvcView implements WebMvcConfigurer {

重写里面的方法就可以扩展了,我们看看有什么方法可以重写的

spring boot 转义 springboot自定义转换器_spring boot_06


配置路径匹配

配置内容协商

配置异步支持

配置默认Servlet处理

添加格式化程序

添加拦截器

添加资源处理程序

添加Cors映射

添加视图控制器

配置视图解析程序

添加参数解析器

添加返回值处理程序

配置消息转换器

扩展消息转换器

配置处理程序异常解决程序

扩展处理程序异常解决程序

获取验证程序

获取消息代码解析程序

spring boot 转义 springboot自定义转换器_spring boot 转义_07


我们就重写一个添加视图控制器

@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");
  }

spring boot 转义 springboot自定义转换器_spring boot 转义_08


spring boot 转义 springboot自定义转换器_spring boot 转义_09


确实也跳转过来了!所以说,我们要扩展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) {
	}

spring boot 转义 springboot自定义转换器_java_10


调用的是WebMvcConfigurer的add

我们重写的也是这个,就是说明吧我们重写的那个视图controller也add进去了

所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;

全面接管SpringMVC

全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个@EnableWebMvc。

我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;

不加注解之前,访问首页:

spring boot 转义 springboot自定义转换器_视图解析器_11


给配置类加上注解:@EnableWebMvc

spring boot 转义 springboot自定义转换器_视图解析器_12


我们发现所有的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