12. Spring Boot中MVC自动配置原理

12. MVC自动配置原理

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

12.1 功能

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

  • 视图解析器
  • 支持静态资源文件夹的路径,以及webjars
  • Converter:自动转换器,就是前端提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
  • Formatter:格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象
  • SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串
  • 定义错误代码生成规则的
  • 首页映射
  • 图标自定义
  • 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中

12.1.1 视图解析器分析

ContentNegotiatingViewResolver 内容协商视图解析器

自动配置了ViewResolver

  1. 进入WebMvcAutoConfiguration.java查找ContentNegotiatingViewResolver 内容协商视图解析器

    @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使用其他视图解析器来定位视图,因此它具有较高的优先级
        resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return resolver;
    }
    
  2. ContentNegotiatingViewResolver.java 解析视图

    @Override
    @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) {
            // 获取候选的视图对象
            List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
            // 选择一个最适合的视图对象,然后把这个对象返回
            View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
            if (bestView != null) {
                return bestView;
            }
        }
        // .....
    }
    
    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");
    			// this.viewResolvers:遍历工具类(就是Bean)中所有的视图解析器
                for (ViewResolver viewResolver : this.viewResolvers) {
    				View view = viewResolver.resolveViewName(viewName, locale);
    				if (view != null) {
    					candidateViews.add(view);
    				}
    				for (MediaType requestedMediaType : requestedMediaTypes) {
    					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
    					for (String extension : extensions) {
    						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;
    	}
    
    
    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());
        }
        // ...............
    }
    

结论:ContentNegotiatingViewResolver 这个视图解析器会将所有的视图解析器组合起来

12.1.2 自定义视图解析器

  1. 创建MyMvcConfig.java

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    
        @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()方法加个断点

    img

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

    img

    找到视图解析器,可以看到里面有自定义的视图解析器

    img

    所以,如果想要使用自己定制化的东西,只需要给容器中添加这个组件就可以了

12.1.3 格式化转换器

此处源码分析以Spring Boot2.3.3版本为准

  1. 进入WebMvcAutoConfiguration.java查找 formatters

    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
       Format format = this.mvcProperties.getFormat();
       WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
             .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
       addFormatters(conversionService);
       return conversionService;
    }
    
  2. 进入 mvcProperties ,找到 DateFormat

    @Deprecated
    @DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
    public String getDateFormat() {
       return this.format.getDate();
    }
    
    @Deprecated
    public void setDateFormat(String dateFormat) {
       this.format.setDate(dateFormat);
    }
    
  3. format 内部集成了时间、日期、年月日时分秒,在我们的配置文件中可以修改它们

    # 示例
    spring.mvc.format.date=dd/MM/yyyy
    

12.2 修改Spring Boot的默认配置

此处源码分析以Spring Boot2.3.3版本为准

/**
 * 自定义类扩展MVC功能
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发生/neo,会跳转到test页面
        registry.addViewController("/neo").setViewName("test");
    }
}
  1. WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

  2. 类上面有个注解,在做其他自动配置时会自动导入

    @Import(EnableWebMvcConfiguration.class)
    
  3. EnableWebMvcConfiguration继承了一个父类:DelegatingWebMvcConfiguration.java,其中有这样一个方法

    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
        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
    	@Override
    	protected void addViewControllers(ViewControllerRegistry registry) {
    		this.configurers.addViewControllers(registry);
    	}
    }
    
  4. 再进入内部会跳转到WebMvcConfigurerComposite这个类,它实现了这个方法

    class WebMvcConfigurerComposite implements WebMvcConfigurer {
    
    	@Override
    	public void addViewControllers(ViewControllerRegistry registry) {
    		for (WebMvcConfigurer delegate : this.delegates) {
    			delegate.addViewControllers(registry);
    		}
    	}	
    }
    

结论:所有的WebMvcConfiguration都会起作用,除了Spring自己的配置类,自定义的配置类也会被调用

12.3 全面接管SpringMVC

只需要在配置类中加一个@EnableWebMvc注解,就可以使所有的SpringMVC自动配置全部失效

12.3.1 访问index.html

不加注解之前:

img

加上注解之后:

img

12.3.1 分析

  1. 进入注解内部

    @Import(DelegatingWebMvcConfiguration.class)
    public @interface EnableWebMvc {
    }
    
  2. 进入DelegatingWebMvcConfiguration ,它继承了WebMvcConfigurationSupport

    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    	// ...
    }
    
  3. 再进入WebMvcAutoConfiguration,类上面有一个注解

    // 这个注解会判断容器中有没有这个组件,没有的话,自动配置类才会生效
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    

结论:因为@EnableMvc导入了WebMvcConfigurationSupport 所以Spring的自动配置就失效了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值