摘要:在前面的文章中實現(xiàn)的功能時,目標(biāo)類都只能被一個切面代理,如果想要生成第二個代理類,就會把之前的代理類覆蓋。改裝原有功能現(xiàn)在要改裝原來的的實現(xiàn)代碼,讓的功能加入到框架中為了讓切面能夠排序,先添加一個注解,用于標(biāo)記排序。
前言
在前面從零開始實現(xiàn)一個簡易的Java MVC框架(四)--實現(xiàn)AOP和從零開始實現(xiàn)一個簡易的Java MVC框架(五)--引入aspectj實現(xiàn)AOP切點這兩節(jié)文章中已經(jīng)實現(xiàn)了AOP功能并且引用aspectj表達(dá)式實現(xiàn)切點的功能,這篇文章繼續(xù)完善doodle框架的AOP功能。
在前面的文章中實現(xiàn)的AOP功能時,目標(biāo)類都只能被一個切面代理,如果想要生成第二個代理類,就會把之前的代理類覆蓋。這篇文章就要來實現(xiàn)多個代理的功能,也就是實現(xiàn)代理鏈。
實現(xiàn)代理鏈在com.zbw.aop包下創(chuàng)建一個類起名為AdviceChain
package com.zbw.aop; import ... /** * 通知鏈 */ public class AdviceChain { /** * 目標(biāo)類 */ @Getter private final Class> targetClass; /** * 目標(biāo)實例 */ @Getter private final Object target; /** * 目標(biāo)方法 */ @Getter private final Method method; /** * 目標(biāo)方法參數(shù) */ @Getter private final Object[] args; /** * 代理方法 */ private final MethodProxy methodProxy; /** * 代理通知列 */ private ListproxyList; /** * 代理通知列index */ private int adviceIndex = 0; public AdviceChain(Class> targetClass, Object target, Method method, Object[] args, MethodProxy methodProxy, List proxyList) { this.targetClass = targetClass; this.target = target; this.method = method; this.args = args; this.methodProxy = methodProxy; this.proxyList = proxyList; } /** * 遞歸執(zhí)行 執(zhí)行代理通知列 */ public Object doAdviceChain() throws Throwable { ... } }
由于要實現(xiàn)多個通知類鏈?zhǔn)綀?zhí)行的功能,這個類就是代替之前的ProxyAdvisor來生產(chǎn)代理類,并且通過doAdviceChain()方法執(zhí)行具體的切面方法以及目標(biāo)代理類的方法。
在最初設(shè)計這個方法的時候,我想的是直接for循環(huán)proxyList這個屬性里的ProxyAdvisor,然后一個個執(zhí)行對應(yīng)的Advice方法不就行了,后來發(fā)現(xiàn)這是不行的。因為在AOP的功能設(shè)計里,多個切面的執(zhí)行順序是一種"先入后出"的順序。比如說有兩個切面Aspect1和Aspect2,那么他們的執(zhí)行順序應(yīng)該是Aspect1@before()->Aspect2@before()->targetClass@method()->Aspect2@after()->Aspect1@after(),先執(zhí)行的Aspect1@before()方法要在最后執(zhí)行Aspect1@after()。
要實現(xiàn)"先入后出"的功能通常有兩種實現(xiàn)方式,一是借助棧這個數(shù)據(jù)結(jié)構(gòu),二是用遞歸的方式,這里我們用遞歸的方式實現(xiàn)。
在實現(xiàn)doAdviceChain()的功能之前,先修改之前的ProxyAdvisor類。
... public class ProxyAdvisor { ... /** * 執(zhí)行順序 */ private int order; /** * 執(zhí)行代理方法 */ 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(); //執(zhí)行代理鏈方法 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,這是用于存儲這個切面類的執(zhí)行順序的。然后再修改doProxy()方法,把傳入?yún)?shù)由原來的很多類相關(guān)的信息改為傳入AdviceChain,因為我們把類信息都放在了AdviceChain中了。然后把原來在doProxy()方法開頭的if (!pointcut.matches(method))這個切點判斷移除,這個判斷將會改在AdviceChain中。然后在原來要調(diào)用proxy.invokeSuper(target, args);的地方改為調(diào)用adviceChain.doAdviceChain();,這樣就能形成一個遞歸調(diào)用。
現(xiàn)在來具體實現(xiàn)AdviceChain的doAdviceChain()方法。
... public Object doAdviceChain() throws Throwable { Object result; while (adviceIndex < proxyList.size() && !proxyList.get(adviceIndex).getPointcut().matches(method)) { //如果當(dāng)前方法不匹配切點,則略過該代理通知類 adviceIndex++; } if (adviceIndex < proxyList.size()) { result = proxyList.get(adviceIndex++).doProxy(this); } else { result = methodProxy.invokeSuper(target, args); } return result; }
在這個方法中,先是通過一個while循環(huán)判定proxyList的當(dāng)前ProxyAdvisor是否匹配切點表達(dá)式,如果不匹配日則跳過這個ProxyAdvisor且adviceIndex這個計數(shù)器加一,假如匹配的話,就執(zhí)行ProxyAdvisor的doProxy()方法,并且把自己當(dāng)作參數(shù)傳入過去。直到adviceIndex計數(shù)器的大小大于等于proxyList的大小,則調(diào)用目標(biāo)類的方法。
這樣就形成一個遞歸的形式來實現(xiàn)代理鏈。
改裝原有AOP功能現(xiàn)在要改裝原來的AOP的實現(xiàn)代碼,讓AdviceChain的功能加入到框架中
為了讓切面能夠排序,先添加一個Order注解,用于標(biāo)記排序。在zbw.aop包下創(chuàng)建Order注解類
package com.zbw.aop.annotation; import ... /** * aop順序 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Order { /** * aop順序,值越大越先執(zhí)行 */ int value() default 0; }
然后再改裝AOP執(zhí)行器,先修改createProxyAdvisor()方法,把Order注解的值存入到ProxyAdvisor中。
// Aop.java ... /** * 通過Aspect切面類創(chuàng)建代理通知類 */ 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就可以了,而現(xiàn)在是一個List
// Aop.java ... /** * 獲取目標(biāo)類匹配的代理通知列表 */ private ListcreateMatchProxies(List 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 ... /** * 執(zhí)行Aop */ public void doAop() { //創(chuàng)建所有的代理通知列表 ListproxyList = beanContainer.getClassesBySuper(Advice.class) .stream() .filter(clz -> clz.isAnnotationPresent(Aspect.class)) .map(this::createProxyAdvisor) .collect(Collectors.toList()); //創(chuàng)建代理類并注入到Bean容器中 beanContainer.getClasses() .stream() .filter(clz -> !Advice.class.isAssignableFrom(clz)) .filter(clz -> !clz.isAnnotationPresent(Aspect.class)) .forEach(clz -> { List matchProxies = createMatchProxies(proxyList, clz); if (matchProxies.size() > 0) { Object proxyBean = ProxyCreator.createProxy(clz, matchProxies); beanContainer.addBean(clz, proxyBean); } }); }
同樣的,由于代理類從ProxyAdvisor改成AdviceChain,對應(yīng)的代理類創(chuàng)造器也要做對應(yīng)的修改。
package com.zbw.aop; import ... /** * 代理類創(chuàng)建器 */ public final class ProxyCreator { /** * 創(chuàng)建代理類 */ public static Object createProxy(Class> targetClass, ListproxyList) { return Enhancer.create(targetClass, new AdviceMethodInterceptor(targetClass, proxyList)); } /** * cglib MethodInterceptor實現(xiàn)類 */ private static class AdviceMethodInterceptor implements MethodInterceptor { /** * 目標(biāo)類 */ private final Class> targetClass; /** * 代理通知列表 */ private List proxyList; public AdviceMethodInterceptor(Class> targetClass, List 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(); } } }
代理鏈的功能又實現(xiàn)了,現(xiàn)在可以寫測試用例了。
測試用例先實現(xiàn)兩個切面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測試類中調(diào)用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(); } }
在結(jié)果的圖中可以看出DoodleAspect和DoodleAspect2兩個代理方法都執(zhí)行了,并且是按照預(yù)期的執(zhí)行順序執(zhí)行的。
從零開始實現(xiàn)一個簡易的Java MVC框架(一)--前言
從零開始實現(xiàn)一個簡易的Java MVC框架(二)--實現(xiàn)Bean容器
從零開始實現(xiàn)一個簡易的Java MVC框架(三)--實現(xiàn)IOC
從零開始實現(xiàn)一個簡易的Java MVC框架(四)--實現(xiàn)AOP
從零開始實現(xiàn)一個簡易的Java MVC框架(五)--引入aspectj實現(xiàn)AOP切點
從零開始實現(xiàn)一個簡易的Java MVC框架(六)--加強(qiáng)AOP功能
從零開始實現(xiàn)一個簡易的Java MVC框架(七)--實現(xiàn)MVC
從零開始實現(xiàn)一個簡易的Java MVC框架(八)--制作Starter
從零開始實現(xiàn)一個簡易的Java MVC框架(九)--優(yōu)化MVC代碼
源碼地址:doodle
原文地址:從零開始實現(xiàn)一個簡易的Java MVC框架(六)--加強(qiáng)AOP功能
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/71611.html
摘要:不過仔細(xì)了解了一段時候發(fā)現(xiàn),其實他的原理是很簡單的,所以想要自己也動手實現(xiàn)一個功能類似的框架。原文地址從零開始實現(xiàn)一個簡易的框架 前言 最近在看spring-boot框架的源碼,看了源碼之后更是讓我感受到了spring-boot功能的強(qiáng)大。而且使用了很多的設(shè)計模式,讓人在看的時候覺得有點難以下手。 不過仔細(xì)了解了一段時候發(fā)現(xiàn),其實他的原理是很簡單的,所以想要自己也動手實現(xiàn)一個功能類似的...
摘要:接下來就可以把這個切點類加入到我們之前實現(xiàn)的功能中了。實現(xiàn)的切點功能首先改裝注解,把之前改成來存儲表達(dá)式。測試用例在上一篇文章從零開始實現(xiàn)一個簡易的框架四實現(xiàn)中的測試用例的基礎(chǔ)上修改測試用例。 前言 在上一節(jié)從零開始實現(xiàn)一個簡易的Java MVC框架(四)--實現(xiàn)AOP中我們實現(xiàn)了AOP的功能,已經(jīng)可以生成對應(yīng)的代理類了,但是對于代理對象的選擇只能通過指定的類,這樣確實不方便也不合理。...
摘要:服務(wù)器相關(guān)配置啟動類資源目錄目錄靜態(tài)文件目錄端口號目錄目錄實現(xiàn)內(nèi)嵌服務(wù)器在上一章文章從零開始實現(xiàn)一個簡易的框架七實現(xiàn)已經(jīng)在文件中引入了依賴,所以這里就不用引用了。 spring-boot的Starter 一個項目總是要有一個啟動的地方,當(dāng)項目部署在tomcat中的時候,經(jīng)常就會用tomcat的startup.sh(startup.bat)的啟動腳本來啟動web項目 而在spring-b...
摘要:前言在從零開始實現(xiàn)一個簡易的框架七實現(xiàn)中實現(xiàn)了框架的的功能,不過最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進(jìn)行優(yōu)化。 前言 在從零開始實現(xiàn)一個簡易的Java MVC框架(七)--實現(xiàn)MVC中實現(xiàn)了doodle框架的MVC的功能,不過最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進(jìn)行優(yōu)化。 優(yōu)化的目標(biāo)是1.去除DispatcherServlet請求分發(fā)器中的http邏...
摘要:容器實際上就是存放所有的地方,即以及相關(guān)信息對應(yīng)其實體的容器,為什么稱之為呢,因為在中,定義信息和實例的東西叫。了解到這個以后接下來就可以開始編寫容器了,在包下創(chuàng)建一個類叫。獲取容器實例至此,這個容器就完成了。 項目準(zhǔn)備 首先確保你擁有以下環(huán)境或者工具 idea java 8 maven 3.3.X lombok插件 然后我們創(chuàng)建一個maven工程,編寫pom.xml引入一些需要的...
閱讀 1924·2021-11-19 09:40
閱讀 2132·2021-10-09 09:43
閱讀 3294·2021-09-06 15:00
閱讀 2810·2019-08-29 13:04
閱讀 2766·2019-08-26 11:53
閱讀 3512·2019-08-26 11:46
閱讀 2320·2019-08-26 11:38
閱讀 390·2019-08-26 11:27