springboot-08-SpringMVC的拓展、接管

一. 解读官方文档

官方文档点击这里:官方文档
在这里插入图片描述
解读:
自动配置在Spring的默认设置之上添加了以下功能:

  • 包含ContentNegotiatingViewResolverBeanNameViewResolverbeans(视图解析器)
  • 支持服务静态资源,包括对WebJars的支持
  • 自动注册ConverterGenericConverter(类型转换器)和Formatter(格式化器)beans
  • HttpMessageConverters(消息转换,转换Http请求和响应)的支持
  • 自动注册MessageCodesResolver(生成绑定错误消息)
  • 静态index.html支持(首页映射)
  • 自定义Favicon支持(图标自定义)
  • 自动使用ConfigurableWebBindingInitializer bean(数据web的初始化绑定)

使用方法:
如果要保留这些SpringBoot MVC特点并添加更多的MVC功能(拦截器,格式化程序,视图控制器和其他功能),则将@Configuration注解添加到类型为WebMvcConfigurer的类上,但不添加@EnableWebMvc注解

如果要提供RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver的自定义实例,并且仍然保留Spring Boot MVC自定义,则可以声明WebMvcRegistrations类型的bean,并使用它提供这些组件的自定义实例

如果要完全控制Spring MVC,则可以添加用@EnableWebMvc注解的自己的@Configuration,或者按照@EnableWebMvc的Javadoc中的说明添加自己的@Configuration注解的DelegatingWebMvcConfiguration

二. 拓展SpringMVC

根据官方文档:如果要保留这些SpringBoot MVC特点并添加更多的MVC功能(拦截器,格式化程序,视图控制器和其他功能),则将@Configuration注解添加到类型为WebMvcConfigurer的类上,但不添加@EnableWebMvc注解

1. 拓展原理

我们查看SpringBoot底层webmvc自动配置类WebMvcAutoConfiguration中的自动适配类WebMvcAutoConfigurationAdapter
在这里插入图片描述

可以看到这样一个注解@Import(EnableWebMvcConfiguration.class)

也就是导入了EnableWebMvcConfiguration这个配置类

我们继续查看该类源码,发现它继承了一个父类DelegatingWebMvcConfiguration
在这里插入图片描述
我们继续查看DelegatingWebMvcConfiguration的源码,可以找到这样一个方法
在这里插入图片描述

@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
	//如果WebMvcConfigurer的配置不为空
	if (!CollectionUtils.isEmpty(configurers)) {
		//加入到配置中
		this.configurers.addWebMvcConfigurers(configurers);
	}
}

该方法就是从Spring容器中获取所有的webmvcConfigurer,及所有的配置类

也就是SpringBoot在底层自动获取了所有的配置类,包括默认的配置类以及我们自定义的配置类,这也就是我们拓展的原理,我们可以新增自己配置类,只要是webmvcConfigurer类,spring都能获取到,然后注入到Spring容器中,SpringBoot即可自动配置

2. 环境搭建:编写拓展配置类

扩展配置类一般都放在,新建的config包中
在这里插入图片描述

通过官方文档的介绍,我们需要将@Configuration注解添加到类型为WebMvcConfigurer的类上,但不添加@EnableWebMvc注解

因此我们现在IDEA中搜索(连按shift)一下WebMvcConfigurer,可以发现它是一个接口
在这里插入图片描述
因此我们需要自定义的配置类MyMvcConfiguration需要实现这个接口

package com.zsr.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    
}

3. 示例:拓展视图解析器

1. 默认视图解析器源码分析

官网文档中提到SpringBoot默认的一个视图解析器ContentNegotiatingViewResolver,我们来分析官方提供的视图解析器是如何实现的
在这里插入图片描述

发现它实现了ViewResolver接口,我们继续查看ViewResolver的源码

在这里插入图片描述
其中有一个解析视图名称方法resolveViewName

我们查看ContentNegotiatingViewResolver重写ViewResolver接口的该方法

	@Override
	@Nullable
	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;
			}
		}

		String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
				" given " + requestedMediaTypes.toString() : "";

		if (this.useNotAcceptableStatusCode) {
			if (logger.isDebugEnabled()) {
				logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
			}
			return NOT_ACCEPTABLE_VIEW;
		}
		else {
			logger.debug("View remains unresolved" + mediaTypeInfo);
			return null;
		}
	}

可以发现该方法,就是从候选的视图中筛选出最好的视图,我们点开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);
				}
				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;
	}

