SpringBoot源码学习之视图解析

SpringBoot源码学习之视图解析_第1张图片

请求来了之后,DispatchServlet里面会调用这个方法

	@Nullable
	protected View resolveViewName(String viewName, @Nullable Map model,
			Locale locale, HttpServletRequest request) throws Exception {

		if (this.viewResolvers != null) {
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
		return null;
	}

resolveViewName这个方法会遍历ContentNegotiatingViewResolver里面的所有viewResolver来解析请求,怎么解析的呢?

public View resolveViewName(String viewName, Locale locale) throws Exception {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
		List requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
			List 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;
		}
	}

1.首先要获取请求的mediaTypes List,也就是什么application/text,text/xml之类的一些东西,我们看一下getMediaTypes的代码:

	@Nullable
	protected List getMediaTypes(HttpServletRequest request) {
		Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
		try {
			ServletWebRequest webRequest = new ServletWebRequest(request);
			List acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest);
			List producibleMediaTypes = getProducibleMediaTypes(request);
			Set compatibleMediaTypes = new LinkedHashSet<>();
			for (MediaType acceptable : acceptableMediaTypes) {
				for (MediaType producible : producibleMediaTypes) {
					if (acceptable.isCompatibleWith(producible)) {
						compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible));
					}
				}
			}
			List selectedMediaTypes = new ArrayList<>(compatibleMediaTypes);
			MediaType.sortBySpecificityAndQuality(selectedMediaTypes);
			return selectedMediaTypes;
		}
		catch (HttpMediaTypeNotAcceptableException ex) {
			if (logger.isDebugEnabled()) {
				logger.debug(ex.getMessage());
			}
			return null;
		}
	}

List acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest);这行代码就是用来解析mediaTypes的,解析的时候使用到了manager中的各种策略来解析,策略是什么时候装配到manager里面的呢,就是在我configurer里面去干这件事情的,每个策略都会去解析请求。策略也是有固定顺序的,当时作者在理解这里的时候差点懵逼了,一直没看到策略的优先级,结果是在代码里面写死的了的,给大家看一下它的策略顺序:

public ContentNegotiationManager build() {
		List strategies = new ArrayList<>();

		if (this.strategies != null) {
			strategies.addAll(this.strategies);
		}
		else {
			if (this.favorPathExtension) {
				PathExtensionContentNegotiationStrategy strategy;
				if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
					strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
				}
				else {
					strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
				}
				strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
				if (this.useRegisteredExtensionsOnly != null) {
					strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
				}
				strategies.add(strategy);
			}

			if (this.favorParameter) {
				ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
				strategy.setParameterName(this.parameterName);
				if (this.useRegisteredExtensionsOnly != null) {
					strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
				}
				else {
					strategy.setUseRegisteredExtensionsOnly(true);  // backwards compatibility
				}
				strategies.add(strategy);
			}

			if (!this.ignoreAcceptHeader) {
				strategies.add(new HeaderContentNegotiationStrategy());
			}

			if (this.defaultNegotiationStrategy != null) {
				strategies.add(this.defaultNegotiationStrategy);
			}
		}

		this.contentNegotiationManager = new ContentNegotiationManager(strategies);
		return this.contentNegotiationManager;
	}

我们在看看manger接卸mediaTypes的代码如下,只要其中一个策略解析出来了,那么就停下来,其他的策略就别太辛苦了。

	@Override
	public List resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
		for (ContentNegotiationStrategy strategy : this.strategies) {
			List mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
				continue;
			}
			return mediaTypes;
		}
		return MEDIA_TYPE_ALL_LIST;
	}

其中一个叫HeaderContentNegotiationStrategy这个策略,它是解析请求头的,它会看请求头里面的ACCEPT属性,然后来确定mediaTypes。当然其他几个策略比如有解析请求参数的,这里就不讲了~

请求头解析好了后,我们要使用解析好的请求头,根据viewResolver的优先级去确定候选视图:

private List getCandidateViews(String viewName, Locale locale, List requestedMediaTypes)
			throws Exception {

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

总的说来就是这样:

拿到请求中用户需要的mediaTypes,这个mediaTypes有且只能被一个strategy解析拿到(如果说一个strategy解析出来的mediaTypes是空,就由下一个strategy来解析)。然后每个viewResolver拿出自己符合名称的view,而且还要根据mediaTypes拿到view对应的扩展名称,啥意思呢?比如Controller中return index,那么如果发现有什么index.html,index.pdf,index.xml都要被加入到候选队列中!可以看看下面的代码说明了一切:

private List getCandidateViews(String viewName, Locale locale, List requestedMediaTypes)
			throws Exception {

		List 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 extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					for (String extension : extensions) {
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}

在把候选view和mediatypes做匹配,如果发现不符合条件,就不返回这个view。个人感觉这里没有必要了,为什么呢,我前面在选候选集的时候不是已经根据requestedMediaTypes做了一些筛选了吗?后来我想了下,我想错了,这是因为我上面代码标注的部分可能返回的是不符合条件的哦,所以还是需要在做一次筛选。就这样,我被好了3个小时一直研究视图解析这个模块。OK了,这一章确实很难,绕来绕去的很多,加上现在做的东西都是前后端分离了,所以感觉这一章节对自己的帮助不是太多,就不写太多了,把难点写出来和大家分享一下就可以了。

你可能感兴趣的:(WebFlux,SpringBoot,Spring,源码,架构)