一、异常拦截器是什么?
异常拦截器的作用是提供一个机会,可以设置在action执行过程中发生异常的时候映射到一个结果字符串而不是直接中断。
将异常整合到业务逻辑中,比如在分层系统的调用中可以从底层抛出一个异常,高层捕捉到这个异常就知道发生了什么事情啦。
二、如何使用?
1.两种异常映射类型:
1.1.global
global的异常映射对整个package下的action都有效:
<struts> <package name="default" namespace="/" extends="struts-default"> <!-- 全局异常配置 -->
<global-exception-mappings>
<exception-mapping result="login" exception="struts_practice_004.UsernameIsNullException" />
</global-exception-mappings> <action name="loginAction" class="struts_practice_004.LoginAction"> <result name="login">/login.jsp</result>
<result name="success">/success.jsp</result>
</action> </package>
</struts>
1.2.action
action级别的异常映射只对单个的action有效:
<struts> <package name="default" namespace="/" extends="struts-default"> <action name="loginAction" class="struts_practice_004.LoginAction"> <!-- action级别的异常映射 -->
<exception-mapping exception="struts_practice_004.UsernameIsNullException" result="login" /> <result name="login">/login.jsp</result>
<result name="success">/success.jsp</result>
</action> </package>
</struts>
2.配置异常映射的可选参数
logEnabled 日志是否开启,默认关闭
logCategory 日志种类
logLevel 日志级别
3.使用异常映射的例子:
User实体:
/**
* 用户实体
* @author CC11001100
*
*/
public class User { private String username;
private String passwd; public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPasswd() {
return passwd;
} public void setPasswd(String passwd) {
this.passwd = passwd;
} }
LoginAction:
/**
* 登录Action
*
* @author CC11001100
*
*/
public class LoginAction extends ActionSupport { private User user;
private LoginService loginService; public LoginAction() {
loginService=new LoginService();
} @Override
public String execute() throws Exception { //尝试登录
User u1=loginService.login(user);
//如果为空,说明登录失败.
if(u1==null){
return LOGIN;
} return SUCCESS;
} public User getUser() {
return user;
} public void setUser(User user) {
this.user = user;
} }
LoginService:
public class LoginService { /**
* 登录是否成功
* @param user
* @return
*/
public User login(User user){
if(true) throw new UsernameIsNullException();
return user;
} }
struts.xml
<struts> <package name="default" namespace="/" extends="struts-default"> <action name="loginAction" class="struts_practice_004.LoginAction">
<exception-mapping exception="struts_practice_004.UsernameIsNullException" result="login" />
<result name="login">/login.jsp</result>
<result name="success">/success.jsp</result>
</action> </package>
</struts>
前端页面:
<form action="loginAction" method="post">
用户名:<input type="text" name="user.username" /><br/>
密 码:<input type="password" name="user.passwd" /><br/>
<input type="submit" value="登录" /><s:property value="exception"/>
</form> <s:debug />
结果:
提示不太友好,感觉例子设计得很牵强....凑活着吧,我们都知道Action会被压入值栈的栈顶,但是这里有一个<s:property value="exception"/> 这个是怎么访问得到的呢?请看下面的源代码分析。
三、工作原理?
异常拦截器处在默认拦截器栈的第一层,它的实现类是ExceptionMappingInterceptor,源代码如下:
/**
*
* 异常映射拦截器:
*
* 1.捕捉异常
* 2.将异常映射到结果字符串
*
*/
public class ExceptionMappingInterceptor extends AbstractInterceptor { protected static final Logger LOG = LoggerFactory.getLogger(ExceptionMappingInterceptor.class); protected Logger categoryLogger;
//是否将异常信息打印到日志
protected boolean logEnabled = false;
//logger的种类
protected String logCategory;
//日志的级别,默认是debug,从哪儿知道的?耐心往下看...
protected String logLevel; /*------------------------- 在这下面是getter和setter ------------------------------*/
public boolean isLogEnabled() {
return logEnabled;
} public void setLogEnabled(boolean logEnabled) {
this.logEnabled = logEnabled;
} public String getLogCategory() {
return logCategory;
} public void setLogCategory(String logCatgory) {
this.logCategory = logCatgory;
} public String getLogLevel() {
return logLevel;
} public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
/*----------------------------- getter和setter结束 ----------------------------------*/ /**
* 拦截器方法
*/
@Override
public String intercept(ActionInvocation invocation) throws Exception { String result; try {
//正常执行,继续往下递归调用,如果执行过程中没有抛出异常就相当于没有异常拦截器
//意思就是异常拦截器只有在抛出异常的时候才发挥作用,如果没发生异常就感觉不到它的存在
result = invocation.invoke();
} catch (Exception e) {
//如果在执行的过程catch到了异常,异常拦截器才会发挥作用 //如果开启日志记录的话,默认是false,上面已经写死了
if (isLogEnabled()) {
//记录一下
handleLogging(e);
} //获得异常映射信息,这个信息是在struts.xml中的<exception-mapping>中配置的
List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings(); //找一下当前抛出的异常有没有配置对应的结果字符串
String mappedResult = this.findResultFromExceptions(exceptionMappings, e); if (mappedResult != null) {
//如果找到了异常对应的结果字符串,那么保存结果
result = mappedResult;
//同时将这个异常包裹起来放到ValueStack的栈顶
publishException(invocation, new ExceptionHolder(e));
} else {
//没有没有配置结果字符串,我也不知道该怎么处理了,直接往上抛吧
throw e;
}
} //执行正常或者发生异常但配置了异常映射结果字符串才会执行到这里
return result;
} /**
* Handles the logging of the exception.
* 打印日志
* @param e the exception to log.
*/
protected void handleLogging(Exception e) {
//决定日志的打印方式
if (logCategory != null) {
//使用categoryLogger
if (categoryLogger == null) {
// init category logger
categoryLogger = LoggerFactory.getLogger(logCategory);
}
doLog(categoryLogger, e);
} else {
//或是使用默认的这个
doLog(LOG, e);
}
} /**
* Performs the actual logging.
*
* 打印日志的具体实现
*
* @param logger the provided logger to use.
* @param e the exception to log.
*/
protected void doLog(Logger logger, Exception e) {
//如果没有指定logLevel的话,默认就是debug级别了
if (logLevel == null) {
logger.debug(e.getMessage(), e);
return;
} //根据指定的日志级别打印
if ("trace".equalsIgnoreCase(logLevel)) {
logger.trace(e.getMessage(), e);
} else if ("debug".equalsIgnoreCase(logLevel)) {
logger.debug(e.getMessage(), e);
} else if ("info".equalsIgnoreCase(logLevel)) {
logger.info(e.getMessage(), e);
} else if ("warn".equalsIgnoreCase(logLevel)) {
logger.warn(e.getMessage(), e);
} else if ("error".equalsIgnoreCase(logLevel)) {
logger.error(e.getMessage(), e);
} else if ("fatal".equalsIgnoreCase(logLevel)) {
logger.fatal(e.getMessage(), e);
} else {
//都不匹配啊,说明指定的日志级别不合法(这种情况八成是拼写错误,血泪经验....),抛出异常警告一下搞开发的这傻小子~
throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
}
} /**
* 看看映射列表中有没有对应的映射
*/
protected String findResultFromExceptions(List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
String result = null; // Check for specific exception mappings
//看看是否配置了异常映射,即<exception-mapping exception="" result="" />
if (exceptionMappings != null) {
//因为后面要比惨..啊是比小,所以就预先给一个最大值,比较简单的技巧
int deepest = Integer.MAX_VALUE;
//遍历映射列表项
for (Object exceptionMapping : exceptionMappings) {
//看不大懂,为什么放着泛型不用呢,直接取出来不就是ExceptionMappingConfig类型的么? - -
ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
//看看当前的异常类跟配置的<exception-mapping exception="就是这一个" />能不能匹配的上,是不是它本身或者其子类
int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
if (depth >= 0 && depth < deepest) {
//说明找到了,当前抛出的异常类是配置的类本身或者是其子类
//按照一般想法找到了就直接return就可以了呗,我刚开始也是这样子想的,
//但是呢仔细看一看,结合上面的depth < deepest和下面的deepest = depth;
//所以呢,这里只是保存了一下仍然继续的意思就是要从这些映射列表中找到一个跟当前类最接近的
//这里的接近是从t(此时的t是超类引用子类对象,它的运行时类型并不是Throwable)一直到Throwable
//的这段距离,从[t,Throwable]这个区间上找一个最靠左的,感觉这样就能解释清楚了,
//这样做是为了最细程度的映射
deepest = depth;
result = exceptionMappingConfig.getResult();
}
}
} return result;
} /**
* Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*
* 找一下这个映射类到抛出的异常类之间有几个超类,可能有三种情况:
* 1. 没找到:-1表示exceptionMapping不是t的超类(这样貌似也没办法映射到了,映射的话包括本类和子类)
* 2. 本身:如果0的话表示exceptionMapping和t就是同一个类
* 3. 子类:其它字符串表示中间的超类个数,表示t是exceptionMapping的一个子类,但我们要的是中间的继承次数
*
* @param exceptionMapping the mapping classname
* @param t the cause
* @return the depth, if not found -1 is returned.
*/
public int getDepth(String exceptionMapping, Throwable t) {
return getDepth(exceptionMapping, t.getClass(), 0);
} /**
* 找exceptionMapping到exceptionClass的“距离”
*/
private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
//看看这个映射类是不是当前异常类的超类
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
//找到了
return depth;
}
// If we've gone as far as we can go and haven't found it...
//已经找到了顶层(Throwable),但仍然未找到,说明不可能找到了,返回不匹配
if (exceptionClass.equals(Throwable.class)) {
return -1;
} //递归顺着继承链往上找
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
} /**
* Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
* Subclasses may override this to customize publishing.
*
* 将这个ExceptionHolder压入ValueStack的栈顶
*
* @param invocation The invocation to publish Exception for.
* @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
*/
protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
//压入值栈的栈顶,以便OGNL表达式访问
invocation.getStack().push(exceptionHolder);
}
}
放入ValueStack栈顶的wrapper类的源代码分析:
/**
*
* A simple wrapper around an exception, providing an easy way to print out the stack trace of the exception as well as
* a way to get a handle on the exception itself.
*
* 将异常wrapper一下,然后提供两个方法以供简便的获得这个异常和异常追踪栈。
*
* getException()
* getExceptionStack()
*
* 这两个getter方法是用来给OGNL访问exception信息使用的,
* 当发生异常的时候会将异常对象用这个类包裹起来放到ValueStack的栈顶,
* 所以直接通过exception就可以访问得到异常对象本身,
* 通过exceptionStack就可以访问得到这个异常栈信息。
*
*/
public class ExceptionHolder implements Serializable { //要包裹的异常本身
private Exception exception; /**
* Holds the given exception
* 实例化这个类必须传进来一个异常,它持有一个异常。
* @param exception the exception to hold.
*/
public ExceptionHolder(Exception exception) {
this.exception = exception;
} /**
* Gets the holded exception
* 返回持有的异常对象
* @return the holded exception
*/
public Exception getException() {
return this.exception;
} /**
* Gets the holded exception stacktrace using {@link Exception#printStackTrace()}.
* 以String的形式返回异常栈追踪信息
* @return stacktrace
*/
public String getExceptionStack() {
String exceptionStack = null; if (getException() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); try {
getException().printStackTrace(pw);
exceptionStack = sw.toString();
}
finally {
try {
sw.close();
pw.close();
} catch (IOException e) {
// ignore
}
}
} return exceptionStack;
} }
用来表示异常映射的类的源代码:
这个类使用了Builder模式:设计模式之构建者模式(Builder)
/**
* Configuration for exception mapping.
* 异常映射的配置信息
*/
public class ExceptionMappingConfig extends Located implements Serializable { //结果字符串
private String name;
//异常类的类名
private String exceptionClassName;
//映射到的结果字符串
private String result;
//传递的参数
private Map<String,String> params; protected ExceptionMappingConfig(String name, String exceptionClassName, String result) {
this.name = name;
this.exceptionClassName = exceptionClassName;
this.result = result;
this.params = new LinkedHashMap<String,String>();
} protected ExceptionMappingConfig(ExceptionMappingConfig target) {
this.name = target.name;
this.exceptionClassName = target.exceptionClassName;
this.result = target.result;
this.params = new LinkedHashMap<String,String>(target.params);
} public String getName() {
return name;
} public String getExceptionClassName() {
return exceptionClassName;
} public String getResult() {
return result;
} public Map<String,String> getParams() {
return params;
} @Override
public boolean equals(Object o) {
if (this == o) {
return true;
} if (!(o instanceof ExceptionMappingConfig)) {
return false;
} final ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) o; if ((name != null) ? (!name.equals(exceptionMappingConfig.name)) : (exceptionMappingConfig.name != null)) {
return false;
} if ((exceptionClassName != null) ? (!exceptionClassName.equals(exceptionMappingConfig.exceptionClassName)) : (exceptionMappingConfig.exceptionClassName != null))
{
return false;
} if ((result != null) ? (!result.equals(exceptionMappingConfig.result)) : (exceptionMappingConfig.result != null))
{
return false;
} if ((params != null) ? (!params.equals(exceptionMappingConfig.params)) : (exceptionMappingConfig.params != null))
{
return false;
} return true;
} @Override
public int hashCode() {
int hashCode;
hashCode = ((name != null) ? name.hashCode() : 0);
hashCode = (29 * hashCode) + ((exceptionClassName != null) ? exceptionClassName.hashCode() : 0);
hashCode = (29 * hashCode) + ((result != null) ? result.hashCode() : 0);
hashCode = (29 * hashCode) + ((params != null) ? params.hashCode() : 0); return hashCode;
} /**
* The builder for this object. An instance of this object is the only way to construct a new instance. The
* purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining.
* After setting any values you need, call the {@link #build()} method to create the object.
*/
public static class Builder{ private ExceptionMappingConfig target; public Builder(ExceptionMappingConfig toClone) {
target = new ExceptionMappingConfig(toClone);
} public Builder(String name, String exceptionClassName, String result) {
target = new ExceptionMappingConfig(name, exceptionClassName, result);
} public Builder name(String name) {
target.name = name;
return this;
} public Builder exceptionClassName(String name) {
target.exceptionClassName = name;
return this;
} public Builder result(String result) {
target.result = result;
return this;
} public Builder addParam(String name, String value) {
target.params.put(name, value);
return this;
} public Builder addParams(Map<String,String> params) {
target.params.putAll(params);
return this;
} public Builder location(Location loc) {
target.location = loc;
return this;
} public ExceptionMappingConfig build() {
target.params = Collections.unmodifiableMap(target.params);
ExceptionMappingConfig result = target;
target = new ExceptionMappingConfig(target);
return result;
}
} }
这是它的内存里的表示: