对于Exception的处理,究竟是采用check exception还是unchecked exception,众说纷纭,业绩很多大牛都有不同的意见,本文的关注点不在这个话题上,而是着重于,当我们程序中,需要把各种check exception转化为自定义的和业务逻辑相关的Runtime exceptionn的时候,如何利用Spring AOP来简化这一转化过程。
在实际工作中,如果我们也想像Spring Jdbctemplate一样,把check exception转化为Runtime exception的话,笨一点的办法就是在每次抛出check exception的地方都将其捕获,然后重新包装成Runtime exception,不过这样的话,不但重复工作量大,而且还容易产生遗漏,可谓事倍功半。那还要其它的什么办法能做到这一点呢?仔细分析下我们的我们面临的问题:即要在每个抛出check exception的方法里把异常包装成runtime exception重新抛出。这里有两个关键词很重要:每个。。。方法。。。,我们要在每个XX方法执行的时候都给其添加一项新的功能,这不正是AOP的用武之地。
因为Spring 提供了强大的AOP功能,我们就不需要自己去重复造轮子实现类似的功能,我翻了下Spring AOP部分的API,很快就找到ThrowsAdviceInterceptor这个类,这个类正好提供了我所需要的功能:
public ThrowsAdviceInterceptor(Object throwsAdvice) {
Assert.notNull(throwsAdvice, "Advice must not be null");
this.throwsAdvice = throwsAdvice;
Method[] methods = throwsAdvice.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getName().equals(AFTER_THROWING) &&
//m.getReturnType() == null &&
(method.getParameterTypes().length == 1 || method.getParameterTypes().length == 4) &&
Throwable.class.isAssignableFrom(method.getParameterTypes()[method.getParameterTypes().length - 1])
) {
// Have an exception handler
this.exceptionHandlerMap.put(method.getParameterTypes()[method.getParameterTypes().length - 1], method);
if (logger.isDebugEnabled()) {
logger.debug("Found exception handler method: " + method);
}
}
}
if (this.exceptionHandlerMap.isEmpty()) {
throw new IllegalArgumentException(
"At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
}
}
public int getHandlerMethodCount() {
return this.exceptionHandlerMap.size();
}
/**
* Determine the exception handle method. Can return null if not found.
* @param exception the exception thrown
* @return a handler for the given exception type
*/
private Method getExceptionHandler(Throwable exception) {
Class exceptionClass = exception.getClass();
if (logger.isTraceEnabled()) {
logger.trace("Trying to find handler for exception of type [" + exceptionClass.getName() + "]");
}
Method handler = (Method) this.exceptionHandlerMap.get(exceptionClass);
while (handler == null && !exceptionClass.equals(Throwable.class)) {
exceptionClass = exceptionClass.getSuperclass();
handler = (Method) this.exceptionHandlerMap.get(exceptionClass);
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Found handler for exception of type [" + exceptionClass.getName() + "]: " + handler);
}
return handler;
}
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
Method handlerMethod = getExceptionHandler(ex);
if (handlerMethod != null) {
invokeHandlerMethod(mi, ex, handlerMethod);
}
throw ex;
}
}
private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
Object[] handlerArgs;
if (method.getParameterTypes().length == 1) {
handlerArgs = new Object[] { ex };
}
else {
handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
}
try {
method.invoke(this.throwsAdvice, handlerArgs);
}
catch (InvocationTargetException targetEx) {
throw targetEx.getTargetException();
}
}
这个类的源代码并不复杂,简而言之就是根据注入的throwsAdvice,来生产针对各种不同的exception的AOP处理方法。其中有一点需要注意的是,在这个throwsAdvice当中,各种exception的处理方法都要以afterThrowing开头。见下面的例子:
public class CDMExceptionHandler implements ThrowsAdvice {
private static final int stackTraceLevel = 4;
public void afterThrowingFileNotFound(Method m, Object[] args, Object target,
FileNotFoundException ex) throws Exception {
//处理异常并包装成业务逻辑对应的Runtime exception,重新抛出
}
public void afterThrowing(Method m, Object[] args, Object target,
HttpHostConnectException ex) throws Exception {
//处理异常并包装成业务逻辑对应的Runtime exception,重新抛出
}
}
如果把ExceptionHandler 的实例当做throwsAdvice注入进ThrowsAdviceInterceptor的话,那么ThrowsAdviceInterceptor里的exceptionHandlerMap就有两个entery,分别为:
Key=FileNotFoundException, value(method)=afterThrowingFileNotFound
key=HttpHostConnectionException,value(method)=afterThrowingHttpHost。
在Invoke,ThrowsAdviceInterceptor根据捕获的异常从exceptionHandlerMethod里面get到相应的处理方法,进行处理。
通过对ThrowsAdviceInterceptor原理的简单分析,我们可以发现,其正好满足我们的需要,我们唯一要做的事情就是根据具体的应用,编写好自己的exceptionHandler即throwsAdvice,并将其注入进ThrowsAdviceInterceptor,然后把这个ThrowsAdviceInterceptor配置到我们的应用程序当中即可。至于AOP的具体配置,google下Spring AOP,将会有无数的文章教你如何配置,下面给出个简单的配置示例:
<bean id ="throwInterceptor" class="org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor">
<constructor-arg ref="throwAdvice" />
</bean>
<bean id ="throwAdvice" class="exceptionHandler.utils.ExceptionHandler">
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>contentPutServiceImpl</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>throwInterceptor</value>
</list>
</property>
</bean>
BeanNameAutoProxyCreator来配置需要在应用程序的那些类中使用ThrowsAdviceInterceptor。