1. 程式人生 > 其它 >SpringBoot裡MVC自動配置原理

SpringBoot裡MVC自動配置原理

技術標籤:G_網際網路分散式技術# G1_SpringBootspring bootjava

官網閱讀

在進行專案編寫前,我們還需要知道一個東西,就是SpringBoot對我們的SpringMVC還做了哪些配置, 包括如何擴充套件,如何定製。
只有把這些都搞清楚了,我們在之後使用才會更加得心應手。
途徑一:原始碼分析,
途徑二:官方文件! 地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-feature s-spring-mvc-auto-configuration

Spring MVC Auto-
configuration // Spring Boot為Spring MVC提供了自動配置,它可以很好地與大多數應用程式一起工作 Spring Boot provides auto-configuration for Spring MVC that works well with most applications. // 自動配置在Spring預設設定的基礎上添加了以下功能 The auto-configuration adds the following features on top of Spring’s defaults: // 包含檢視解析器 - Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支援靜態資原始檔夾的路徑,以及webjars - Support for serving static resources, including support for WebJars (covered later in this document)). // 自動註冊了Converter: // 轉換器,這就是我們網頁提交資料到後臺自動封裝成為物件的東西,比如把"1"字串自動轉換為 int型別 // Formatter:【格式化器,比如頁面給我們了一個2019-8-10,它會給我們自動格式化為Date對 象】 - Automatic registration of Converter,
GenericConverter, and Formatter beans. // HttpMessageConverters // SpringMVC用來轉換Http請求和響應的的,比如我們要把一個User物件轉換為JSON字串,可以 去看官網文件解釋 - Support for HttpMessageConverters (covered later in this document). // 定義錯誤程式碼生成規則的 - Automatic registration of MessageCodesResolver (covered later in this document). // 首頁定製 - Static index.html support. // 圖示定製 - Custom Favicon support (covered later in this document). // 初始化資料繫結器:幫我們把請求資料繫結到JavaBean中! - Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document). /* 如果您希望保留Spring Boot MVC功能,並且希望新增其他MVC配置(攔截器、格式化程式、檢視控制 器和其他功能), 則可以新增自己 的@configuration類,型別為webmvcconfiguer,但不新增@EnableWebMvc。 如果希望提供 RequestMappingHandlerMapping、RequestMappingHandlerAdapter或 ExceptionHandlerExceptionResolver的自定義 例項, 則可以宣告WebMVCregistrationAdapter例項來提供此類元件。 */ 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. 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. //如果您想完全控制Spring MVC,可以新增自己的@Configuration, //並用@EnableWebMvc進行 註釋 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.

我們來仔細對照,看一下它怎麼實現的,它告訴我們SpringBoot已經幫我們自動配置好了SpringMVC, 然後自動配置了哪些東西呢

ContentNegotiatingViewResolver 內容協商檢視解析器

自動配置了ViewResolver,就是我們之前學習的SpringMVC的檢視解析器;
即根據方法的返回值取得檢視物件(View),然後由檢視物件決定如何渲染(轉發,重定向)。
我們去看看這裡的原始碼:我們找到 WebMvcAutoConfiguration , 然後搜尋 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;
}

我們可以點進ContentNegotiatingViewResolver這類看看!找到對應的解析檢視的程式碼;

@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 = 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中看到他是把所有的檢視解析器拿來,進行迴圈,挨個解析!

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);
						}
					}
				}
			}
		}

所以得出結論:ContentNegotiatingViewResolver 這個檢視解析器就是用來組合所有的檢視解析器 的
我們再去研究下他的組合邏輯,看到有個屬性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);
			}
		}
	}
	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);
}

既然它是在容器中去找檢視解析器,我們是否可以猜想,我們就可以去實現一個檢視解析器了呢?

自己給容器中去新增一個檢視解析器

我們可以自己給容器中去新增一個檢視解析器;這個類就會幫我們自動的將它組合進來;我們去實現一

1、我們在我們的主程式中去寫一個檢視解析器來試試;

我們要做的就是編寫一個@Configuration註解類,並且型別要為WebMvcConfigurer,還不能標註 @EnableWebMvc註解;我們去自己寫一個;我們新建一個包叫config,寫一個類MyMvcConfig;

//擴充套件springmvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //ViewResolver 實現了檢視解析器介面的類,我們就可以把它看做檢視解析器
    @Bean//放到bean中
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //我們寫一個靜態內部類,檢視解析器就需要實現ViewResolver介面
    private static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {

            return null;
        }
    }
}

2、怎麼看我們自己寫的檢視解析器有沒有起作用呢?
我們給 DispatcherServlet 中的 doDispatch方法 加個斷點進行除錯一下,因為所有的請求都會走到這個 方法中
在這裡插入圖片描述
3、我們啟動我們的專案,然後隨便訪問一個頁面,看一下Debug資訊;
找到this
在這裡插入圖片描述
找到檢視解析器,我們看到我們自己定義的就在這裡了;
在這裡插入圖片描述
所以說,我們如果想要使用自己定製化的東西,我們只需要給容器中新增這個元件就好了!剩下的事情 SpringBoot就會幫我們做