那么所有的视图是从那里来的呢?我们可以找到initServletContext方法,该方法就是得到所有视图解析器的方法

	@Override
	protected void initServletContext(ServletContext servletContext) {
		//从BeanFactoryUtils工具类中获取容器中的所有视图解析器
		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);
				}
			}
		}
		else {
			for (int i = 0; i < this.viewResolvers.size(); i++) {
				ViewResolver vr = this.viewResolvers.get(i);
				if (matchingBeans.contains(vr)) {
					continue;
				}
				String name = vr.getClass().getName() + i;
				obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
			}

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

其中从BeanFactoryUtils工具类中获取容器中的所有视图解析器,然后再对其进行赋值,拿来组合

因此:SpringBoot默认的ContentNegotiatingViewResolver视图解析器就是用来组合所有的视图解析器的

2. 自定义视图解析器

在上述编写好的配置类MyMvcConfig类中编写一个自己的视图解析器静态内部类,实现视图解析器ViewResolver接口,重写其抽象方法

@Configuration
public class MyMVCConfig implements WebMvcConfigurer {
    
    //将自定义的视图解析器注入到容器中
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }
    
    //声明一个自定义的视图解析器
    static class MyViewResolver implements ViewResolver{

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

接下来我们通过打断点查看我们自定义的视图解析器是否生效

我们给DispatcherServlet类中的doDispatch方法加个断点进行调试一下,因为所有的请求都会走到这个方法中
在这里插入图片描述

然后我们Debug主程序在这里插入图片描述
程序启动后,访问localhost:8080,程序进入doDispatcher方法

我们点击this可以查看所有的视图解析器对象
在这里插入图片描述

`ContentNegotiatingViewResolver`:SpringBoot默认视图解析器
`BeanNameViewResolver`:SpringBoot默认视图解析器
`TymeleafViewResolver`:导入了Tymeleaf模板引擎后Tymeleaf的视图解析器
`MyViewResolver`:我们自定义的视图解析器

我们发现了自定义的视图解析器,证明ContentNegotiatingViewResolver成功将我们自定义的视图解析器组合进来;

4. 修改SpringBoot默认配置

上述我们通过拓展视图解析器的例子简单了解了如何在SpringBoot新增自定义功能

我们还可以直接通过修改默认的配置达到自己想要的效果,接下来我们以修改默认的日期格式为例,找寻修改默认配置的方法

示例:修改默认日期格式

SpringBoot底层的自动装配,都在WebMvcAutoConfiguration自动配置类中,可以在其中找到关于格式化的方法mvcConversionService()
在这里插入图片描述

可以发现是从配置文件中获取格式化的规则,然后我们按住ctrl点击mvcProperties
在这里插入图片描述
然后点击进入WebMvcPropertieswebMVC的配置文件类,可以找到关于日期格式化的方法

可以看到我们可以通过spring.mvc.format.date在配置文件中设置自定义日期格式,但是已经不推荐使用了

@Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
public String getDateFormat() {
    return this.format.getDate();
}

我们再点击getDate方法

public String getDate() {
   return this.date;
}

再点击date

public static class Format {
   /**
    * Date format to use, for example `dd/MM/yyyy`.
    */
   private String date;
   ...
}

可以看到默认的日期格式为dd/MM/yyyy

我们可以在配置文件中修改默认的格式,自定义日期格式,比如这里为dd-MM-yyyy

spring.mvc.format.date=dd-MM-yyyy

如果配置了自己的格式化方式,就会注册到Bean中生效,以后就必须按照自定义的日期格式书写

其余的默认配置亦是如此,我们都可以在源码中找到答案

5. 总结

通过上述拓展原理以及示例,我们可以得出以下结论:

  • SpringBoot的底层,大量用到了上述设计细节思想,很多的自动配置,原理都相同;
  • 如果我们想自定义一些功能组件,只需要给Spring容器中添加这个组件,然后SpringBoot就会帮我们自动配置了
  • SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
  • 如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

三. 全面接管SpringMVC

1. 什么是全面接管?

全面接管:让SpringBoot对SpringMVC的自动配置失效,所有东西都使用我们自己配置得!

  • 实际开发中,并不推荐使用全面接管SpringMVC
  • 而是推荐拓展配置,使用SpringBoot的自动配置和我们自己写的扩展配置相结合的方式进行开发

在官方文档中可以看到:如果要完全控制Spring MVC

  • 可以添加用@EnableWebMvc注解的自己的@Configuration

  • 或者按照@EnableWebMvc的Javadoc中的说明添加自己的@Configuration注解的DelegatingWebMvcConfiguration

2. 测试

根据官方文档,我们在配置类上添加@EnableWebMvc注解即实现全面接管SpringMVC

@Configuration
@EnableWebMvc
public class MyMVCConfig implements WebMvcConfigurer {

    //将自定义的视图解析器注入到容器中
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //声明一个自定义的视图解析器
    static class MyViewResolver implements ViewResolver{

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

我们重启主程序进行测试,访问localhost:8080

在这里插入图片描述
可以看到先前配置的主页已经失效,所有都回归到了最初的样子

3. @EnableWebMvc原理

为什么加了这个注解,自动配置就失效了,我们来一探究竟~

我们查看@EnableWebMvc注解源码,发现导入了类DelegatingWebMvcConfiguration

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

进入该类看看,发现它继承了一个父类WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
   	//...
}

也就是说,我们使用了@EnableWebMvc注解,就相当于导入了WebMvcConfigurationSupport

我们再查看Webmvc自动配置类WebMvcAutoConfiguration

在这里插入图片描述
其中有这样一个注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

意思是:不存在WebMvcConfigurationSupport类的情况下配置生效

也就是如果这个类存在,则整个WebMvcAutoConfiguration自动配置类会失效,即SpringBoot的自动配置全部失效

而我们导入@EnableWebMvc注解,就相当于导入了WebMvcConfigurationSupport类,因此SpringBoot所有的自动配置失效

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值