Action执行时是被DefaultActionInvocation对象的invoke方法调用
而在Action执行之前又有一批拦截器
拦截器的调用也是被DefalutActionInvocation对象invoke方法调用
最初调用到DefalutActionInvocation对象invoke方法是由ActionProxy的execute方法调用。
来看一下action执行的部分代码。
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
try{
if (!methodCalled) {
methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);
}
return saveResult(actionConfig, methodResult);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
} catch (InvocationTargetException e) {
// We try to return the source exception.
Throwable t = e.getTargetException();
}
这里就是捕获了没有这个方法和调用目标异常,没有捕获其他异常,遇到其他异常直接往上抛。
那么我们看看那个方法调用到这个方法
public String invoke() throws Exception {
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
// this is needed because the result will be executed, then control will return to the Interceptor, which will
// return above and flow through again
}
这个invoke方法继续往上抛异常,看看谁调用invoke方法呢?proxy.execute()方法
public String execute() throws Exception {
ActionContext nestedContext = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
String retCode = null;
String profileKey = "execute: ";
try {
UtilTimerStack.push(profileKey);
retCode = invocation.invoke();
} finally {
if (cleanupContext) {
ActionContext.setContext(nestedContext);
}
UtilTimerStack.pop(profileKey);
}
return retCode;
}
这里继续往上抛,再看看在那个方法里调用了这个execute()方法呢?
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {
Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
boolean nullStack = stack == null;
if (nullStack) {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
stack = ctx.getValueStack();
}
}
if (stack != null) {
extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
}
String timerKey = "Handling request from Dispatcher";
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
// If there was a previous value stack then set it back onto the request
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
// WW-2874 Only log error if in devMode
if(devMode) {
String reqStr = request.getRequestURI();
if (request.getQueryString() != null) {
reqStr = reqStr + "?" + request.getQueryString();
}
LOG.error("Could not find action or result\n" + reqStr, e);
}
else {
if (LOG.isWarnEnabled()) {
LOG.warn("Could not find action or result", e);
}
}
sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} finally {
UtilTimerStack.pop(timerKey);
}
}
在这个dispatcher的serviceAction方法进行了捕获。并用sendError进行了处理
public void sendError(HttpServletRequest request, HttpServletResponse response,
ServletContext ctx, int code, Exception e) {
Boolean devModeOverride = FilterDispatcher.getDevModeOverride();
if (devModeOverride != null ? devModeOverride : devMode) {
response.setContentType("text/html");
try {
FreemarkerManager mgr = getContainer().getInstance(FreemarkerManager.class);
freemarker.template.Configuration config = mgr.getConfiguration(ctx);
Template template = config.getTemplate("/org/apache/struts2/dispatcher/error.ftl");
List<Throwable> chain = new ArrayList<Throwable>();
Throwable cur = e;
chain.add(cur);
while ((cur = cur.getCause()) != null) {
chain.add(cur);
}
HashMap<String,Object> data = new HashMap<String,Object>();
data.put("exception", e);
data.put("unknown", Location.UNKNOWN);
data.put("chain", chain);
data.put("locator", new Locator());
template.process(data, response.getWriter());
response.getWriter().close();
} catch (Exception exp) {
try {
response.sendError(code, "Unable to show problem report: " + exp);
} catch (IOException ex) {
// we're already sending an error, not much else we can do if more stuff breaks
}
}
} else {
try {
// WW-1977: Only put errors in the request when code is a 500 error
if (code == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
// send a http error response to use the servlet defined error handler
// make the exception availible to the web.xml defined error page
request.setAttribute("javax.servlet.error.exception", e);
// for compatibility
request.setAttribute("javax.servlet.jsp.jspException", e);
}
// send the error response
response.sendError(code, e.getMessage());
} catch (IOException e1) {
// we're already sending an error, not much else we can do if more stuff breaks
}
}
}
这里获取错误信息模版,将信息放入到模版中进行输出到浏览器
那么我们如何修改错误信息呢?这里我们不必修改,我们要利用好struts2提供的拦截器,这里有一个exception拦截器
<interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
在默认拦截器栈的栈顶。
那么我们来看看exception拦截器的intercept方法
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String result;
try {
result = invocation.invoke();
} catch (Exception e) {
if (isLogEnabled()) {
handleLogging(e);
}
List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
if (mappingConfig != null && mappingConfig.getResult()!=null) {
Map parameterMap = mappingConfig.getParams();
// create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
invocation.getInvocationContext().setParameters(new HashMap<String, Object>(parameterMap));
result = mappingConfig.getResult();
publishException(invocation, new ExceptionHolder(e));
} else {
throw e;
}
}
return result;
}
执行invocation.invoke();时对异常信息进行了捕获捕获好异常信息就执行下面的代码,去找配置文件中的错误exception-error节点,获取result名称,然后执行publishException()方法
protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
invocation.getStack().push(exceptionHolder);
}
这里将错误信息放入栈顶,然后params过滤器就会错误信息,set到action的成员变量中。
解决方案 :
<package name="struts-global" namespace="/" extends="struts-default">
<global-results>
<result name="errHandler" type="chain">
<param name="actionName">errorProcessor</param>
</result>
</global-results>
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception"
result="errHandler" />
</global-exception-mappings>
<action name="errorProcessor" class="ErrorProcess">
<result>error.jsp</result>
</action>
</package>