详解SpringBoot中如何扩展SpringMVC

SpringMVC的自动配置参见官方文档https://docs.spring.io/spring-boot/docs/2.1.18.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-auto-configuration

Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。

自动配置在Spring的默认值之上添加了以下功能:

  • 包含ContentNegotiatingViewResolverBeanNameViewResolver
  • 支持提供静态资源,包括对WebJars的支持。
  • 自动注册ConverterGenericConverterFormatter等类。
  • 支持HttpMessageConverters
  • 自动注册MessageCodesResolver
  • 静态index.html支持。
  • 定制Favicon支持。
  • 自动使用ConfigurableWebBindingInitializerbean。

如果您想保留Spring Boot MVC功能并想添加其他[MVC配置](拦截器,格式化程序,视图控制器和其他功能),则可以添加自己@Configuration的type类WebMvcConfigurer不添加 @EnableWebMvc。如果您希望提供或自定义实例RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolverWebMvcRegistrationsAdapter等,则可以声明一个实例来提供此类组件。

如果您想完全控制Spring MVC,可以使用添加自己的@Configuration注释@EnableWebMvc

这里扩展Spring MVC不要添加@EnableWebMvc,而完全接管就要添加,是什么原因呢?

其实从Springboot自动配置原理出发我们大致也能知道,应该就是添加了@EnableWebMvc会使WebMvcAutoConfiguration失效。

那么到底是不是呢?我们来看看源码:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

@ EnableWebMvc注解的作用就是导入了DelegatingWebMvcConfiguration这个类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UvTUGrZJ-1614565065501)(G:\Typora\SpringBoot学习笔记.asserts\image-20201102203613935.png)]
WebMvcAutoConfiguration上面有这样一个条件,就是容器没有WebMvcConfigurationSupport这个类是才会生效

那和@ EnableWebMvc又有啥关系呢?继续看DelegatingWebMvcConfiguration这个类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GibI1QyB-1614565065506)(G:\Typora\SpringBoot学习笔记.asserts\image-20201102203800405.png)]

wtf!?DelegatingWebMvcConfiguration是WebMvcConfigurationSupport的子类!

所以说 @EnableWebMvc注解可不能乱加,否则整个SpringMVC配置都得我们自己写。

我们以前用SpringMVC的一个核心就是视图解析器,Springboot中到底是怎么应用的呢?

打开WebMvcAutoConfiguration,找到如下方法:

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
   ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
   resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
   // ContentNegotiatingViewResolver uses all the other view resolvers to locate
   // a view so it should have a high precedence
   // ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
   resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
   return resolver;
}

点进ContentNegotiatingViewResolver这个类中看看它是如何解析视图的

@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 = 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;
      }
   }
    ...
}

点进getCandidateViews方法看看怎么获取到候选视图

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.add(view);
         }
          ...
      }
   }

可以发现,这里它就是拿到ViewResolvers这个属性(视图解析器),然后开始遍历。那么这里的视图解析器是在哪里获取到的呢???找找这个ViewResolvers属性。

@Override
protected void initServletContext(ServletContext servletContext) {
    // 这里它是从beanFactory工具中获取容器中的所有视图解析器
    // ViewRescolver.class 把所有的视图解析器组合
   Collection<ViewResolver> matchingBeans =
         BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
   if (this.viewResolvers == null) {
      this.viewResolvers = new ArrayList<>(matchingBeans.size());
      for (ViewResolver viewResolver : matchingBeans) {
         if (this != viewResolver) {
            this.viewResolvers.add(viewResolver);
         }
      }
   }
    ...
}

看到ApplicationContext是不是很熟悉?!

原来就是从容器中去获取到的视图解析器,那么我们要实现自己的视图解析器也是这个逻辑。

点开ViewResolver,原来是个接口

public interface ViewResolver {
   @Nullable
   View resolveViewName(String viewName, Locale locale) throws Exception;

}

我们实现ViewResolver接口,并将自己的视图解析器加入容器中即可生效

@Bean
public ViewResolver getMyViewResolver(){
    return new MyViewResolver();
}

private static class MyViewResolver implements ViewResolver{

    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        return null;
    }
}

如何查看是否生效呢?可以去DispatcherServlet中的doDispatch方法打断点调试,因为所有的请求都会走这个方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HimSgpjk-1614565065507)(G:\Typora\SpringBoot学习笔记.asserts\image-20201102202507709.png)]

1.启动应用

2.从浏览器发起请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TlkSkTBO-1614565065509)(G:\Typora\SpringBoot学习笔记.asserts\image-20201102202657825.png)]

3.查看控制台信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uR9K0YYp-1614565065510)(G:\Typora\SpringBoot学习笔记.asserts\image-20201102203103392.png)]

自定义的视图解析器已然生效!

类似的,如果我们要自定义其他的一些组件,也是如此,直接将写好的组件加入容器中就可以了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值