轉換器和格式化器

WebMvcAutoConfiguration裡找到格式化轉換器:

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

點進dateFormat去:

public DateTimeFormatters dateFormat(String pattern) {
		if (isIso(pattern)) {
			this.dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE;
			this.datePattern = "yyyy-MM-dd";
		}
		else {
			this.dateFormatter = formatter(pattern);
			this.datePattern = pattern;
		}
		return this;
	}
private final WebMvcProperties mvcProperties;

可以看到在我們的Properties檔案中,我們可以進行自動配置它!

如果配置了自己的格式化方式,就會註冊到Bean中生效,我們可以在配置檔案中配置日期格式化的規 則:

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

其餘的就不一一舉例了,大家可以下去多研究探討即可!

修改SpringBoot的預設配置

這麼多的自動配置,原理都是一樣的,通過這個WebMVC的自動配置原理分析,我們要學會一種學習方 式,通過原始碼探究,得出結論;這個結論一定是屬於自己的,而且一通百通。

SpringBoot的底層,大量用到了這些設計細節思想,所以,沒事需要多閱讀原始碼!得出結論;

SpringBoot在自動配置很多元件的時候,先看容器中有沒有使用者自己配置的(如果使用者自己配置 @bean),如果有就用使用者配置的,如果沒有就用自動配置的;

如果有些元件可以存在多個,比如我們的檢視解析器,就將使用者配置的和自己預設的組合起來!
擴充套件使用SpringMVC 官方文件如下

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.

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.

我們要做的就是編寫一個@Configuration註解類,並且型別要為WebMvcConfigurer,還不能標註 @EnableWebMvc註解;我們去自己寫一個;我們新建一個包叫config,寫一個類MyMvcConfig;

//應為型別要求為WebMvcConfigurer,所以我們實現其介面 
//可以使用自定義類擴充套件MVC的功能 
@Configuration 
public class MyMvcConfig implements WebMvcConfigurer {
    @Override    
    public void addViewControllers(ViewControllerRegistry registry) {        
    // 瀏覽器傳送/test , 就會跳轉到test頁面;        
    registry.addViewController("/test").setViewName("test");    			     
    } 
}

我們去瀏覽器訪問一下:
在這裡插入圖片描述
我們要擴充套件SpringMVC,官方就推薦我們這麼去使用,既保SpringBoot 留所有的自動配置,也能用我們擴充套件的配置

我們可以去分析一下原理:

  • 1、WebMvcAutoConfiguration 是 SpringMVC的自動配置類,裡面有一個類 WebMvcAutoConfigurationAdapter
  • 2、這個類上有一個註解,在做其他自動配置時會匯入: @Import(EnableWebMvcConfiguration.class)
  • 3、我們點進EnableWebMvcConfiguration這個類看一下,它繼承了一個父類: DelegatingWebMvcConfiguration
@Configuration(proxyBeanMethods = false)
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);
		}
	}

4、我們可以在這個類中去尋找一個我們剛才設定的viewController當做參考,發現它呼叫了一個addViewControllers

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

5、我們點進去看一下

@Override
public void addViewControllers(ViewControllerRegistry registry) {
 // 將所有的WebMvcConfigurer相關配置來一起呼叫!包括我們自己配置的和Spring給我 們配置
	for (WebMvcConfigurer delegate : this.delegates) {
		delegate.addViewControllers(registry);
	}
}

所以得出結論:所有的WebMvcConfiguration都會被作用,不止Spring自己的配置類,我們自己的配置 類當然也會被呼叫

全面接管SpringMVC:@EnableWebMvc

官方文件:

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.

全面接管即:SpringBoot對SpringMVC的自動配置不需要了,所有都是我們自己去配置! 只需在我們的配置類中要加一個@EnableWebMvc

我們看下如果我們全面接管了SpringMVC了,我們之前SpringBoot給我們配置的靜態資源對映一定會無 效,我們可以去測試一下;

不加註解之前,訪問首頁:
在這裡插入圖片描述

給配置類加上註解: @EnableWebMvc

//擴充套件springmvc
@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {

在這裡插入圖片描述
我們開發中,不推薦使用全面接管SpringMVC

思考問題?為什麼加了一個註解,自動配置就失效了!我們看下原始碼:

1、這裡發現它是匯入了一個類,我們可以繼續進去看

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

2、它繼承了一個父類 WebMvcConfigurationSupport

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//....
}

3、我們來回顧一下Webmvc自動配置類

@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基本的功能!
在SpringBoot中會有非常多的擴充套件配置,只要看見了這個,我們就應該多留心注意~