SpringMVC原始碼關於檢視解析渲染
之前我們完成了springmvc各元件的初始化,然後進入doDispatch()的方法,通過handlerMappings迭代出order優先順序最高的handlerMapping,然後呼叫handlerMapping的gethandler()方法獲取handlerExcutionChain物件,接著我們通過getHandlerAdapter()方法,傳入handler作為引數,獲得匹配的handlerAdapter,獲取到handlerExcutionChain和handlerAdapter後,我們呼叫介面卡的handler(req,res,handler)的方式獲取ModelAndView物件,這是前幾天完成的部分。以下引用連結https://blog.csdn.net/wangbiao007/article/details/50510274 來展示整個流程
通過ModelAndView我們獲取到了model和viewName,程式接著往下走,到方法applyDefaultViewName(processedRequest, mv)的呼叫,這個方法內部如下:
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
}
如果先前我們獲取到mv,並且mv是有viewName的,那麼這個方法沒有用,因為這個方法就是在我們有mv但是沒有viewName的情況下,給我們設定一個預設的viewName
程式接著往下走,到方法mappedHandler.applyPostHandle(processedRequest, response, mv)的呼叫,這個內部如下:
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
獲取到專案的攔截器,然後判斷攔截器是否存在,存在則對該請求進行測試(postHandler),如果有一個攔截器不成功,則請求失敗。
程式接著往下走,到本次最重要的檢視解析和渲染部分,也就是呼叫方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
該方法的程式碼如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
首先判斷一下是否異常,如果出現異常,判斷異常是否是ModelAndViewDefiningException的例項,如果是就直接呼叫getModelAndView方法返回一個mv,否則呼叫processHanlderException方法初始化mv物件
如果沒有異常出現,那麼判斷mv是否為空,為空丟擲異常,不為空則判斷mv,wasCleared()方法返回的值是否為真,我們來看下這個方法的實現:
public boolean wasCleared() {
return (this.cleared && isEmpty());
}
首先這個方法存在於ModelAndView類中,cleared的預設值設定為false,如果執行了clear()方法,則cleared的值置為true,接著是isEmpty()方法判斷:
public boolean isEmpty() {
return (this.view == null && CollectionUtils.isEmpty(this.model));
}
判斷完成後,呼叫DispatcherServlet的render(mv,req,res)方法,程式碼如下:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
首先介紹一下Locale物件,設定整個系統的執行語言,然後根據系統的執行語言來展示對應語言的頁面,平時我們總會出現亂碼的情況,那麼可以看下response的語言型別到底是否是我們設定的情況。
這個方法中有兩部分,第一部分是解析檢視,在if(mv.isReference()){}裡頭,第二部分是檢視渲染,在try{}catch{}程式碼塊中的,view.render(mv.getModelInternal(),req,res)
首先介紹一下第一部分解析檢視,mv.isReference()是判斷viewName是否存在,存在,則通過resolveViewName(mv.getViewName,mv.getModelInternal,locale,request)方法進行解析,該方法的程式碼如下:
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
迭代viewResolverss物件,然後呼叫viewResolver的resolverViewName(viewName,locale)方法,springMVC給我們提供了多種的resolverViewName的方法實現
通過這些實現來返回我們的view物件。
如果之前判斷isReference為false,則直接呼叫view.getView的方法獲取view。
解析成功view之後,就是執行渲染操作了,渲染操作呼叫剛剛解析獲得的view的render方法,該方法的內部實現如下:
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
首先先獲取到需要加入到request的model的map集合,呼叫方法createMergedOutputModel方法,然後呼叫prepareResponse(request,response),該方法是設定響應請求頭的兩個引數,程式碼如下:
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
if (generatesDownloadContent()) {
response.setHeader("Pragma", "private");
response.setHeader("Cache-Control", "private, must-revalidate");
}
}
最後一步就是呼叫renderMergedOutputModel()方法去將mergeModel合到請求裡,該方法的程式碼實現如下:
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}
前面四行都是配置requestDispatcher用的,我主要看了下方法exposeModelAsRequestAttributes(),程式碼如下:
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
for (Map.Entry<String, Object> entry : model.entrySet()) {
String modelName = entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
if (logger.isDebugEnabled()) {
logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
"] to request in view with name '" + getBeanName() + "'");
}
}
else {
request.removeAttribute(modelName);
if (logger.isDebugEnabled()) {
logger.debug("Removed model object '" + modelName +
"' from request in view with name '" + getBeanName() + "'");
}
}
}
}
發現了什麼,我們不使用框架的時候,如果想要將model傳到前端,就是需要使用request.setAttribute()的方法,也就是這個方法封裝了我們對於model到前端的操作。
RequestDispatcher 物件配置完成後,就是選擇它要進行的操作了,是使用include還是forward進行跳轉。至此,檢視的解析與渲染工作都完成了,並且也傳送到了前端頁面,完成了一個請求的整個流程。
總結
在獲取到ModelAndView物件後,先判斷在mv存在的情況下viewName是否存在,不存在就給定一個初始值,之後對請求進行攔截判斷,如果有一個不通過則請求失敗,都通過則進行下一步,呼叫DispatcherServlet.render()方法進行檢視的解析和渲染,呼叫resolverViewName()方法迭代viewResolvers,如果viewResolver.resolveViewName(viewName, locale)的返回結果不為空,則返回該結果,解析成功。
然後再進行檢視的渲染工作,通過前面獲取到的view,呼叫該物件的render(model,req,res)方法,方法內先獲取該請求的Controller方法中設定的model,然後設定響應請求頭,最後呼叫renderMergedOutputModel將model放到request中通過requestDispatcher.include(req,res)或者requestDispatcher.forward(req,res)的方式傳到前端,展現給使用者。
參考連結:
https://www.cnblogs.com/solverpeng/p/5743609.html
https://blog.csdn.net/qq924862077/article/details/52878507
https://www.jianshu.com/p/5f91b6d7591a
https://blog.csdn.net/prince2270/article/details/5891085
相關文章
- SpringMVC原始碼解析SpringMVC原始碼
- SpringMVC 檢視解析出錯SpringMVC
- SpringMVC DispatcherServlet原始碼解析SpringMVCServlet原始碼
- django rest framework 檢視原始碼解析DjangoRESTFramework原始碼
- springmvc配置thymeleaf檢視解析器SpringMVC
- 深入瞭解SpringMVC原始碼解析SpringMVC原始碼
- SpringMVC原始碼解析系列4-HandleAdapterSpringMVC原始碼APT
- SpringMVC原始碼解析系列3-HandleMappingSpringMVC原始碼APP
- SpringMVC原始碼解析系列2-DispatcherServletSpringMVC原始碼Servlet
- 【原始碼解析】React Native元件渲染原始碼React Native元件
- SpringMVC原始碼解析(1)-啟動過程SpringMVC原始碼
- SpringMVC原始碼之引數解析繫結原理SpringMVC原始碼
- SpringMVC原始碼解析 - HandlerAdater - ModelAndViewContainer上下文容器SpringMVC原始碼ViewAI
- 關於 Spring 中 getBean 的全流程原始碼解析SpringBean原始碼
- 關於開發檢視
- Spring 原始碼解析一:SpringMVC 的載入機制原始碼SpringMVC
- 如何檢視python原始碼Python原始碼
- SpringMVC原始碼分析1:SpringMVC概述SpringMVC原始碼
- SpringMVC原始碼分析SpringMVC原始碼
- pdf.js原始碼解析-渲染的時序分析JS原始碼
- Audio Kit 檢視檔案波形的相關原始碼,easy o原始碼
- SpringMVC原始碼分析原理SpringMVC原始碼
- 8.1關於動態效能檢視
- drf 檢視使用及原始碼分析原始碼
- 0x04. 檢視渲染
- Spring中AOP相關原始碼解析Spring原始碼
- springMvc原始碼解讀–AbstractUrlHandlerMappingSpringMVC原始碼APP
- 帝國cms原始碼怎麼檢視原始碼
- vue原始碼之資料控制檢視Vue原始碼
- GDB為什麼檢視不了原始碼?原始碼
- 檢視織夢CMS原始碼中的資料庫相關檔案原始碼資料庫
- Spring原始碼深度解析(郝佳)-學習-原始碼解析-基於註解注入(二)Spring原始碼
- SpringMVC原始碼分析2:SpringMVC設計理念與DispatcherServletSpringMVC原始碼Servlet
- openGauss資料庫原始碼解析——慢SQL檢測資料庫原始碼SQL
- Alink漫談(二十) :卡方檢驗原始碼解析原始碼
- 關於聊天室原始碼服務端渲染那些事,沒有那麼複雜原始碼服務端
- springmvc工作原理及原始碼分析SpringMVC原始碼
- SpringMVC之原始碼分析--ViewResolver(五)SpringMVC原始碼View