随笔分类
Aop异常的玩法
在 ThrowsAdvice上看到这么一段注释,这段注释给出了异常的一些玩法:
* <p>Some examples of valid methods would be:
*
* <pre class="code">public void afterThrowing(Exception ex)</pre>
* <pre class="code">public void afterThrowing(RemoteException)</pre>
* <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre>
* <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre>
*
此时,联系到其标准实现 ThrowsAdviceInterceptor上,其同样存在着这段注释
经过源码分析得知,这里实际上就是来为我们提供了一些方法签名供我们参考
观看其构造:
/**
* Create a new ThrowsAdviceInterceptor for the given ThrowsAdvice.
* @param throwsAdvice the advice object that defines the exception handler methods
* (usually a {@link org.springframework.aop.ThrowsAdvice} implementation) 这里也说明了, 参数类型通常是 ThrowsAdvice的实现
*/
// 因此, 我们可以通过在这块做些异常的处理 - 方法名为
public ThrowsAdviceInterceptor(Object throwsAdvice) {
Assert.notNull(throwsAdvice, "Advice must not be null");
this.throwsAdvice = throwsAdvice;
// 获取类中的所有方法
Method[] methods = throwsAdvice.getClass().getMethods();
for (Method method : methods) {
// true - 有对应异常发生的处理方法
if (method.getName().equals(AFTER_THROWING) &&
// 方法签名:对应参数可以是 1个或者 4个
(method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
// 获取此 method处理对应的异常 Class
Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];
// 即, method 参数必须是 throw及其子类
if (Throwable.class.isAssignableFrom(throwableParam)) {
// An exception handler to register...
// 往 Map中进行存储, 这样的话, 当异常发生时, 就可以直接去做些相应的处理
this.exceptionHandlerMap.put(throwableParam, method);
if (logger.isDebugEnabled()) {
logger.debug("Found exception handler method on throws advice: " + method);
}
}
}
}
if (this.exceptionHandlerMap.isEmpty()) {
throw new IllegalArgumentException(
"At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
}
}
可以,我们可以在构造 Interceptor时传递过来一个 Object,在构造中对 Object进行解析,对应的便是通过反射获取该 Object中参数数目为 1或 4个并且方法名为 afterThrowing的 method,若是该 method中对应位置上存在 Throw子类的话,那么该 method将视为异常发生时的一个处理方法,由上也可以看出,将其存储到了一个 map中去
对应:
private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>();
由拦截器特性,我们关注于其 invoke()
try...catch式玩法
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
// 从这里也可以看出, 标准实现实际上只会去处理一个 Method, 这是区别于 AspectJ实现的
Method handlerMethod = getExceptionHandler(ex);
if (handlerMethod != null) {
// 反射执行对应异常发生时的处理方法
invokeHandlerMethod(mi, ex, handlerMethod);
}
throw ex;
}
}
发生异常时,捕获异常,调用了 getExceptionHandler()
/**
* Determine the exception handle method for the given exception.
* @param exception the exception thrown
* @return a handler for the given exception type, or {@code null} if none found
*/
@Nullable
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 = this.exceptionHandlerMap.get(exceptionClass);
// 递归层次性地去进行查找
while (handler == null && exceptionClass != Throwable.class) {
exceptionClass = exceptionClass.getSuperclass();
handler = this.exceptionHandlerMap.get(exceptionClass);
}
if (handler != null && logger.isTraceEnabled()) {
logger.trace("Found handler for exception of type [" + exceptionClass.getName() + "]: " + handler);
}
return handler;
}
这块实际上去 map中去查找有没有对应 Exeception发生时的处理方法,这里需要注意的是递归层次性往上查找的一个过程,最终返回的便是一个能够处理该 ex的 Method,也就是说可能存在多个 method能够处理该 ex的情况,返回的会是与该 ex处理层次上最为相近的 method
即,对应的便是,最多也只会有一个 method能够被执行,对应的便是:invokeHandlerMethod()
// 根据具体的类型去执行对应的异常处理方法 - afterThrowing()
private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
Object[] handlerArgs;
if (method.getParameterCount() == 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();
}
}
这块便是反射调用执行对应的方法
来个小 demo,验证我们的想法:
public interface EchoService {
String echo(String msg);
}
public class EchoServiceImpl implements EchoService {
public String echo(String msg) {
// return msg;
// throw new RuntimeException();
throw new NullPointerException();
}
}
// 这块只是遵循注释罢了, 其实可以不用去实现 Advice接口, 由源码分析可知也并没有去做相关的验证
public class MyExceptionHandler implements Advice {
public void afterThrowing(NullPointerException ex) {
System.out.println("NullPointerException happened");
}
public void afterThrowing(RuntimeException ex) {
System.out.println("RuntimeException happened");
}
}
测试:
public class Main {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new EchoServiceImpl());
proxyFactory.addAdvice(new ThrowsAdviceInterceptor(new MyExceptionHandler()));
EchoService proxy = (EchoService) proxyFactory.getProxy();
System.out.println(proxy.echo("liangye"));
}
}
对应执行结果:
NullPointerException happened
Exception in thread "main" java.lang.NullPointerException
at service.EchoServiceImpl.echo(EchoServiceImpl.java:8)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:112)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy0.echo(Unknown Source)
at inter.Main.main(Main.java:14)
可以看到,异常发生时,最多也只会有一个 Method去进行处理,这也就验证了我们前面的逻辑是正确的
而这块实际上和 AspectJ实现是有所差别的
经过前面源码分析,AspectJ注解驱动式 Aop,最终会通过反射的方式在 bean初始化后将对应的 Aspect内的通知注解解析成 Interceptor,然后形成一个 chain,得以执行
也就是当目标方法发生异常后,若是存在多个 Advice可以进行处理,那么这些 Advice对应的 method会来逐一进行执行,这块和标准实现有所区别,其底层实现不依托于标准实现,并且也没有方法命名的要求
当然,如果是要去进行指定异常的处理的话,在通知注解中还是需要加上对应的参数的
先来看下源码:
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable ex) {
// 判断当前 Advice对应的通知方法是否需要去进行执行
if (shouldInvokeOnThrowing(ex)) {
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
验证是否需要去执行
/**
* In AspectJ semantics, after throwing advice that specifies a throwing clause
* is only invoked if the thrown exception is a subtype of the given throwing type.
*/
// 实际上就是判断声明的类型和 catch是不是同一个类型
private boolean shouldInvokeOnThrowing(Throwable ex) {
return getDiscoveredThrowingType().isAssignableFrom(ex.getClass());
}
需要执行,调用 invokeAdviceMethod()
AbstractAspectJAdvice.invokeAdviceMethod()
// As above, but in this case we are given the join point.
protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
@Nullable Object returnValue, @Nullable Throwable t) throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
}
// 由这里也可以看出, Advice是查找实际上是由 AspectJ来做的, Spring仅仅是做了整合而已
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterCount() == 0) {
actualArgs = null;
}
try {
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
// 反射调用对应通知方法
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("Mismatch on arguments to advice method [" +
this.aspectJAdviceMethod + "]; pointcut expression [" +
this.pointcut.getPointcutExpression() + "]", ex);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
最终,异常处理方法得以执行,由于 invoke可能会被调用多次,这也是区别于标准实现的一个地方
这块就自行验证吧,类述即可