简单描述Sping的AOP就是动态代理加拦截器链实现面向切面编程。当然AOP肯定没有这么简单,但是如果能自己实现AOP然后再去看源码的话是不是会更轻松点呢。
众所周知,Sping中AOP之所以能起作用就是我们在目标类的基础上定义了很多通知,例如before(前置通知),after-returning(后置通知),after(最终通知),after-throwing(异常通知),around(后置通知)等,那么我们就可以先从这里入手定义自己的通知
public interface FilterChain { /** * 目标方法执行之前执行 * * @timestamp Feb 17, 2016 2:41:59 PM */ public void before(Object target, Matcher[] matchers); /** * 目标方法执行之后执行 * * @timestamp Feb 17, 2016 2:42:12 PM */ public void after(Object target, Matcher[] matchers); }
target是目标类,matchers是匹配规则,它是个Matcher数组。之后可以再定义它的子接口扩展一下通知的功能
public interface FilterChainSub extends FilterChain { /** * 处理catch中的异常 * * @timestamp Feb 17, 2016 5:57:09 PM * @param exception * @param target */ public void operateException(Exception exception, Object target); /** * 释放资源,相当于finally中的逻辑 * * @timestamp Feb 17, 2016 4:25:53 PM * @param target */ public void destroy(Object target); }
现在通知定义好了,下一步就要决定是什么机制调用了实现这些接口的实体。
ApplicationFilterChain.java由于这个源码比较多就不贴了,把链接放这了。下面来看一下这个类的主要字段
/** * 实现过滤器链 */ private List<FilterChain> filterChainList = new ArrayList<>; /** * 索引计数器 */ private int index = 0; /** * 目标方法 */ private Method method = null; /** * 目标对象 */ private Object target = null; /** * 参数 */ private Object args = null; /** * 匹配规则 */ private Matcher matchers;
这个其实就是一个过滤器链,在Spring中是拦截器链。拦截器与过滤器的最主要的区别就是拦截器是动态代理实现的,而过滤器是函数的回调实现的。所以总感觉这里Filter好一些。也许设计AOP的大牛们是从整体上来看的,他就是一个Interceptor。不管什么了能实现功能再说。filterChainList是之后要添加的通知集合。下面来看一下核心代码
public Object invoke(ApplicationFilterChain chain) { if (filterChainList.size < 0) return null; if (chain.size == chain.index) { Object obj = null; try { obj = method.invoke(target, args); } catch (Exception e) { throw new RuntimeException(e); } return obj; } // 获取过滤链中当前索引所指向的节点 FilterChain f = chain.get(chain.index); Object obj = null; f.before(target, matchers); // 过滤链中当前索引+1 this.index++; obj = invoke(this); f.after(target, matchers); return obj; }
外界要调用这个invoke方法就要传进来一个ApplicationFilterChain实例,如果传入的这个chain中通知集合元素的个数等于它的index的话就执行目标方法。否则就根据索引从链中获取通知然后执行前置通知,执行this.index++;重新调用invoke方法,再调用后置通知。这里为什么可以成this.index++不是chain吗?是因为我在外面调invoke的对象和传入的参数是一个对象,所以chain和this就是一个对象。使用哪个都可以,如果感觉别扭的话可以把this换成chain。再者就是invoke方法的调用会形成一个递归,知道index==size时就会跳出递归。现在知道了通知是怎么被调用的。下一步就可以讨论整体结构是怎么形成的了。
public class Interceptor implements InvocationHandler { /** * 目标对象 */ private Object target = null; /** * 过滤器链 */ private ApplicationFilterChain filterChain = null; private String regular; /** * * @param target * 目标类 * @param filterChain * 过滤器链 * @param regular * 匹配规则 */ public Interceptor(Object target, ApplicationFilterChain filterChain, String... regular) { super; this.target = target; this.filterChain = filterChain; this.regular = regular; } /** * 执行目标方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 设置目标方法,目标类,参数 filterChain.setMethod(method); filterChain.setTarget(target); filterChain.setArgs(args); filterChain.setRegular(regular); return filterChain.invoke(filterChain); } catch (Exception e) { filterChain.operateException(e); } finally { filterChain.destroy(target); } return null; } }
这里我定义了一个拦截器类然后让它实现了InvocationHandler接口,如果不知道InvocationHandler是什么作用的话可以先看一下动态代理,这里就不说了。这个拦截器类有一个构造函数接收三个参数分别是被代理的目标类,过滤器链,匹配规则。之后在代理方法中分别对ApplicationFilterChain的必要参数进行了设置然后调用filterChain.invoke(filterChain);现在知道了上面的那个invoke是在哪被调用了,再者就是这个代理方法中的try,catch块也是很有必要的,在invoke抛出异常时,会被catch接住,然后调用filterChain的operateException。看一下这个方法。
public void operateException(Exception e) { for (FilterChain f : filterChainList) { if (f instanceof FilterChainSub) { ((FilterChainSub) f).operateException(e, target); } } }
它会在通知链中找到可以处理异常的通知然后处理掉他。这是一个很大的隐患,时间原因就不改了,有兴趣的朋友可以试着改改。例如可以使用FilterChain出错时的index作索引,取出通知实例然后调用它的处理异常的方法。destroy是同样的思路也有同样的毛病。下面是测试代码
public class TestAop { public static void main(String[] args) { ApplicationFilterChain chain = new ApplicationFilterChain; chain.addChain(new Transactional); ServiceImpl impl = new ServiceImpl; Interceptor inte = new Interceptor(impl, chain, "^save*"); Service s = (Service) Proxy.newProxyInstance(// TestAop.class.getClassLoader, // impl.getClass.getInterfaces, // inte); s.save; } }