资讯专栏INFORMATION COLUMN

从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

Loong_T / 2837人阅读

摘要:在前面的文章中实现的功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。改装原有功能现在要改装原来的的实现代码,让的功能加入到框架中为了让切面能够排序,先添加一个注解,用于标记排序。

前言

在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章中已经实现了AOP功能并且引用aspectj表达式实现切点的功能,这篇文章继续完善doodle框架的AOP功能。

在前面的文章中实现的AOP功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。这篇文章就要来实现多个代理的功能,也就是实现代理链。

实现代理链

在com.zbw.aop包下创建一个类起名为AdviceChain

</>复制代码

  1. package com.zbw.aop;
  2. import ...
  3. /**
  4. * 通知链
  5. */
  6. public class AdviceChain {
  7. /**
  8. * 目标类
  9. */
  10. @Getter
  11. private final Class targetClass;
  12. /**
  13. * 目标实例
  14. */
  15. @Getter
  16. private final Object target;
  17. /**
  18. * 目标方法
  19. */
  20. @Getter
  21. private final Method method;
  22. /**
  23. * 目标方法参数
  24. */
  25. @Getter
  26. private final Object[] args;
  27. /**
  28. * 代理方法
  29. */
  30. private final MethodProxy methodProxy;
  31. /**
  32. * 代理通知列
  33. */
  34. private List proxyList;
  35. /**
  36. * 代理通知列index
  37. */
  38. private int adviceIndex = 0;
  39. public AdviceChain(Class targetClass, Object target, Method method, Object[] args, MethodProxy methodProxy, List proxyList) {
  40. this.targetClass = targetClass;
  41. this.target = target;
  42. this.method = method;
  43. this.args = args;
  44. this.methodProxy = methodProxy;
  45. this.proxyList = proxyList;
  46. }
  47. /**
  48. * 递归执行 执行代理通知列
  49. */
  50. public Object doAdviceChain() throws Throwable {
  51. ...
  52. }
  53. }

由于要实现多个通知类链式执行的功能,这个类就是代替之前的ProxyAdvisor来生产代理类,并且通过doAdviceChain()方法执行具体的切面方法以及目标代理类的方法。

在最初设计这个方法的时候,我想的是直接for循环proxyList这个属性里的ProxyAdvisor,然后一个个执行对应的Advice方法不就行了,后来发现这是不行的。因为在AOP的功能设计里,多个切面的执行顺序是一种"先入后出"的顺序。比如说有两个切面Aspect1Aspect2,那么他们的执行顺序应该是Aspect1@before()->Aspect2@before()->targetClass@method()->Aspect2@after()->Aspect1@after(),先执行的Aspect1@before()方法要在最后执行Aspect1@after()。

要实现"先入后出"的功能通常有两种实现方式,一是借助栈这个数据结构,二是用递归的方式,这里我们用递归的方式实现。

在实现doAdviceChain()的功能之前,先修改之前的ProxyAdvisor类。

</>复制代码

  1. ...
  2. public class ProxyAdvisor {
  3. ...
  4. /**
  5. * 执行顺序
  6. */
  7. private int order;
  8. /**
  9. * 执行代理方法
  10. */
  11. public Object doProxy(AdviceChain adviceChain) throws Throwable {
  12. Object result = null;
  13. Class targetClass = adviceChain.getTargetClass();
  14. Method method = adviceChain.getMethod();
  15. Object[] args = adviceChain.getArgs();
  16. if (advice instanceof MethodBeforeAdvice) {
  17. ((MethodBeforeAdvice) advice).before(targetClass, method, args);
  18. }
  19. try {
  20. result = adviceChain.doAdviceChain(); //执行代理链方法
  21. if (advice instanceof AfterReturningAdvice) {
  22. ((AfterReturningAdvice) advice).afterReturning(targetClass, result, method, args);
  23. }
  24. } catch (Exception e) {
  25. if (advice instanceof ThrowsAdvice) {
  26. ((ThrowsAdvice) advice).afterThrowing(targetClass, method, args, e);
  27. } else {
  28. throw new Throwable(e);
  29. }
  30. }
  31. return result;
  32. }
  33. }

