最全SpringMVC 九大组件之 ViewResolver 深入分析,分享面试经历的网站

最后

经过日积月累, 以下是小编归纳整理的深入了解Java虚拟机文档,希望可以帮助大家过关斩将顺利通过面试。
由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。







由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

在这个方法中,View 加载出来后,会调用其 checkResource 方法判断 View 是否存在,如果存在就返回 View,不存在就返回 null。

这是一个非常关键的步骤,但是我们常用的视图对此的处理却不尽相同:

  • FreeMarkerView:会老老实实检查。

  • ThymeleafView:没有检查这个环节(Thymeleaf 的整个 View 体系不同于 FreeMarkerView 和 JstlView)。

  • JstlView:检查结果总是返回 true。

至此,我们就找到了所有的候选 View,但是大家需要注意,这个候选 View 不一定存在,在有 Thymeleaf 的情况下,返回的候选 View 不一定可用,在 JstlView 中,候选 View 也不一定真的存在。

接下来调用 getBestView 方法,从所有的候选 View 中找到最佳的 View。getBestView 方法的逻辑比较简单,就是查找看所有 View 的 MediaType,然后和请求的 MediaType 数组进行匹配,第一个匹配上的就是最佳 View,这个过程它不会检查视图是否真的存在,所以就有可能选出来一个压根没有的视图,最终导致 404。

这就是 ContentNegotiatingViewResolver#resolveViewName 方法的工作过程。

那么这里还涉及到一个问题,ContentNegotiatingViewResolver 中的 ViewResolver 是从哪里来的?这个有两种来源:默认的和手动配置的。我们来看如下一段初始化代码:

@Override

protected void initServletContext(ServletContext servletContext) {

Collection 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. 首先获取到 matchingBeans,这个是获取到了 Spring 容器中的所有视图解析器。

  2. 如果 viewResolvers 变量为 null,也就是开发者没有给 ContentNegotiatingViewResolver 配置视图解析器,此时会把查到的 matchingBeans 赋值给 viewResolvers。

  3. 如果开发者为 ContentNegotiatingViewResolver 配置了相关的视图解析器,则去检查这些视图解析器是否存在于 matchingBeans 中,如果不存在,则进行初始化操作。

这就是 ContentNegotiatingViewResolver 所做的事情。

4.AbstractCachingViewResolver


视图这种文件有一个特点,就是一旦开发好了不怎么变,所以将之缓存起来提高加载速度就显得尤为重要了。事实上我们使用的大部分视图解析器都是支持缓存功能,也即 AbstractCachingViewResolver 实际上有很多用武之地。

我们先来大致了解一下 AbstractCachingViewResolver,然后再来学习它的子类。

@Override

@Nullable

public View resolveViewName(String viewName, Locale locale) throws Exception {

if (!isCache()) {

return createView(viewName, locale);

}

else {

Object cacheKey = getCacheKey(viewName, locale);

View view = this.viewAccessCache.get(cacheKey);

if (view == null) {

synchronized (this.viewCreationCache) {

view = this.viewCreationCache.get(cacheKey);

if (view == null) {

view = createView(viewName, locale);

if (view == null && this.cacheUnresolved) {

view = UNRESOLVED_VIEW;

}

if (view != null && this.cacheFilter.filter(view, viewName, locale)) {

this.viewAccessCache.put(cacheKey, view);

this.viewCreationCache.put(cacheKey, view);

}

}

}

}

else {

}

return (view != UNRESOLVED_VIEW ? view : null);

}

}

  1. 首先如果没有开启缓存,则直接调用 createView 方法创建视图返回。

  2. 调用 getCacheKey 方法获取缓存的 key。

  3. 去 viewAccessCache 中查找缓存 View,找到了就直接返回。

  4. 去 viewCreationCache 中查找缓存 View,找到了就直接返回,没找到就调用 createView 方法创建新的 View,并将 View 放到两个缓存池中。

  5. 这里有两个缓存池,两个缓存池的区别在于,viewAccessCache 的类型是 ConcurrentHashMap,而 viewCreationCache 的类型是 LinkedHashMap。前者支持并发访问,效率非常高;后者则限制了缓存最大数,效率低于前者。当后者缓存数量达到上限时,会自动删除它里边的元素,在删除自身元素的过程中,也会删除前者 viewAccessCache 中对应的元素。

那么这里还涉及到一个方法,那就是 createView,我们也来稍微看一下:

@Nullable

protected View createView(String viewName, Locale locale) throws Exception {

return loadView(viewName, locale);

}

@Nullable

protected abstract View loadView(String viewName, Locale locale) throws Exception;

可以看到,createView 中调用了 loadView,而 loadView 则是一个抽象方法,具体的实现要去子类中查看了。

这就是缓存 View 的查找过程。

直接继承 AbstractCachingViewResolver 的视图解析器有四种:ResourceBundleViewResolver、XmlViewResolver、UrlBasedViewResolver 以及 ThymeleafViewResolver,其中前两种从 Spring5.3 开始就已经被废弃掉了,因此这里松哥就不做过多介绍,我们主要来看下后两者。

4.1 UrlBasedViewResolver

UrlBasedViewResolver 重写了父类的 getCacheKey、createView、loadView 三个方法:

getCacheKey

@Override

protected Object getCacheKey(String viewName, Locale locale) {

return viewName;

}

父类的 getCacheKey 是 viewName + '_' + locale,现在变成了 viewName。

createView

@Override

protected View createView(String viewName, Locale locale) throws Exception {

if (!canHandle(viewName, locale)) {

return null;

}

if (viewName.startsWith(REDIRECT_URL_PREFIX)) {

String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());

RedirectView view = new RedirectView(redirectUrl,

isRedirectContextRelative(), isRedirectHttp10Compatible());

String[] hosts = getRedirectHosts();

if (hosts != null) {

view.setHosts(hosts);

}

return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);

}

