摘要:在前面的文章中实现的功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。改装原有功能现在要改装原来的的实现代码,让的功能加入到框架中为了让切面能够排序,先添加一个注解,用于标记排序。
前言
在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章中已经实现了AOP功能并且引用aspectj表达式实现切点的功能,这篇文章继续完善doodle框架的AOP功能。
在前面的文章中实现的AOP功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。这篇文章就要来实现多个代理的功能,也就是实现代理链。
实现代理链在com.zbw.aop包下创建一个类起名为AdviceChain
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 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; } /** * 递归执行 执行代理通知列 */ public Object doAdviceChain() throws Throwable { ... } }
由于要实现多个通知类链式执行的功能,这个类就是代替之前的ProxyAdvisor来生产代理类,并且通过doAdviceChain()方法执行具体的切面方法以及目标代理类的方法。
在最初设计这个方法的时候,我想的是直接for循环proxyList这个属性里的ProxyAdvisor,然后一个个执行对应的Advice方法不就行了,后来发现这是不行的。因为在AOP的功能设计里,多个切面的执行顺序是一种"先入后出"的顺序。比如说有两个切面Aspect1和Aspect2,那么他们的执行顺序应该是Aspect1@before()->Aspect2@before()->targetClass@method()->Aspect2@after()->Aspect1@after(),先执行的Aspect1@before()方法要在最后执行Aspect1@after()。
要实现"先入后出"的功能通常有两种实现方式,一是借助栈这个数据结构,二是用递归的方式,这里我们用递归的方式实现。
在实现doAdviceChain()的功能之前,先修改之前的ProxyAdvisor类。
... 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的大小,则调用目标类的方法。
这样就形成一个递归的形式来实现代理链。
改装原有AOP功能现在要改装原来的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
// Aop.java ... /** * 获取目标类匹配的代理通知列表 */ 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 ... /** * 执行Aop */ public void doAop() { //创建所有的代理通知列表 ListproxyList = 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 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, ListproxyList) { return Enhancer.create(targetClass, new AdviceMethodInterceptor(targetClass, proxyList)); } /** * cglib MethodInterceptor实现类 */ private static class AdviceMethodInterceptor implements MethodInterceptor { /** * 目标类 */ 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(); } } }
代理链的功能又实现了,现在可以写测试用例了。
测试用例先实现两个切面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
原文地址:从零开始实现一个简易的Java MVC框架(六)--加强AOP功能
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/71611.html
摘要:不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的框架。原文地址从零开始实现一个简易的框架 前言 最近在看spring-boot框架的源码,看了源码之后更是让我感受到了spring-boot功能的强大。而且使用了很多的设计模式,让人在看的时候觉得有点难以下手。 不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的...
摘要:接下来就可以把这个切点类加入到我们之前实现的功能中了。实现的切点功能首先改装注解,把之前改成来存储表达式。测试用例在上一篇文章从零开始实现一个简易的框架四实现中的测试用例的基础上修改测试用例。 前言 在上一节从零开始实现一个简易的Java MVC框架(四)--实现AOP中我们实现了AOP的功能,已经可以生成对应的代理类了,但是对于代理对象的选择只能通过指定的类,这样确实不方便也不合理。...
摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。 spring-boot的Starter 一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的startup.sh(startup.bat)的启动脚本来启动web项目 而在spring-b...
摘要:前言在从零开始实现一个简易的框架七实现中实现了框架的的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 优化的目标是1.去除DispatcherServlet请求分发器中的http逻...
摘要:容器实际上就是存放所有的地方,即以及相关信息对应其实体的容器,为什么称之为呢,因为在中,定义信息和实例的东西叫。了解到这个以后接下来就可以开始编写容器了,在包下创建一个类叫。获取容器实例至此,这个容器就完成了。 项目准备 首先确保你拥有以下环境或者工具 idea java 8 maven 3.3.X lombok插件 然后我们创建一个maven工程,编写pom.xml引入一些需要的...
阅读 1854·2021-11-19 09:40
阅读 2101·2021-10-09 09:43
阅读 3162·2021-09-06 15:00
阅读 2793·2019-08-29 13:04
阅读 2738·2019-08-26 11:53
阅读 3462·2019-08-26 11:46
阅读 2299·2019-08-26 11:38
阅读 368·2019-08-26 11:27