ProxyAdvisor类中添加一个属性order,这是用于存储这个切面类的执行顺序的。然后再修改doProxy()方法,把传入参数由原来的很多类相关的信息改为传入AdviceChain,因为我们把类信息都放在了AdviceChain中了。然后把原来在doProxy()方法开头的if (!pointcut.matches(method))这个切点判断移除,这个判断将会改在AdviceChain中。然后在原来要调用proxy.invokeSuper(target, args);的地方改为调用adviceChain.doAdviceChain();,这样就能形成一个递归调用。

现在来具体实现AdviceChaindoAdviceChain()方法。

</>复制代码

  1. ...
  2. public Object doAdviceChain() throws Throwable {
  3. Object result;
  4. while (adviceIndex < proxyList.size()
  5. && !proxyList.get(adviceIndex).getPointcut().matches(method)) {
  6. //如果当前方法不匹配切点,则略过该代理通知类
  7. adviceIndex++;
  8. }
  9. if (adviceIndex < proxyList.size()) {
  10. result = proxyList.get(adviceIndex++).doProxy(this);
  11. } else {
  12. result = methodProxy.invokeSuper(target, args);
  13. }
  14. return result;
  15. }

在这个方法中,先是通过一个while循环判定proxyList的当前ProxyAdvisor是否匹配切点表达式,如果不匹配日则跳过这个ProxyAdvisoradviceIndex这个计数器加一,假如匹配的话,就执行ProxyAdvisordoProxy()方法,并且把自己当作参数传入过去。直到adviceIndex计数器的大小大于等于proxyList的大小,则调用目标类的方法。

这样就形成一个递归的形式来实现代理链。

改装原有AOP功能

现在要改装原来的AOP的实现代码,让AdviceChain的功能加入到框架中

为了让切面能够排序,先添加一个Order注解,用于标记排序。在zbw.aop包下创建Order注解类

</>复制代码

  1. package com.zbw.aop.annotation;
  2. import ...
  3. /**
  4. * aop顺序
  5. */
  6. @Target(ElementType.TYPE)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface Order {
  9. /**
  10. * aop顺序,值越大越先执行
  11. */
  12. int value() default 0;
  13. }

然后再改装AOP执行器,先修改createProxyAdvisor()方法,把Order注解的值存入到ProxyAdvisor中。

</>复制代码

  1. // Aop.java
  2. ...
  3. /**
  4. * 通过Aspect切面类创建代理通知类
  5. */
  6. private ProxyAdvisor createProxyAdvisor(Class aspectClass) {
  7. int order = 0;
  8. if (aspectClass.isAnnotationPresent(Order.class)) {
  9. order = aspectClass.getAnnotation(Order.class).value();
  10. }
  11. String expression = aspectClass.getAnnotation(Aspect.class).pointcut();
  12. ProxyPointcut proxyPointcut = new ProxyPointcut();
  13. proxyPointcut.setExpression(expression);
  14. Advice advice = (Advice) beanContainer.getBean(aspectClass);
  15. return new ProxyAdvisor(advice, proxyPointcut, order);
  16. }

然后再增加一个createMatchProxies()方法,由于之前生成代理类都是用一个ProxyAdvisor就可以了,而现在是一个List,所以现在要用该方法用于生成一个List,其中存放的是匹配目标类的切面集合。传入的参数proxyList为所有的ProxyAdvisor集合,返回的参数为目标类匹配的代理通知集合,并且这个集合是根据order排序的。

