从零开始实现一个简易的Java MVC框架(六)--加强AOP功能
前言
在前面從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(四)--實(shí)現(xiàn)AOP和從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(五)--引入aspectj實(shí)現(xiàn)AOP切點(diǎn)這兩節(jié)文章中已經(jīng)實(shí)現(xiàn)了AOP功能并且引用aspectj表達(dá)式實(shí)現(xiàn)切點(diǎn)的功能,這篇文章繼續(xù)完善doodle框架的AOP功能。
在前面的文章中實(shí)現(xiàn)的AOP功能時(shí),目標(biāo)類都只能被一個(gè)切面代理,如果想要生成第二個(gè)代理類,就會(huì)把之前的代理類覆蓋。這篇文章就要來(lái)實(shí)現(xiàn)多個(gè)代理的功能,也就是實(shí)現(xiàn)代理鏈。
實(shí)現(xiàn)代理鏈
在com.zbw.aop包下創(chuàng)建一個(gè)類起名為AdviceChain
package com.zbw.aop;import .../*** 通知鏈*/ public class AdviceChain {/*** 目標(biāo)類*/private final Class<?> targetClass;/*** 目標(biāo)實(shí)例*/private final Object target;/*** 目標(biāo)方法*/private final Method method;/*** 目標(biāo)方法參數(shù)*/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;}/*** 遞歸執(zhí)行 執(zhí)行代理通知列*/public Object doAdviceChain() throws Throwable {...} } 復(fù)制代碼由于要實(shí)現(xiàn)多個(gè)通知類鏈?zhǔn)綀?zhí)行的功能,這個(gè)類就是代替之前的ProxyAdvisor來(lái)生產(chǎn)代理類,并且通過(guò)doAdviceChain()方法執(zhí)行具體的切面方法以及目標(biāo)代理類的方法。
在最初設(shè)計(jì)這個(gè)方法的時(shí)候,我想的是直接for循環(huán)proxyList這個(gè)屬性里的ProxyAdvisor,然后一個(gè)個(gè)執(zhí)行對(duì)應(yīng)的Advice方法不就行了,后來(lái)發(fā)現(xiàn)這是不行的。因?yàn)樵贏OP的功能設(shè)計(jì)里,多個(gè)切面的執(zhí)行順序是一種'先入后出'的順序。比如說(shuō)有兩個(gè)切面Aspect1和Aspect2,那么他們的執(zhí)行順序應(yīng)該是Aspect1@before()->Aspect2@before()->targetClass@method()->Aspect2@after()->Aspect1@after(),先執(zhí)行的Aspect1@before()方法要在最后執(zhí)行Aspect1@after()。
要實(shí)現(xiàn)'先入后出'的功能通常有兩種實(shí)現(xiàn)方式,一是借助棧這個(gè)數(shù)據(jù)結(jié)構(gòu),二是用遞歸的方式,這里我們用遞歸的方式實(shí)現(xiàn)。
在實(shí)現(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;} } 復(fù)制代碼在ProxyAdvisor類中添加一個(gè)屬性order,這是用于存儲(chǔ)這個(gè)切面類的執(zhí)行順序的。然后再修改doProxy()方法,把傳入?yún)?shù)由原來(lái)的很多類相關(guān)的信息改為傳入AdviceChain,因?yàn)槲覀儼杨愋畔⒍挤旁诹薃dviceChain中了。然后把原來(lái)在doProxy()方法開(kāi)頭的if (!pointcut.matches(method))這個(gè)切點(diǎn)判斷移除,這個(gè)判斷將會(huì)改在AdviceChain中。然后在原來(lái)要調(diào)用proxy.invokeSuper(target, args);的地方改為調(diào)用adviceChain.doAdviceChain();,這樣就能形成一個(gè)遞歸調(diào)用。
現(xiàn)在來(lái)具體實(shí)現(xiàn)AdviceChain的doAdviceChain()方法。
...public Object doAdviceChain() throws Throwable {Object result;while (adviceIndex < proxyList.size()&& !proxyList.get(adviceIndex).getPointcut().matches(method)) {//如果當(dāng)前方法不匹配切點(diǎn),則略過(guò)該代理通知類adviceIndex++;}if (adviceIndex < proxyList.size()) {result = proxyList.get(adviceIndex++).doProxy(this);} else {result = methodProxy.invokeSuper(target, args);}return result; } 復(fù)制代碼在這個(gè)方法中,先是通過(guò)一個(gè)while循環(huán)判定proxyList的當(dāng)前ProxyAdvisor是否匹配切點(diǎn)表達(dá)式,如果不匹配日則跳過(guò)這個(gè)ProxyAdvisor且adviceIndex這個(gè)計(jì)數(shù)器加一,假如匹配的話,就執(zhí)行ProxyAdvisor的doProxy()方法,并且把自己當(dāng)作參數(shù)傳入過(guò)去。直到adviceIndex計(jì)數(shù)器的大小大于等于proxyList的大小,則調(diào)用目標(biāo)類的方法。
這樣就形成一個(gè)遞歸的形式來(lái)實(shí)現(xiàn)代理鏈。
改裝原有AOP功能
現(xiàn)在要改裝原來(lái)的AOP的實(shí)現(xiàn)代碼,讓AdviceChain的功能加入到框架中
為了讓切面能夠排序,先添加一個(gè)Order注解,用于標(biāo)記排序。在zbw.aop包下創(chuàng)建Order注解類
package com.zbw.aop.annotation;import .../*** aop順序*/ (ElementType.TYPE) (RetentionPolicy.RUNTIME) public Order {/*** aop順序,值越大越先執(zhí)行*/int value() default 0; } 復(fù)制代碼然后再改裝AOP執(zhí)行器,先修改createProxyAdvisor()方法,把Order注解的值存入到ProxyAdvisor中。
// Aop.java .../*** 通過(guò)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); } 復(fù)制代碼然后再增加一個(gè)createMatchProxies()方法,由于之前生成代理類都是用一個(gè)ProxyAdvisor就可以了,而現(xiàn)在是一個(gè)List,所以現(xiàn)在要用該方法用于生成一個(gè)List,其中存放的是匹配目標(biāo)類的切面集合。傳入的參數(shù)proxyList為所有的ProxyAdvisor集合,返回的參數(shù)為目標(biāo)類匹配的代理通知集合,并且這個(gè)集合是根據(jù)order排序的。
// Aop.java .../*** 獲取目標(biāo)類匹配的代理通知列表*/ 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()); } 復(fù)制代碼最后再修改doAop()方法。
// Aop.java .../*** 執(zhí)行Aop*/ public void doAop() {//創(chuàng)建所有的代理通知列表List<ProxyAdvisor> proxyList = 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<ProxyAdvisor> matchProxies = createMatchProxies(proxyList, clz);if (matchProxies.size() > 0) {Object proxyBean = ProxyCreator.createProxy(clz, matchProxies);beanContainer.addBean(clz, proxyBean);}}); } 復(fù)制代碼同樣的,由于代理類從ProxyAdvisor改成AdviceChain,對(duì)應(yīng)的代理類創(chuàng)造器也要做對(duì)應(yīng)的修改。
package com.zbw.aop;import .../*** 代理類創(chuàng)建器*/ public final class ProxyCreator {/*** 創(chuàng)建代理類*/public static Object createProxy(Class<?> targetClass, List<ProxyAdvisor> proxyList) {return Enhancer.create(targetClass, new AdviceMethodInterceptor(targetClass, proxyList));}/*** cglib MethodInterceptor實(shí)現(xiàn)類*/private static class AdviceMethodInterceptor implements MethodInterceptor {/*** 目標(biāo)類*/private final Class<?> targetClass;/*** 代理通知列表*/private List<ProxyAdvisor> proxyList;public AdviceMethodInterceptor(Class<?> targetClass, List<ProxyAdvisor> proxyList) {this.targetClass = targetClass;this.proxyList = proxyList;}public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {return new AdviceChain(targetClass, target, method, args, proxy, proxyList).doAdviceChain();}} } 復(fù)制代碼代理鏈的功能又實(shí)現(xiàn)了,現(xiàn)在可以寫(xiě)測(cè)試用例了。
測(cè)試用例
先實(shí)現(xiàn)兩個(gè)切面DoodleAspect和DoodleAspect2:
// DoodleAspect 4j (1) (pointcut = "@within(com.zbw.core.annotation.Controller)") public class DoodleAspect implements AroundAdvice {public void before(Class<?> clz, Method method, Object[] args) throws Throwable {log.info("-----------before DoodleAspect-----------");log.info("class: {}, method: {}", clz.getName(), method.getName());}public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable {log.info("-----------after DoodleAspect-----------");log.info("class: {}, method: {}", clz, method.getName());}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());} } 復(fù)制代碼// DoodleAspect2 4j (2) (pointcut = "@within(com.zbw.core.annotation.Controller)") public class DoodleAspect2 implements AroundAdvice {public void before(Class<?> clz, Method method, Object[] args) throws Throwable {log.info("-----------before DoodleAspect2-----------");log.info("class: {}, method: {}", clz.getName(), method.getName());}public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable {log.info("-----------after DoodleAspect2-----------");log.info("class: {}, method: {}", clz, method.getName());}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());} } 復(fù)制代碼然后在AopTest測(cè)試類中調(diào)用DoodleController的hello()方法。
4j public class AopTest {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();} } 復(fù)制代碼在結(jié)果的圖中可以看出DoodleAspect和DoodleAspect2兩個(gè)代理方法都執(zhí)行了,并且是按照預(yù)期的執(zhí)行順序執(zhí)行的。
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(一)--前言
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(二)--實(shí)現(xiàn)Bean容器
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(三)--實(shí)現(xiàn)IOC
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(四)--實(shí)現(xiàn)AOP
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(五)--引入aspectj實(shí)現(xiàn)AOP切點(diǎn)
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(六)--加強(qiáng)AOP功能
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(七)--實(shí)現(xiàn)MVC
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(八)--制作Starter
- 從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(九)--優(yōu)化MVC代碼
源碼地址:doodle
原文地址:從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(六)--加強(qiáng)AOP功能
總結(jié)
以上是生活随笔為你收集整理的从零开始实现一个简易的Java MVC框架(六)--加强AOP功能的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数据结构与算法之KMP算法02
- 下一篇: 用Vue来实现图片上传多种方式