请求来了之后,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
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了,这一章确实很难,绕来绕去的很多,加上现在做的东西都是前后端分离了,所以感觉这一章节对自己的帮助不是太多,就不写太多了,把难点写出来和大家分享一下就可以了。