SpringMVC 九大组件之 ViewResolver 深入分析,2024年最新docker容器面试题

可以看到,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 使用。

  2. allowRequestOverride:当 RequestAttributes 和 Model 中的数据同名时,是否允许 RequestAttributes 中的参数覆盖 Model 中的同名参数。

  3. exposeSessionAttributes:是否将 SessionAttributes 暴露给 View 使用。

  4. allowSessionOverride:当 SessionAttributes 和 Model 中的数据同名时,是否允许 SessionAttributes 中的参数覆盖 Model 中的同名参数。

  5. exposeSpringMacroHelpers:是否将 RequestContext 暴露出来供 Spring Macro 使用。

这就是 AbstractTemplateViewResolver 特性,比较简单,再来看 FreeMarkerViewResolver。

public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {

public FreeMarkerViewResolver() {

setViewClass(requiredViewClass());

}

public FreeMarkerViewResolver(String prefix, String suffix) {

this();

setPrefix(prefix);

setSuffix(suffix);

}

@Override

protected Class<?> requiredViewClass() {

return FreeMarkerView.class;

}

@Override

protected AbstractUrlBasedView instantiateView() {

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

}

}

FreeMarkerViewResolver 的源码就很简单了,配置一下前后缀、重写 requiredViewClass 方法提供 FreeMarkerView,重写 instantiateView 方法完成 View 的初始化。

ThymeleafViewResolver 继承自 AbstractCachingViewResolver,具体的工作流程和前面的差不多,因此这里也就不做过多介绍了。需要注意的是,ThymeleafViewResolver#loadView 方法并不会去检查视图模版是否存在,所以有可能会最终会返回一个不存在的视图(参见:SpringMVC 中如何同时存在多个视图解析器一文)。

5.ViewResolverComposite


最后我们再来看下 ViewResolverComposite,ViewResolverComposite 其实我们在前面的源码分析中已经多次见到过这种模式了,通过 ViewResolverComposite 来代理其他的 ViewResolver,不同的是,这里的 ViewResolverComposite 还为其他 ViewResolver 做了一些初始化操作。为对应的 ViewResolver 分别配置了 applicationContext 以及 servletContext。这里的代码比较简单,我就不贴出来了,最后在 ViewResolverComposite#resolveViewName 方法中,遍历其他视图解析器进行处理:

@Override

@Nullable

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

for (ViewResolver viewResolver : this.viewResolvers) {

View view = viewResolver.resolveViewName(viewName, locale);

if (view != null) {

return view;

}

}

return null;

}

6.小结


好啦,今天主要和小伙伴们聊了下 SpringMVC 中视图解析器的工作流程,结合松哥之前的文章SpringMVC 中如何同时存在多个视图解析器,相信大家对于 SpringMVC 中的视图解析器的理解会更进一步。
好啦,今天就先和大家聊这么多~

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:

  • Java基础部分

  • 算法与编程

  • 数据库部分

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

2966)]

  • 算法与编程

[外链图片转存中…(img-NoYkVCdg-1712673672966)]

  • 数据库部分

[外链图片转存中…(img-TLlZcMX8-1712673672967)]

  • 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)

[外链图片转存中…(img-UXB8Xcj0-1712673672967)]

这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。

作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-xqnVDS7S-1712673672967)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值