if (viewName.startsWith(FORWARD_URL_PREFIX)) {

String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());

InternalResourceView view = new InternalResourceView(forwardUrl);

return applyLifecycleMethods(FORWARD_URL_PREFIX, view);

}

return super.createView(viewName, locale);

}

  1. 首先调用 canHandle 方法判断是否支持这里的逻辑视图。

  2. 接下来判断逻辑视图名前缀是不是 redirect:,如果是,则表示这是一个重定向视图,则构造 RedirectView 进行处理。

  3. 接下来判断逻辑视图名前缀是不是 forward:,如果是,则表示这是一个服务端跳转,则构造 InternalResourceView 进行处理。

  4. 如果前面都不是,则调用父类的 createView 方法去构建视图,这最终会调用到子类的 loadView 方法。

loadView

@Override

protected View loadView(String viewName, Locale locale) throws Exception {

AbstractUrlBasedView view = buildView(viewName);

View result = applyLifecycleMethods(viewName, view);

return (view.checkResource(locale) ? result : null);

}

这里边就干了三件事:

  1. 调用 buildView 方法构建 View。

  2. 调用 applyLifecycleMethods 方法完成 View 的初始化。

  3. 检车 View 是否存在并返回。

第三步比较简单,没啥好说的,主要就是检查视图文件是否存在,像我们常用的 Jsp 视图解析器以及 Freemarker 视图解析器都会去检查,但是 Thymeleaf 不会去检查(具体参见:SpringMVC 中如何同时存在多个视图解析器一文)。这里主要是前两步,松哥要和大家着重说一下,这里又涉及到两个方法 buildView 和 applyLifecycleMethods。

4.1.1 buildView

这个方法就是用来构建视图的:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {

AbstractUrlBasedView view = instantiateView();

view.setUrl(getPrefix() + viewName + getSuffix());

view.setAttributesMap(getAttributesMap());

String contentType = getContentType();

if (contentType != null) {

view.setContentType(contentType);

}

String requestContextAttribute = getRequestContextAttribute();

if (requestContextAttribute != null) {

view.setRequestContextAttribute(requestContextAttribute);

}

Boolean exposePathVariables = getExposePathVariables();

if (exposePathVariables != null) {

view.setExposePathVariables(exposePathVariables);

}

Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();

if (exposeContextBeansAsAttributes != null) {

view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);

}

String[] exposedContextBeanNames = getExposedContextBeanNames();

if (exposedContextBeanNames != null) {

view.setExposedContextBeanNames(exposedContextBeanNames);

}

