前面我們聊了關于Dispatcher邏輯處理大緻步驟,下面我們進行詳細的每個步驟分析
MultipartContent類型的request處理
對于請求的處理,Spring首先考慮的是對于Multipart的處理,如果是MultipartContent類型的request,則轉換request為MultipartHttpServletRequest類型的request.
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
else {
return this.multipartResolver.resolveMultipart(request);
}
}
// If not returned before: return original request.
return request;
}
每當我們上傳檔案的時候就是用的這段代碼的源碼。
根據request資訊尋找對應的handler
我們知道dispatcher擷取request後會将request交給handlerMapping處理。在這一步驟中它會尋找我們平時湧動的Controller,而我們看看自己代碼都知道,貌似Controller與handler沒有任何關聯,那麼這一步是如何封裝的呢?
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
return getHandler(request);
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
在之前我們說過,在系統啟動的時候,Spring會将所有的配置的或預設的handlerMapping類型的bean注冊到this.handlerMappings變量中,是以這個函數的目的就是周遊所有的handlerMapping,并調用其getHandler方法進行封裝處理。我們來看看其getHandler方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根據request擷取對應的handler
/*
* 函數中會首先使用getHandlerInternal方法根據request資訊擷取對應的handler,以我們平時的應用為例
* 此方法就是根據URL找到比對的Controller并傳回,如果沒有找到對應的Controller處理器那麼程式會嘗試
* 去查找配置中預設的處理器。
*/
Object handler = getHandlerInternal(request);
if (handler == null) {
//如果沒有request的handler則使用預設的handler
handler = getDefaultHandler();
}
//如果沒有預設的handler則無法繼續處理,傳回null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
/*
* 如果查找的handler為String類型的時候,那就意味着傳回的是配置的bean名稱,需要根據bean名稱查找對應的bean
* 最後還要通過getHandlerExecutionChain方法對傳回的Handler進行封裝,以滿足傳回類型的比對
*/
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
下面看下詳細過程:
1.根據request查找對應的Handler
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//截取用于比對的url有效路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//根據路徑尋找handler
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
//如果請求的路徑僅僅是“/”那麼使用RootHandler進行處理
rawHandler = getRootHandler();
}
if (rawHandler == null) {
//無法找到handler則使用預設handler
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
//根據beanName尋找handler
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
//模闆方法
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
//直接比對情況的處理
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
//通配符比對的處理
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
}
String bestPatternMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestPatternMatch = matchingPatterns.get(0);
}
if (bestPatternMatch != null) {
handler = this.handlerMap.get(bestPatternMatch);
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
根據URL擷取對應Handler的比對規則代碼實作起來雖然很長,但是并不難了解,考慮了直接比對與通配符兩種情況。其中主要提及的是buildPathExposingHandler函數,它将Handler封裝成了HandlerExecutionChain類型。
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
在函數中我們看到了通過将Handler以參數形式傳入,并建構HandlerExecutionChain類型執行個體,加入了兩個攔截器。此時我們似乎已經了解了Spring這樣大費周折的目的。鍊處理機制,是Spring中非常常用的處理方式,是AOP中的重要組成部分,可以友善地對目标對象進行擴充及攔截,這是非常優秀的設計。
2.加入攔截器到執行鍊
getHandlerExecutionChain函數最主要的目的是将配置中的對應攔截器加入到執行鍊中,以保證這些攔截器可以有效地作用于目标對象。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
chain.addInterceptors(getAdaptedInterceptors());
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
return chain;
}
沒找到對應的Handler的錯誤處理
每一個請求都應該對應着一個Handler,因為每個請求都會在背景有相應的邏輯處理,而邏輯實作就是在handler中,是以一旦遇到沒有找到handler的情況(正常情況下如果沒有URL比對的Handler,開發人員可以設定預設的Handler來處理請求,但是如果預設請求也未設定就會出現Handler為空的情況),就隻能通過response想使用者傳回錯誤資訊
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
根據目前Handler尋找對應的HandlerAdapter
在WebApplicationContext初始化過程中我們讨論了HandlerAdapter的初始化,了解了預設情況下普通的Web請求會交給SimpleControllerHandlerAdapter處理,下面我們以SimpleControllerHandlerAdapter為例來分析擷取擴充卡的邏輯。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
通過上面的函數,對于擷取擴充卡的邏輯無非就是周遊所有擴充卡來選擇合适的擴充卡并傳回他,而某個擴充卡是否适用于目前的Handler邏輯被封裝在具體的擴充卡中。進一步檢視SimpleControllerHandlerAdapter中的supports方法。
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
分析道這裡,一切已經明了,SimpleControllerHandlerAdapter就是用于處理普通的Web請求的,而且對于SpringMVC來說,我們會把邏輯封裝至Controller的子類中。例如我們掃描注解@Controller的時候,就是掃描了Controller的子類。
緩存處理
在研究Spring對緩存處理的功能支援前,我們先了解一個概念:Last-Modified緩存機制。
(1)在用戶端第一次輸入URL時,伺服器端會傳回内容和狀态嗎200,表示請求成功,同時會 添加一個“Last-Modefied”的響應頭,表示此檔案在伺服器上的最後更新時間,例如:“Last-Modified:Wed 14 Mar 2012 10:22:42 GMT”表示最後更新時間為(2012-03-14 10:22)。
(2)用戶端第二次請此URL時,用戶端會向霧浮起發送請求頭“If-Modified-Since”,詢問伺服器該事件隻有目前請求内容是否改變過,如果服務端内容沒有變化,則自動傳回HTTP304狀态碼(隻要響應頭,内容為空,這樣就節省了網絡寬帶)。
Spring提供的對Last-Modified機制的支援,隻需要實作LastModified接口如下執行個體:
public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified{
private long lastModified;
protected ModelAndView handleRequestInternal(HttpServletRequest req ,HttpServletResponse resp)throws Exception{
//點選後再次請求目前頁面
resp.getWriter().write("<a href=''>this</a>");
return null;
}
public long getLastModified(HttpServletRequest request){
if(lastModified == 0L){
//第一次或邏輯有變化的時候,應該重新傳回内容最新修改的時間戳
lastModified = System.currentTimeMillis();
}
return lastModified;
}
}
HelloWorldLastModifiedCacheController隻需要實作LastModified接口的getLastModified方法,保證當内容發證變化時傳回最新的修改時間即可。
HandlerInterceptor的處理
Servlet API定義的servlet過濾器可以在servlet處理每個Web請求的前後分别對它進行前置和後置處理。
SpringMVC允許你通過處理攔截Web請求,進行前置處理和後置處理。處理攔截是在Spring的Web應用程式上下文中配置的,是以它們可以利用各種容器特性,并引用容器中聲明的任何bean.處理攔截是針對特殊的處理程式映射進行注冊的,是以它隻攔截通過這些處理程式映射的請求。每個攔截器都必須實作HandlerInterceptor接口,它包含三個需要你實作的回調方法:preHandler(),postHandler()和afterCompletion()。第一個和第二個方法分别是處理程式請求之前和之後被調用。第二個方法還允許傳回modelandview對象是以可以在它裡面操作模型屬性。最後一個方法是處理完成之後被調用的。
邏輯處理
對于邏輯處理其實是通過擴充卡中轉調用Handler并傳回視圖的,對應代碼:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
同樣,還是以引導示例為基礎進行邏輯處理分析,之前分析過,對于普通的web請求,Spring預設使用SimpleControllerHandlerAdapter類進行處理,方法如下:
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
但是回顧前面一節說的,我們的邏輯實作實在handlerRequestInternal函數中,而不是handleRequest函數,是以我們進一步分析:
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Delegate to WebContentGenerator for checking and preparing.
checkAndPrepare(request, response, this instanceof LastModified);
// Execute handleRequestInternal in synchronized block if required.
//如果需要session内的同步執行
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
//調用使用者的邏輯
return handleRequestInternal(request, response);
}
}
}
//調用使用者邏輯
return handleRequestInternal(request, response);
}
異常視圖的處理
有時候系統運作過程中出現異常,我們并不希望就此中斷使用者的服務,而是至少告知客戶目前系統在處理邏輯的過程中出現了異常,甚至告知他們什麼原因導緻的。Spring中的異常處理機制會幫我們完成這個工作。其實,這裡Spring的主要邏輯就是将邏輯引導至HandlerExceptionResolver類的resolverException方法,而handlerExcpetionResolver的使用,我們以前介紹過。
proteced ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
exMv.setViewName(getDefaultViewName(request));
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
根據視圖跳轉頁面
無論是一個系統還是一個站點,最重要的工作都是與使用者進行互動,使用者作業系統後無論下發的指令成功與否都需要給使用者一個回報,以便于使用者進行下一步的譜判斷。是以在邏輯處理 的最後一定會涉及一個頁面跳轉的問題。
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 {
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;
}
}
1.解析視圖名稱
在上一節中我們說過DispatcherServlet會根據ModelAndView選擇合适的視圖來進行渲染,而這以功能就是在resolveViewName函數中來完成的。
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;
}
我們以預設的org.Springframework.web.servlet.view.InternalResourceViewResolver為例來分析ViewResolver邏輯的解析過程,其中resolveViewName函數的實作是在其父類AbstractCachingViewResolver中完成的。
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) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
在父類UrlBasedViewResolver中重寫了createView函數。
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
//如果目前解析器不支援目前解析器如viewName為空等情況
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
//處理字首為redirect:xx的情況
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
//添加字首及字尾
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
//這隻contentType
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}
通讀以上代碼,我們發現對于InternalResourceViewResolver所提供的解析功能主要考慮到了幾個方面的處理。
- 基于效率的考慮,提供了緩存的支援
- 提供了對redirect:xx和forward:xx字首的支援
- 添加了字首及字尾,并向View中加入了必須的屬性設定
2.頁面跳轉
當通過viewName解析到對應的View後,就可以進一步地處理跳轉邏輯了。
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, request, response);
}
對于ModelView的使用,可以将一些屬性直接放入到其中,然後再頁面上直接通過JSTL文法或者原始的request擷取。這個一個很友善也很神奇的功能,但是實作卻不複雜,為非就是把我們将要用到的屬性放入到request中,以便在其他地方可以直接調用,而解析這個屬性的工作就是在createMergedOutputModel函數中完成的。
protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
}
return mergedModel;
}
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
// Expose the model object as request attributes.
//将model中的資料以屬性的方式設定到request中
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
//擷取關于目标資源的requestDispatcher(通常是JSP頁面)
RequestDispatcher rd = getRequestDispatcher(requestToExpose, 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(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
exposeForwardRequestAttributes(requestToExpose);
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response);
}
}