在前面從零開始實現一個簡易的Java MVC框架(四)--實現AOP和從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點這兩節文章中已經實現了AOP功能而且引用aspectj表達式實現切點的功能,這篇文章繼續完善doodle框架的AOP功能。java
在前面的文章中實現的AOP功能時,目標類都只能被一個切面代理,若是想要生成第二個代理類,就會把以前的代理類覆蓋。這篇文章就要來實現多個代理的功能,也就是實現代理鏈。git
在com.zbw.aop包下建立一個類起名爲AdviceChain
github
package com.zbw.aop; import ... /** * 通知鏈 */ public class AdviceChain { /** * 目標類 */ @Getter private final Class<?> targetClass; /** * 目標實例 */ @Getter private final Object target; /** * 目標方法 */ @Getter private final Method method; /** * 目標方法參數 */ @Getter private final Object[] args; /** * 代理方法 */ private final MethodProxy methodProxy; /** * 代理通知列 */ private List<ProxyAdvisor> proxyList; /** * 代理通知列index */ private int adviceIndex = 0; public AdviceChain(Class<?> targetClass, Object target, Method method, Object[] args, MethodProxy methodProxy, List<ProxyAdvisor> proxyList) { this.targetClass = targetClass; this.target = target; this.method = method; this.args = args; this.methodProxy = methodProxy; this.proxyList = proxyList; } /** * 遞歸執行 執行代理通知列 */ public Object doAdviceChain() throws Throwable { ... } }
因爲要實現多個通知類鏈式執行的功能,這個類就是代替以前的ProxyAdvisor
來生產代理類,而且經過doAdviceChain()
方法執行具體的切面方法以及目標代理類的方法。express
在最初設計這個方法的時候,我想的是直接for循環proxyList
這個屬性裏的ProxyAdvisor
,而後一個個執行對應的Advice方法不就好了,後來發現這是不行的。由於在AOP的功能設計裏,多個切面的執行順序是一種'先入後出'的順序。好比說有兩個切面Aspect1
和Aspect2
,那麼他們的執行順序應該是Aspect1@before()->Aspect2@before()->targetClass@method()->Aspect2@after()->Aspect1@after(),先執行的Aspect1@before()方法要在最後執行Aspect1@after()。數據結構
要實現'先入後出'的功能一般有兩種實現方式,一是藉助棧這個數據結構,二是用遞歸的方式,這裏咱們用遞歸的方式實現。框架
在實現doAdviceChain()
的功能以前,先修改以前的ProxyAdvisor
類。ide
... public class ProxyAdvisor { ... /** * 執行順序 */ private int order; /** * 執行代理方法 */ public Object doProxy(AdviceChain adviceChain) throws Throwable { Object result = null; Class<?> targetClass = adviceChain.getTargetClass(); Method method = adviceChain.getMethod(); Object[] args = adviceChain.getArgs(); if (advice instanceof MethodBeforeAdvice) { ((MethodBeforeAdvice) advice).before(targetClass, method, args); } try { result = adviceChain.doAdviceChain(); //執行代理鏈方法 if (advice instanceof AfterReturningAdvice) { ((AfterReturningAdvice) advice).afterReturning(targetClass, result, method, args); } } catch (Exception e) { if (advice instanceof ThrowsAdvice) { ((ThrowsAdvice) advice).afterThrowing(targetClass, method, args, e); } else { throw new Throwable(e); } } return result; } }
在ProxyAdvisor
類中添加一個屬性order
,這是用於存儲這個切面類的執行順序的。而後再修改doProxy()
方法,把傳入參數由原來的不少類相關的信息改成傳入AdviceChain
,由於咱們把類信息都放在了AdviceChain
中了。而後把原來在doProxy()
方法開頭的if (!pointcut.matches(method))
這個切點判斷移除,這個判斷將會改在AdviceChain
中。而後在原來要調用proxy.invokeSuper(target, args);
的地方改成調用adviceChain.doAdviceChain();
,這樣就能造成一個遞歸調用。測試
如今來具體實現AdviceChain
的doAdviceChain()
方法。優化
... public Object doAdviceChain() throws Throwable { Object result; while (adviceIndex < proxyList.size() && !proxyList.get(adviceIndex).getPointcut().matches(method)) { //若是當前方法不匹配切點,則略過該代理通知類 adviceIndex++; } if (adviceIndex < proxyList.size()) { result = proxyList.get(adviceIndex++).doProxy(this); } else { result = methodProxy.invokeSuper(target, args); } return result; }
在這個方法中,先是經過一個while循環斷定proxyList
的當前ProxyAdvisor
是否匹配切點表達式,若是不匹配日則跳過這個ProxyAdvisor
且adviceIndex
這個計數器加一,假如匹配的話,就執行ProxyAdvisor
的doProxy()
方法,而且把本身看成參數傳入過去。直到adviceIndex
計數器的大小大於等於proxyList
的大小,則調用目標類的方法。this
這樣就造成一個遞歸的形式來實現代理鏈。
如今要改裝原來的AOP的實現代碼,讓AdviceChain
的功能加入到框架中
爲了讓切面可以排序,先添加一個Order
註解,用於標記排序。在zbw.aop包下建立Order
註解類
package com.zbw.aop.annotation; import ... /** * aop順序 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Order { /** * aop順序,值越大越先執行 */ int value() default 0; }
而後再改裝AOP執行器,先修改createProxyAdvisor()
方法,把Order
註解的值存入到ProxyAdvisor
中。
// Aop.java ... /** * 經過Aspect切面類建立代理通知類 */ private ProxyAdvisor createProxyAdvisor(Class<?> aspectClass) { int order = 0; if (aspectClass.isAnnotationPresent(Order.class)) { order = aspectClass.getAnnotation(Order.class).value(); } String expression = aspectClass.getAnnotation(Aspect.class).pointcut(); ProxyPointcut proxyPointcut = new ProxyPointcut(); proxyPointcut.setExpression(expression); Advice advice = (Advice) beanContainer.getBean(aspectClass); return new ProxyAdvisor(advice, proxyPointcut, order); }
而後再增長一個createMatchProxies()
方法,因爲以前生成代理類都是用一個ProxyAdvisor
就能夠了,而如今是一個List<ProxyAdvisor>,因此如今要用該方法用於生成一個List,其中存放的是匹配目標類的切面集合。傳入的參數proxyList
爲全部的ProxyAdvisor
集合,返回的參數爲目標類匹配的代理通知集合,而且這個集合是根據order排序的。
// Aop.java ... /** * 獲取目標類匹配的代理通知列表 */ private List<ProxyAdvisor> createMatchProxies(List<ProxyAdvisor> proxyList, Class<?> targetClass) { Object targetBean = beanContainer.getBean(targetClass); return proxyList .stream() .filter(advisor -> advisor.getPointcut().matches(targetBean.getClass())) .sorted(Comparator.comparingInt(ProxyAdvisor::getOrder)) .collect(Collectors.toList()); }
最後再修改doAop()
方法。
// Aop.java ... /** * 執行Aop */ public void doAop() { //建立全部的代理通知列表 List<ProxyAdvisor> proxyList = beanContainer.getClassesBySuper(Advice.class) .stream() .filter(clz -> clz.isAnnotationPresent(Aspect.class)) .map(this::createProxyAdvisor) .collect(Collectors.toList()); //建立代理類並注入到Bean容器中 beanContainer.getClasses() .stream() .filter(clz -> !Advice.class.isAssignableFrom(clz)) .filter(clz -> !clz.isAnnotationPresent(Aspect.class)) .forEach(clz -> { List<ProxyAdvisor> matchProxies = createMatchProxies(proxyList, clz); if (matchProxies.size() > 0) { Object proxyBean = ProxyCreator.createProxy(clz, matchProxies); beanContainer.addBean(clz, proxyBean); } }); }
一樣的,因爲代理類從ProxyAdvisor
改爲AdviceChain
,對應的代理類創造器也要作對應的修改。
package com.zbw.aop; import ... /** * 代理類建立器 */ public final class ProxyCreator { /** * 建立代理類 */ public static Object createProxy(Class<?> targetClass, List<ProxyAdvisor> proxyList) { return Enhancer.create(targetClass, new AdviceMethodInterceptor(targetClass, proxyList)); } /** * cglib MethodInterceptor實現類 */ private static class AdviceMethodInterceptor implements MethodInterceptor { /** * 目標類 */ private final Class<?> targetClass; /** * 代理通知列表 */ private List<ProxyAdvisor> proxyList; public AdviceMethodInterceptor(Class<?> targetClass, List<ProxyAdvisor> proxyList) { this.targetClass = targetClass; this.proxyList = proxyList; } @Override public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { return new AdviceChain(targetClass, target, method, args, proxy, proxyList).doAdviceChain(); } } }
代理鏈的功能又實現了,如今能夠寫測試用例了。
先實現兩個切面DoodleAspect
和DoodleAspect2
:
// DoodleAspect @Slf4j @Order(1) @Aspect(pointcut = "@within(com.zbw.core.annotation.Controller)") public class DoodleAspect implements AroundAdvice { @Override public void before(Class<?> clz, Method method, Object[] args) throws Throwable { log.info("-----------before DoodleAspect-----------"); log.info("class: {}, method: {}", clz.getName(), method.getName()); } @Override public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable { log.info("-----------after DoodleAspect-----------"); log.info("class: {}, method: {}", clz, method.getName()); } @Override public void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e) { log.error("-----------error DoodleAspect-----------"); log.error("class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage()); } }
// DoodleAspect2 @Slf4j @Order(2) @Aspect(pointcut = "@within(com.zbw.core.annotation.Controller)") public class DoodleAspect2 implements AroundAdvice { @Override public void before(Class<?> clz, Method method, Object[] args) throws Throwable { log.info("-----------before DoodleAspect2-----------"); log.info("class: {}, method: {}", clz.getName(), method.getName()); } @Override public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable { log.info("-----------after DoodleAspect2-----------"); log.info("class: {}, method: {}", clz, method.getName()); } @Override public void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e) { log.error("-----------error DoodleAspect2-----------"); log.error("class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage()); } }
而後在AopTest
測試類中調用DoodleController
的hello()
方法。
@Slf4j public class AopTest { @Test public void doAop() { BeanContainer beanContainer = BeanContainer.getInstance(); beanContainer.loadBeans("com.zbw"); new Aop().doAop(); new Ioc().doIoc(); DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class); controller.hello(); } }
在結果的圖中能夠看出DoodleAspect
和DoodleAspect2
兩個代理方法都執行了,而且是按照預期的執行順序執行的。
- 從零開始實現一個簡易的Java MVC框架(一)--前言
- 從零開始實現一個簡易的Java MVC框架(二)--實現Bean容器
- 從零開始實現一個簡易的Java MVC框架(三)--實現IOC
- 從零開始實現一個簡易的Java MVC框架(四)--實現AOP
- 從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點
- 從零開始實現一個簡易的Java MVC框架(六)--增強AOP功能
- 從零開始實現一個簡易的Java MVC框架(七)--實現MVC
- 從零開始實現一個簡易的Java MVC框架(八)--製做Starter
- 從零開始實現一個簡易的Java MVC框架(九)--優化MVC代碼
源碼地址:doodle