return view;

}

  1. 首先调用 instantiateView 方法,根据我们在配置视图解析器时提供的 viewClass,构建一个 View 对象返回。

  2. 给 view 配置 url,就是前缀+viewName+后缀,其中前缀后缀都是我们在配置视图解析器的时候提供的。

  3. 同理,如果用户在配置视图解析器时提供了 content-type,也将其设置给 View 对象。

  4. 配置 requestContext 的属性名称。

  5. 配置 exposePathVariables,也就是通过 @PathVaribale 注解标记的参数信息。

  6. 配置 exposeContextBeansAsAttributes,表示是否可以在 View 中使用容器中的 Bean,该参数我们可以在配置视图解析器时提供。

  7. 配置 exposedContextBeanNames,表示可以在 View 中使用容器中的哪些 Bean,该参数我们可以在配置视图解析器时提供。

就这样,视图就构建好了,是不是非常 easy!

4.1.2 applyLifecycleMethods

protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {

ApplicationContext context = getApplicationContext();

if (context != null) {

Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);

if (initialized instanceof View) {

return (View) initialized;

}

}

return view;

}

这个就是 Bean 的初始化,没啥好说的。

UrlBasedViewResolver 的子类还是比较多的,其中有两个比较有代表性的,分别是我们使用 JSP 时所用的 InternalResourceViewResolver 以及当我们使用 Freemarker 时所用的 FreeMarkerViewResolver,由于这两个我们比较常见,因此松哥在这里再和大家介绍一下这两个组件。

4.2 InternalResourceViewResolver

当我们使用 JSP 时,可能会用到这个视图解析器。

InternalResourceViewResolver 主要干了 4 件事:

  1. 通过 requiredViewClass 方法规定了视图。

@Override

protected Class<?> requiredViewClass() {

return InternalResourceView.class;

}

  1. 在构造方法中调用 requiredViewClass 方法去确定视图,如果项目中引入了 JSTL,则会将视图调整为 JstlView。

  2. 重写了 instantiateView 方法,会根据实际情况初始化不同的 View:

@Override

protected AbstractUrlBasedView instantiateView() {

return (getViewClass() == InternalResourceView.class ? new InternalResourceView() :

(getViewClass() == JstlView.class ? new JstlView() : super.instantiateView()));

}

会根据实际情况初始化 InternalResourceView 或者 JstlView,或者调用父类的方法完成 View 的初始化。

  1. buildView 方法也重写了,如下:

@Override

protected AbstractUrlBasedView buildView(String viewName) throws Exception {

InternalResourceView view = (InternalResourceView) super.buildView(viewName);

if (this.alwaysInclude != null) {

view.setAlwaysInclude(this.alwaysInclude);

}

view.setPreventDispatchLoop(true);

return view;

}

这里首先调用父类方法构建出 InternalResourceView,然后配置 alwaysInclude,表示是否允许在使用 forward 的情况下也允许使用 include,最后面的 setPreventDispatchLoop 方法则是防止循环调用。

4.3 FreeMarkerViewResolver

FreeMarkerViewResolver 和 UrlBasedViewResolver 之间还隔了一个 AbstractTemplateViewResolver,AbstractTemplateViewResolver 比较简单,里边只是多出来了五个属性而已,这五个属性松哥在之前和大家分享 Freemarker 用法的时候都已经说过了(参见:Spring Boot + Freemarker 中的弯弯绕!),这里再和大家啰嗦下:

  1. exposeRequestAttributes:是否将 RequestAttributes 暴露给 View 使用。

笔者福利

以下是小编自己针对马上即将到来的金九银十准备的一套“面试宝典”,不管是技术还是HR的问题都有针对性的回答。

有了这个,面试踩雷?不存在的!

回馈粉丝,诚意满满!!!




本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

参见:Spring Boot + Freemarker 中的弯弯绕!),这里再和大家啰嗦下:

  1. exposeRequestAttributes:是否将 RequestAttributes 暴露给 View 使用。

笔者福利

以下是小编自己针对马上即将到来的金九银十准备的一套“面试宝典”,不管是技术还是HR的问题都有针对性的回答。

有了这个,面试踩雷?不存在的!

回馈粉丝,诚意满满!!!

[外链图片转存中…(img-jCvER3We-1715583932116)]
[外链图片转存中…(img-EFxpuF8H-1715583932117)]
[外链图片转存中…(img-nNw04WK2-1715583932117)]
[外链图片转存中…(img-fKCSq5zD-1715583932117)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值