微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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认设置的基础上添加了以下功能


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方法 加个断点进行调试一下,因为所有的请求都会走到这个方法

在这里插入图片描述

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

找到this

在这里插入图片描述

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

在这里插入图片描述

如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了,剩下的事情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最基本的功能

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