9.SpringMVC自动配置原理

官方文档相关说明

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

我们先去看看SpringBoot的官方文档,点击此处查看


If you want to build servlet-based web applications, you can take advantage of Spring Boot’s auto-configuration for Spring MVC or Jersey.

如果您想构建基于 servlet 的 Web 应用程序,您可以利用 Spring Boot 的 Spring MVC 或 Jersey 自动配置。


The Spring Web MVC framework (often referred to as “Spring MVC”) is a rich “model view controller” web framework. Spring MVC lets you create special @Controller or @RestController beans to handle incoming HTTP requests. Methods in your controller are mapped to HTTP by using @RequestMapping annotations.

Spring Web MVC 框架(通常称为“Spring MVC”)是一个丰富的“模型视图控制器”Web 框架。Spring MVC 允许您创建特殊的@Controller@RestControllerbean 来处理传入的 HTTP 请求。控制器中的方法通过使用@RequestMapping注解映射到 HTTP。


Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

Spring Boot 为 Spring MVC 提供了自动配置,它可以很好地与大多数应用程序一起工作。


The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  • Support for serving static resources, including support for WebJars (covered later in this document).
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
  • Support for HttpMessageConverters (covered later in this document).
  • Automatic registration of MessageCodesResolver (covered later in this document).
  • Static index.html support.
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

自动配置在Spring默认设置的基础上添加了以下功能:

  • 包括ContentNegotiatingViewResolverBeanNameViewResolver这两个视图解析器。
  • 支持提供静态资源文件夹的路径,包括对 WebJars 的支持(本文档后面会介绍)。
  • 自动注册Converter、(这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为 int类型)GenericConverterFormatter(格式化器,比如页面给我们了一个2022-1-1,它会给我们自动格式化为Date对象)bean。
  • 支持HttpMessageConverters(SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串)(本文档稍后介绍)。
  • 自动注册MessageCodesResolver(用来定义错误代码生成规则的)(本文档稍后介绍)。
  • 静态index.html支持。
  • ConfigurableWebBindingInitializerbean( 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中)的自动使用(本文档稍后会介绍)。

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

如果您想保留 Spring Boot MVC 功能并进行更多MVC 自定义(拦截器、格式化程序、视图控制器和其他功能),您可以添加自己的@Configuration的类,类型为WebMvcConfigurer添加 @EnableWebMvc


If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

如果您想提供RequestMappingHandlerMapping``RequestMappingHandlerAdapterExceptionHandlerExceptionResolver 的自定义实例,并且仍然保留 Spring Boot MVC 自定义,则可以声明一个类型为WebMvcRegistrations的 bean并使用它来提供这些组件的自定义实例。


If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

如果你想完全控制 Spring MVC,你可以添加你自己的@Configuration并用@EnableWebMvc进行 注释,或者添加你自己的@Configuration 带注释的 DelegatingWebMvcConfiguration,如@EnableWebMvc


ContentNegotiatingViewResolver

现在我们就来看看这个由 SpringBoot 自动配置了 SpringMVC 的ContentNegotiatingViewResolver 内容协商视图解析器,这就是我们之前学习的SpringMVC的视图解析器,它会根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。

我们再来看看源码,找到 WebMvcAutoConfiguration 中的 ContentNegotiatingViewResolver,发现这个 ContentNegotiatingViewResolver 是 WebMvcAutoConfigurationAdapter 类的 viewResolver方法返回的结果

		@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使用所有其他视图解析器来定位
			// ContentNegotiatingViewResolver uses all the other view resolvers to locate
            //所以它应该有很高的优先级
			// a view so it should have a high precedence
            //设置顺序
			resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
            //返回这个解析器
			return resolver;
		}

我们点进 ContentNegotiatingViewResolver 这个类看看,找到对应的解析视图的代码

//参数可为null
@Nullable
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        //获取RequestContextHolder的请求属性
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        //声明断言
        Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        //获取ContentNegotiatingViewResolver类的媒体类型
        List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
        //判断这个请求的媒体类型是否不为空
        if (requestedMediaTypes != null) {
            //获取候选的视图
            List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
            //获取最佳的视图
            View bestView = this.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");
            Iterator var5 = this.viewResolvers.iterator();

            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    candidateViews.add(view);
                }

                Iterator var8 = requestedMediaTypes.iterator();

                while(var8.hasNext()) {
                    MediaType requestedMediaType = (MediaType)var8.next();
                    List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                    Iterator var11 = extensions.iterator();

                    while(var11.hasNext()) {
                        String extension = (String)var11.next();
                        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;
    }

可以看到它是把所有的视图解析器拿来,进行while循环,挨个解析

所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的


我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的

    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());
            Iterator var3 = matchingBeans.iterator();

            while(var3.hasNext()) {
                viewResolver = (ViewResolver)var3.next();
                if (this != viewResolver) {
                    this.viewResolvers.add(viewResolver);
                }
            }
        } else {
            for(int i = 0; i < this.viewResolvers.size(); ++i) {
                viewResolver = (ViewResolver)this.viewResolvers.get(i);
                if (!matchingBeans.contains(viewResolver)) {
                    String name = viewResolver.getClass().getName() + i;
                    this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name);
                }
            }
        }

        AnnotationAwareOrderComparator.sort(this.viewResolvers);
        this.cnmFactoryBean.setServletContext(servletContext);
    }

我们可以自己给容器中添加一个视图解析器,这个类就会帮我们自动的将它组合进来


现在在我们的主程序中写一个视图解析器

@Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }
    
    private static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }

怎么看我们自己写的视图解析器有没有起作用呢?我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中

9.SpringMVC自动配置原理

然后Debug启动我们的项目,随便访问一个页面,看一下Debug信息

找到this

9.SpringMVC自动配置原理

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

9.SpringMVC自动配置原理

如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了,剩下的事情SpringBoot就会帮我们做了


修改SpringBoot的默认配置

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

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

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

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/test").setViewName("test");
    }
}

测试一下发现确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!


我们可以去分析一下原理

1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration

这个父类中有这样一段代码:

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();	
	......
	// 从容器中获取所有的webmvcConfigurer    
	@Autowired(
        required = false
    )
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }

    }

我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考

protected void addViewControllers(ViewControllerRegistry registry) {
        this.configurers.addViewControllers(registry);
    }

发现它调用了一个

public void addViewControllers(ViewControllerRegistry registry) {
        Iterator var2 = this.delegates.iterator();

        while(var2.hasNext()) {
            // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我 们配置的
            WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
            delegate.addViewControllers(registry);
        }

    }

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


全面接管SpringMVC

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

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

如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,当然,我们开发中不推荐使用全面接管SpringMVC


为什么加了一个注解,自动配置就失效了!我们看下源码

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

我们发现它导入了一个 DelegatingWebMvcConfiguration.class ,点进去发现这个类继承了一个父类 WebMvcConfigurationSupport

再回到WebMvcAutoConfiguration这个类

@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最基本的功能!

上一篇:springMVC乱码问题


下一篇:直播过程是如何实现的?带你探索直播系统源码的奥秘