</>复制代码

  1. // Aop.java
  2. ...
  3. /**
  4. * 获取目标类匹配的代理通知列表
  5. */
  6. private List createMatchProxies(List proxyList, Class targetClass) {
  7. Object targetBean = beanContainer.getBean(targetClass);
  8. return proxyList
  9. .stream()
  10. .filter(advisor -> advisor.getPointcut().matches(targetBean.getClass()))
  11. .sorted(Comparator.comparingInt(ProxyAdvisor::getOrder))
  12. .collect(Collectors.toList());
  13. }

最后再修改doAop()方法。

</>复制代码

  1. // Aop.java
  2. ...
  3. /**
  4. * 执行Aop
  5. */
  6. public void doAop() {
  7. //创建所有的代理通知列表
  8. List proxyList = beanContainer.getClassesBySuper(Advice.class)
  9. .stream()
  10. .filter(clz -> clz.isAnnotationPresent(Aspect.class))
  11. .map(this::createProxyAdvisor)
  12. .collect(Collectors.toList());
  13. //创建代理类并注入到Bean容器中
  14. beanContainer.getClasses()
  15. .stream()
  16. .filter(clz -> !Advice.class.isAssignableFrom(clz))
  17. .filter(clz -> !clz.isAnnotationPresent(Aspect.class))
  18. .forEach(clz -> {
  19. List matchProxies = createMatchProxies(proxyList, clz);
  20. if (matchProxies.size() > 0) {
  21. Object proxyBean = ProxyCreator.createProxy(clz, matchProxies);
  22. beanContainer.addBean(clz, proxyBean);
  23. }
  24. });
  25. }

同样的,由于代理类从ProxyAdvisor改成AdviceChain,对应的代理类创造器也要做对应的修改。

</>复制代码

  1. package com.zbw.aop;
  2. import ...
  3. /**
  4. * 代理类创建器
  5. */
  6. public final class ProxyCreator {
  7. /**
  8. * 创建代理类
  9. */
  10. public static Object createProxy(Class targetClass, List proxyList) {
  11. return Enhancer.create(targetClass, new AdviceMethodInterceptor(targetClass, proxyList));
  12. }
  13. /**
  14. * cglib MethodInterceptor实现类
  15. */
  16. private static class AdviceMethodInterceptor implements MethodInterceptor {
  17. /**
  18. * 目标类
  19. */
  20. private final Class targetClass;
  21. /**
  22. * 代理通知列表
  23. */
  24. private List proxyList;
  25. public AdviceMethodInterceptor(Class targetClass, List proxyList) {
  26. this.targetClass = targetClass;
  27. this.proxyList = proxyList;
  28. }
  29. @Override
  30. public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  31. return new AdviceChain(targetClass, target, method, args, proxy, proxyList).doAdviceChain();
  32. }
  33. }
  34. }

代理链的功能又实现了,现在可以写测试用例了。

测试用例

先实现两个切面DoodleAspectDoodleAspect2

