SpringMVC视图解析流程

从dispatcherServlet开始

//1.根据当前请求找到哪个类能来处理
mappedHandler = this.getHandler(processedRequest);

//2.拿到处理器的适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

//3.适配器执行目标方法,返回modelandview对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

//4.将得到的mv(封装了model和视图信息)的对象传入此方法执行后就会去往页面
this.processDispatchResult(processedRequest, response, mappedHandler, mv, 
(Exception)dispatchException);

就是将域中的数据在页面上展示,也可以理解为页面就是用来渲染模型数据的

下面是dispatcherServlet中的processDispatchResult方法源码(非重要部分已被删除)
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
 @Nullable Exception exception) throws Exception {
       ###如果modelandview不为空且没被清理
        if (mv != null && !mv.wasCleared()) 
        ###调用render方法,其中mv中保存了模型数据,req/resp 能支持转发或重定向
            this.render(mv, request, response);
下面是dispatcherServlet中的this.render(mv, request, response)方法源码(非重要部分已被删除)
protected void render(ModelAndView mv, HttpServletRequest request, 
HttpServletResponse response) 
		###传入视图名和模型数据得到视图对象
        view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
        
		/**中间省略的代码。。。。**/
		###在上方的代码执行结束后拿到了视图对象,调用得到的视图对象的render方法
        view.render(mv.getModelInternal(), request, response);
1)进入this.resolveViewName(viewName, mv.getModelInternal(), locale, request)方法
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, 
Locale locale, HttpServletRequest request) throws Exception {
        if (this.viewResolvers != null) {
            ###拿到视图解析器list的迭代器
            Iterator var5 = this.viewResolvers.iterator();
            ###对视图解析器一一遍历,分别调用他们的resolveViewName方法,如果不能处理
            此视图名会返回空,就会接着调用遍历的下一个视图解析器继续执行,直到由视图对象
            并且返回###
            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                ###拿到视图解析器,执行视图解析器的resolveViewName方法,来得到视图对象
                注意:此处是dispatcherServlet的resolveViewName方法调用视图解析器的
                resolveViewName方法###
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }
知识点:internalResourceViewResolver继承UrlBasedViewResolver,UrlBasedViewResolver继承
AbstractCachingViewResolver,AbstractCachingViewResolver实现了ViewResolver接口

如果没有配置任何视图解析器,viewResolver虽然是默认配置的internalResourceViewResolver但是调用的resolveViewName方法是internalResourceViewResolver的父类UrlBasedViewResolver的父类AbstractCachingViewResolver的resolveViewName方法来生成view对象的(因为internalResourceViewResolver和UrlBasedViewResolver均未重写此方法)

AbstractCachingViewResolver的resolveViewName方法内部调用AbstractCachingViewResolver的createView方法
注意此处的大坑下面是AbstractCachingViewResolver的createView方法

public View resolveViewName(String viewName, Locale locale) throws Exception {
        if (!this.isCache()) {
            return this.createView(viewName, locale);
        } else {
            Object cacheKey = this.getCacheKey(viewName, locale);
            View view = (View)this.viewAccessCache.get(cacheKey);
            if (view == null) {
                synchronized(this.viewCreationCache) {
                    view = (View)this.viewCreationCache.get(cacheKey);
                    if (view == null) {
                    ###注意,这里调用的createView并不是AbstractCachingViewResolver本身的
                        view = this.createView(viewName, locale);

正如上面的注意所示:这里调用的createView()是UrlBasedViewResolver重写的createView()方法

在UrlBasedViewResolver的createView()方法中,首先对传入的视图名进行判断,先判断请求有无redirect,forward的前缀,如果有redirect前缀,则生成RedirectView对象,如果是forward:前缀,则直接创建internal ResourceView视图对象,如果没有前缀,调用 super.createView(viewName,locale)。到此处,才是调用AbstractCachingViewResolver的createView方法。

在AbstractCachingViewResolver的createView方法中,调用了其loadView()的抽象方法,而UrlBasedViewResolver重写了此抽象方法, UrlBasedViewResolver的loadView方法调用了其buildView方法,此方法大致内容为:给视图名拼串形成URL,设置了各种属性,最终返回了AbstractUrlBasedView对象。

此处有可能有人会有疑问:之前说到过,如果controller返回的视图名,如果使用了redirect:或者forward:前缀,则一律得写全名,例如 return “foward : success.html”;因为他们不在视图解析过程中拼串(拼接视图解析器配置得prefix和sufix)。但是forward前缀的在视图解析流程中,不是经过了UrlBasedViewResolver方法的处理,生成了internalResourceView视图对象吗?
答案:
带前缀的视图名,在UrlBasedViewResolver重写的createView()中根据判断,直接生成视图对象,forward:前缀的视图名是生成internalresourceVIew,其间它并没有经历任何的URL拼串,所以必须写全视图名才可。
不带前缀的视图名,在UrlBasedViewResolver的createView()中还是最终调用其父类AbstractCachingViewResolver的createView方法,最终由UrlBasedViewResolver的buildView方法生成AbstractUrlBasedView对象,而在buildView方法中,进行了拼串。

2)得到view对象后,dispatcherServlet的render()调用view.render()

下面是AbstractView的render()方法

public void render(@Nullable Map<String, ?> model, HttpServletRequest request, 
HttpServletResponse response){
	
	//将隐含模型的东西都封装好为一个map
    Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
    this.prepareResponse(request, response);
    
    ###此方法一调用,渲染要给页面的所有数据
    this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
    }

AbstractView的renderMergedOutputModel方法是抽象方法,会调用到internalResourceView的renderMergedOutputModel方法,下面是此方法源码:

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
 HttpServletResponse response) throws Exception {
 		###此方法将model数据通过req.setAttribute都放入请求域中了
        this.exposeModelAsRequestAttributes(model, request);
        
        this.exposeHelpers(request);
        
        ###拿到转发路径
        String dispatcherPath = this.prepareForRendering(request, response);

        ###拿到转发器
        RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
  
  		###执行转发(include方式或者forward方式)
            if (this.useInclude(request, response)) {
                response.setContentType(this.getContentType());
                rd.include(request, response);
            } else {
                rd.forward(request, response);
            }
         forward方法是把请求的内容转发到另bai外du的一个servlet.而include是把另一个servlet处理过后
         的内容拿过来.
		 举例来说比如在servlet1打一句out.print("1111"),servlet2打上out.print("22222"),
		 在servlet1中用daoforward命令会转到servlet2中,显示22222.而在servlet1中使用include方法会依然
		 在servlet1的页面中,但是在1111后打出22222
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值