Alfresco的错误机制是值得借鉴和学习的!
Alfresco的错误主要分为如下几点:
1.面向表现层
Alfresco 面向表现层的处理,只要是向开发人员和用户呈现错误信息和引起错误的原因!实际上大多数错误机制的处理也就包含这两个层面的意思。通过如下代码我们可以看到Alfresco是如何配置错误处理页面的:
web.xml:
< exception-type > java.lang.Exception </ exception-type >
< location > /jsp/error.jsp </ location >
</ error-page >
在Java中,java.lang.Exception是所有错误或异常的基类,其又继承了java.lang.Throwable这个超类,而所谓超类是指:只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw
语句抛出。类似地,只有此类或其子类之一才可以是 catch
子句中的参数类型。
由上配置可以看出,当页面访问出错时都会转向到error.jsp,这样就可以给用户一个统一的异常处理界面,看看error.jsp:
* Copyright (C) 2005-2007 Alfresco Software Limited.
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
-- %>
<% ... @ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<% ... @ taglib uri="/WEB-INF/repo.tld" prefix="r" %>
<% ... @ page buffer="32kb" contentType="text/html;charset=UTF-8" %>
<% ... @ page isELIgnored="false" %>
<% ... @ page import="org.alfresco.web.app.Application" %>
< r:page titleId ="title_error" >
< table cellspacing ="0" cellpadding ="2" width ="100%" >
< tr >
<% ... -- Top level toolbar and company logo area -- %>
< td width =100% >
< table cellspacing ="0" cellpadding ="0" width ="100%" >
< tr >
< td >< a href ="http://www.alfresco.org" target ="new" >< img src ="<%=request.getContextPath()%>/images/logo/AlfrescoLogo32.png" width =32 height =30 alt ="Alfresco" title ="Alfresco" border =0 style ="padding-right:4px" ></ a ></ td >
< td >< img src ="<%=request.getContextPath()%>/images/parts/titlebar_begin.gif" width ="10" height ="30" ></ td >
< td width =100% style ="background-image: url(<%=request.getContextPath()%>/images/parts/titlebar_bg.gif)" >
< span class ="topToolbarTitle" > <% = Application.getMessage(session, " system_error " ) %> </ span >
</ td >
< td >< img src ="<%=request.getContextPath()%>/images/parts/titlebar_end.gif" width ="8" height ="30" ></ td >
</ tr >
</ table >
</ td >
</ tr >
< tr >
< td >
< r:systemError styleClass ="errorMessage" detailsStyleClass ="mainSubTextSmall" showDetails ="false" />
</ td >
</ tr >
</ table >
</ r:page >
此代码中主要内容为:
这是由标签库封装了的表现层技术,它主要完成了如下信息处理:
- 错误信息是什么
- 引起错误信息的原因是什么
核心代码:
... {
String errorMessage = "No error currently stored";
String errorDetails = "No details";
// get the error details from the bean, this may be in a portlet
// session or a normal servlet session.
ErrorBean errorBean = null;
RenderRequest renderReq = (RenderRequest)pageContext.getRequest().
getAttribute("javax.portlet.request");
if (renderReq != null)
...{
PortletSession session = renderReq.getPortletSession();
errorBean = (ErrorBean)session.getAttribute(ErrorBean.ERROR_BEAN_NAME);
}
else
...{
errorBean = (ErrorBean)pageContext.getSession().
getAttribute(ErrorBean.ERROR_BEAN_NAME);
}
if (errorBean != null)
...{
errorMessage = errorBean.getLastErrorMessage();
errorDetails = errorBean.getStackTrace();
}
else
...{
// if we reach here the error was caught by the declaration in web.xml so
// pull all the information from the request and create the error bean
Throwable error = (Throwable)pageContext.getRequest().getAttribute("javax.servlet.error.exception");
String uri = (String)pageContext.getRequest().getAttribute("javax.servlet.error.request_uri");
// create and store the ErrorBean
errorBean = new ErrorBean();
pageContext.getSession().setAttribute(ErrorBean.ERROR_BEAN_NAME, errorBean);
errorBean.setLastError(error);
errorBean.setReturnPage(uri);
errorMessage = errorBean.getLastErrorMessage();
errorDetails = errorBean.getStackTrace();
}
try
...{
Writer out = pageContext.getOut();
ResourceBundle bundle = Application.getBundle(pageContext.getSession());
out.write("<div");
if (this.styleClass != null)
...{
out.write(" class='");
out.write(this.styleClass);
out.write("'");
}
out.write(">");
out.write(errorMessage);
out.write("</div>");
// work out initial state
boolean hidden = !this.showDetails;
String display = "inline";
String toggleTitle = "Hide";
if (hidden)
...{
display = "none";
toggleTitle = "Show";
}
// output the script to handle toggling of details
out.write("<script language='JavaScript'> ");
out.write("var hidden = ");
out.write(Boolean.toString(hidden));
out.write("; ");
out.write("function toggleDetails() { ");
out.write("if (hidden) { ");
out.write("document.getElementById('detailsTitle').innerHTML = '");
out.write(bundle.getString(MSG_HIDE_DETAILS));
out.write("<br/><br/>'; ");
out.write("document.getElementById('details').style.display = 'inline'; ");
out.write("hidden = false; ");
out.write("} else { ");
out.write("document.getElementById('detailsTitle').innerHTML = '");
out.write(bundle.getString(MSG_SHOW_DETAILS));
out.write("'; ");
out.write("document.getElementById('details').style.display = 'none'; ");
out.write("hidden = true; ");
out.write("} } </script> ");
// output the initial toggle state
out.write("<br/>");
out.write("<a id='detailsTitle' href='javascript:toggleDetails();'>");
out.write(toggleTitle);
out.write(" Details</a>");
out.write("<div style='padding-top:5px;display:");
out.write(display);
out.write("' id='details'");
if (this.detailsStyleClass != null)
...{
out.write(" class='");
out.write(this.detailsStyleClass);
out.write("'");
}
out.write(">");
out.write(errorDetails);
out.write("</div>");
// output a link to return to the application
out.write(" <div style='padding-top:16px;'><a href='");
if (Application.inPortalServer())
...{
RenderResponse renderResp = (RenderResponse)pageContext.getRequest().getAttribute(
"javax.portlet.response");
if (renderResp == null)
...{
throw new IllegalStateException("RenderResponse object is null");
}
PortletURL url = renderResp.createRenderURL();
// NOTE: we don't have to specify the page for the portlet, just the VIEW_ID parameter
// being present will cause the current JSF view to be re-displayed
url.setParameter("org.apache.myfaces.portlet.MyFacesGenericPortlet.VIEW_ID", "current-view");
out.write(url.toString());
}
else
...{
String returnPage = null;
if (errorBean != null)
...{
returnPage = errorBean.getReturnPage();
}
if (returnPage == null)
...{
out.write("javascript:history.back();");
}
else
...{
out.write(returnPage);
}
}
out.write("'>");
out.write(bundle.getString(MSG_RETURN_TO_APP));
out.write("</a></div>");
// use External Access Servlet to generate a URL to relogin again
// this can be used by the user if the app has got into a total mess
if (Application.inPortalServer() == false)
...{
out.write(" <div style='padding-top:16px;'><a href='");
out.write(((HttpServletRequest)pageContext.getRequest()).getContextPath());
out.write(ExternalAccessServlet.generateExternalURL("logout", null));
out.write("'>");
out.write(bundle.getString(MSG_LOGOUT));
out.write("</a></div>");
}
}
catch (IOException ioe)
...{
throw new JspException(ioe);
}
finally
...{
// clear out the error bean otherwise the next error could be hidden
pageContext.getSession().removeAttribute(ErrorBean.ERROR_BEAN_NAME);
}
return SKIP_BODY;
}
上面两个信息分别由属性errorMessage和errorDetails的显示的。他们又是从哪里取得的呢?答案是ErrorBean,此类封装用来如何取得错误或异常的信息,如下代码:
* @return Returns the last error to occur in string form
*/
public String getLastErrorMessage()
... {
String message = "No error currently stored";
if (this.lastError != null)
...{
StringBuilder builder = new StringBuilder(this.lastError.toString());;
Throwable cause = this.lastError.getCause();
// build up stack trace of all causes
while (cause != null)
...{
builder.append(" caused by: ");
builder.append(cause.toString());
if (cause instanceof ServletException &&
((ServletException)cause).getRootCause() != null)
...{
cause = ((ServletException)cause).getRootCause();
}
else
...{
cause = cause.getCause();
}
}
message = builder.toString();
// format the message for HTML display
message = message.replaceAll("<", "<");
message = message.replaceAll(">", ">");
message = message.replaceAll(" ", "<br/>");
}
return message;
}
/** */ /**
* @return Returns the stack trace for the last error
*/
public String getStackTrace()
... {
String trace = "No stack trace available";
if (this.lastError != null)
...{
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
this.lastError.printStackTrace(writer);
// format the message for HTML display
trace = stringWriter.toString();
trace = trace.replaceAll("<", "<");
trace = trace.replaceAll(">", ">");
trace = trace.replaceAll(" ", "<br/>");
}
return trace;
}
其抛出了最上层的错误和错误堆栈的全部信息。
2.面向业务逻辑层
在业务逻辑处理中,Alfresco使用了大量的servlet来处理,如何铺获那些异常呢,看上面的问题,即如何给errorMessage和errorDetails以对应的信息呢?Alfresco 处理servlet抛出的错误或异常直接是引用了java.lang.Throwable这个超类,如下:
throws ServletException, IOException
... {
String uploadId = null;
String returnPage = null;
final RequestContext requestContext = new ServletRequestContext(request);
boolean isMultipart = ServletFileUpload.isMultipartContent(requestContext);
try
...{
//.......
}
catch (Throwable error)
...{
Application.handleServletError(getServletContext(), (HttpServletRequest)request,
(HttpServletResponse)response, error, logger, returnPage);
}
看到了吧,对于servlet处理错误异常是利用了Application.handleServletError这个方法来实现的,这个方法里面做什么呢
HttpServletResponse response, Throwable error, Log logger, String returnPage)
throws IOException, ServletException
... {
// get the error bean from the session and set the error that occurred.
HttpSession session = request.getSession();
ErrorBean errorBean = (ErrorBean)session.getAttribute(ErrorBean.ERROR_BEAN_NAME);
if (errorBean == null)
...{
errorBean = new ErrorBean();
session.setAttribute(ErrorBean.ERROR_BEAN_NAME, errorBean);
}
errorBean.setLastError(error);
errorBean.setReturnPage(returnPage);
// try and find the configured error page
boolean errorShown = false;
String errorPage = getErrorPage(servletContext);
if (errorPage != null)
...{
if (logger.isDebugEnabled())
logger.debug("An error has occurred, redirecting to error page: " + errorPage);
if (response.isCommitted() == false)
...{
errorShown = true;
response.sendRedirect(request.getContextPath() + errorPage);
}
else
...{
if (logger.isDebugEnabled())
logger.debug("Response is already committed, re-throwing error");
}
}
else
...{
if (logger.isDebugEnabled())
logger.debug("No error page defined, re-throwing error");
}
// if we could not show the error page for whatever reason, re-throw the error
if (!errorShown)
...{
if (error instanceof IOException)
...{
throw (IOException)error;
}
else if (error instanceof ServletException)
...{
throw (ServletException)error;
}
else
...{
throw new ServletException(error);
}
}
}
到这里就应该明白了吧!它把错误信息赋值给lastError这个Throwable对象,然后通过getLastErrorMessage()和getStackTrace()类取得错误或异常了!
3.错误信息属性文件
由于本地化实现要求,Alfreso封装了部分错误信息到属性文件,这些属性分散在配置文件夹${Alfresco}/WEB-INF/classes/alfreso/messages下面!