</>复制代码

  1. // DoodleAspect
  2. @Slf4j
  3. @Order(1)
  4. @Aspect(pointcut = "@within(com.zbw.core.annotation.Controller)")
  5. public class DoodleAspect implements AroundAdvice {
  6. @Override
  7. public void before(Class clz, Method method, Object[] args) throws Throwable {
  8. log.info("-----------before DoodleAspect-----------");
  9. log.info("class: {}, method: {}", clz.getName(), method.getName());
  10. }
  11. @Override
  12. public void afterReturning(Class clz, Object returnValue, Method method, Object[] args) throws Throwable {
  13. log.info("-----------after DoodleAspect-----------");
  14. log.info("class: {}, method: {}", clz, method.getName());
  15. }
  16. @Override
  17. public void afterThrowing(Class clz, Method method, Object[] args, Throwable e) {
  18. log.error("-----------error DoodleAspect-----------");
  19. log.error("class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage());
  20. }
  21. }

</>复制代码

  1. // DoodleAspect2
  2. @Slf4j
  3. @Order(2)
  4. @Aspect(pointcut = "@within(com.zbw.core.annotation.Controller)")
  5. public class DoodleAspect2 implements AroundAdvice {
  6. @Override
  7. public void before(Class clz, Method method, Object[] args) throws Throwable {
  8. log.info("-----------before DoodleAspect2-----------");
  9. log.info("class: {}, method: {}", clz.getName(), method.getName());
  10. }
  11. @Override
  12. public void afterReturning(Class clz, Object returnValue, Method method, Object[] args) throws Throwable {
  13. log.info("-----------after DoodleAspect2-----------");
  14. log.info("class: {}, method: {}", clz, method.getName());
  15. }
  16. @Override
  17. public void afterThrowing(Class clz, Method method, Object[] args, Throwable e) {
  18. log.error("-----------error DoodleAspect2-----------");
  19. log.error("class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage());
  20. }
  21. }

然后在AopTest测试类中调用DoodleControllerhello()方法。

</>复制代码

  1. @Slf4j
  2. public class AopTest {
  3. @Test
  4. public void doAop() {
  5. BeanContainer beanContainer = BeanContainer.getInstance();
  6. beanContainer.loadBeans("com.zbw");
  7. new Aop().doAop();
  8. new Ioc().doIoc();
  9. DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
  10. controller.hello();
  11. }
  12. }

在结果的图中可以看出DoodleAspectDoodleAspect2两个代理方法都执行了,并且是按照预期的执行顺序执行的。

</>复制代码

  1. 从零开始实现一个简易的Java MVC框架(一)--前言

  2. 从零开始实现一个简易的Java MVC框架(二)--实现Bean容器

  3. 从零开始实现一个简易的Java MVC框架(三)--实现IOC

  4. 从零开始实现一个简易的Java MVC框架(四)--实现AOP

  5. 从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点

  6. 从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

  7. 从零开始实现一个简易的Java MVC框架(七)--实现MVC

  8. 从零开始实现一个简易的Java MVC框架(八)--制作Starter

  9. 从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

源码地址:doodle

原文地址:从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71611.html

相关文章

  • 从零开始实现一个简易Java MVC框架

    摘要:不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的框架。原文地址从零开始实现一个简易的框架 前言 最近在看spring-boot框架的源码,看了源码之后更是让我感受到了spring-boot功能的强大。而且使用了很多的设计模式,让人在看的时候觉得有点难以下手。 不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的...

    neuSnail 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(五)--引入aspectj实现AOP切点

    摘要:接下来就可以把这个切点类加入到我们之前实现的功能中了。实现的切点功能首先改装注解,把之前改成来存储表达式。测试用例在上一篇文章从零开始实现一个简易的框架四实现中的测试用例的基础上修改测试用例。 前言 在上一节从零开始实现一个简易的Java MVC框架(四)--实现AOP中我们实现了AOP的功能,已经可以生成对应的代理类了,但是对于代理对象的选择只能通过指定的类,这样确实不方便也不合理。...

    wupengyu 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(八)--制作Starter

    摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。 spring-boot的Starter 一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的startup.sh(startup.bat)的启动脚本来启动web项目 而在spring-b...

    AprilJ 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(九)--优化MVC代码

    摘要:前言在从零开始实现一个简易的框架七实现中实现了框架的的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 优化的目标是1.去除DispatcherServlet请求分发器中的http逻...

    ruicbAndroid 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(二)--实现Bean容器

    摘要:容器实际上就是存放所有的地方,即以及相关信息对应其实体的容器,为什么称之为呢,因为在中,定义信息和实例的东西叫。了解到这个以后接下来就可以开始编写容器了,在包下创建一个类叫。获取容器实例至此,这个容器就完成了。 项目准备 首先确保你拥有以下环境或者工具 idea java 8 maven 3.3.X lombok插件 然后我们创建一个maven工程,编写pom.xml引入一些需要的...

    paulquei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<