美文网首页个人笔记程序员
使用代理链自己实现AOP框架

使用代理链自己实现AOP框架

作者: fanyank | 来源:发表于2018-02-25 16:59 被阅读12次

写在前面

今年寒假已经接近尾声,寒假给自己定下的目标--完成一个简单的MVC框架也已经快完成了,在实现一些进阶的功能,比如AOP时,逐渐有了自己的理解,特此写下,帮助自己日后复习。
本文的代码参考 <<架构探险 从零开始写javaweb框架>> 这本书。

完整代码请移步我的 github

框架功能

使用注解形式对切面进行标记,支持前置增强,后置增强,抛出增强,不支持类似于AspectJ切点表达式。

框架结构

├── helper
│   └── AopHelper.java
├── proxy
│   ├── AspectProxy.java
│   ├── Proxy.java
│   ├── ProxyChain.java
│   └── ProxyManager.java

如上图框架图所示,在proxy包中分别有一下四个类:

  • Proxy: 自定义的接口,所有的代理类都需要实现这个接口,该接口只定义了一个方法 doProxy(),即执行横切逻辑。

    以下为该类的声明:

    public interface Proxy {
      Object doProxy(ProxyChain proxyChain);
    }
    
  • AspectProxy: 抽象类,实现Proxy接口,该类中定义了多个钩子方法用于子类去重载(比如前置增强方法before(),后置增强方法after(),是否对方法进行拦截intercept())。实际上,继承自该类的子类也就是我们自定义的切面了。

    以下为该类的声明:

    public abstract class AspectProxy implements Proxy{
      public final Object doProxy(ProxyChain proxyChain) {
          Object result = null;
          //通过ProxyChain拿到目标类的各种信息
          Class<?> targetClass = proxyChain.getTargetClass();
          Method method = proxyChain.getTargetMethod();
          Object[] params = proxyChain.getMethodParams();
    
          begin();
          try {
              //如果符合切点定义的条件,对该方法进行拦截
              if(intercept(targetClass,method,params)) {
                  //前置增强
                  before(targetClass,method,params);
                  //执行代理链中的代理方法,稍后会详细讲解代理链的实现
                  result = proxyChain.doProxyChain();
                  //后置增强
                  after(targetClass,method,params,result);
              //不符合切点定义的条件,直接执行目标类的原方法    
              } else {
                  result = proxyChain.doProxyChain();
              }
          } catch (Throwable throwable) {
              throwable.printStackTrace();
          } finally {
              end();
          }
    
          return result;
      }
    
      /**
      * 以下方法可以被子类(也就是自定义的切面类)选择性的重载
      */
    
    
      //不受切点的约束,目标类的任一方法执行前都要执行该方法
      public void begin(){
    
      }
      //切点定义,与AspectJ框架使用切点表达式不同,需自己重载这个方法定义切点
      public boolean intercept(Class<?> cls,Method method,Object[] params) {
          return true;
      }
      //前置增强
      public void before(Class<?> cls,Method method,Object[] params) {
    
      }
      //后置增强
      public void after(Class<?> cls,Method method,Object[] params,Object result) {
    
      }
      //抛出增强
      public void error(Class<?> cls,Method method,Object[] params) {
    
      }
      //不受切点的约束,目标类任意方法执行之后都要执行该方法
      public void end() {
    
      }
    }
    
  • ProxyChain: 普通类,声明的doProxyChain()方法用于执行主要的切面逻辑。

    以下是该类的声明:

    public class ProxyChain {
      private Class<?> targetClass;
      private Object targetObject;
      private Method targetMethod;
      //使用CGLib框架进行动态代理
      private MethodProxy methodProxy;
      private Object[] methodParams;
      //代理链,目标类可能被多个切面横切,所有的切面类都将保存在这个集合中
      private List<babyframework.proxy.Proxy> proxyList = new ArrayList<>();
      //代理链的索引,用来记录代理链的执行情况,当一个所有的代理链都被执行过后,执行目标类的原方法
      private int proxyIndex = 0;
    
      public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, MethodProxy methodProxy, Object[] methodParams, List<babyframework.proxy.Proxy> proxyList) {
          this.targetClass = targetClass;
          this.targetObject = targetObject;
          this.targetMethod = targetMethod;
          this.methodProxy = methodProxy;
          this.methodParams = methodParams;
          this.proxyList = proxyList;
      }
    
      public Class<?> getTargetClass() {
          return targetClass;
      }
    
      public Method getTargetMethod() {
          return targetMethod;
      }
    
      public Object[] getMethodParams() {
          return methodParams;
      }
    
      public Object doProxyChain() throws Throwable {
          Object methodResult;
          if(proxyIndex < proxyList.size()) {
              //调用代理方法
              methodResult = proxyList.get(proxyIndex++).doProxy(this);
          } else {
              //调用原方法
              methodResult = methodProxy.invokeSuper(targetObject,methodParams);
          }
          return methodResult;
      }
    }
    
  • ProxyManager: 普通类,对CGLib框架的Enhancer.create(cls,callback)进行了二次封装,以便适用于本AOP框架。

    以下是该类的声明:

    public class ProxyManager {
      //使用CGLib动态创建代理类
      @SuppressWarnings("unchecked")
      public static <T> T createProxy(final Class<?> targetClass, List<Proxy> proxyList) {
          return (T) Enhancer.create(targetClass, new MethodInterceptor() {
              //调用目标类任意原方法之前都会调用intercept()方法
              @Override
              public Object intercept(Object targetObject, Method targetMethod, Object[] methodParams, MethodProxy methodProxy) throws Throwable {
                  return new ProxyChain(targetClass,targetObject,targetMethod,methodProxy,methodParams,proxyList).doProxyChain();
              }
          });
      }
    }
    

对代理链的理解

  1. 我们首先从 ProxyManager.createProxy()方法入手,该方法的目的就是创建代理类,使用CGLib框架创建代理类时,需要传入一个callback,这个callback会拦截目标类原方法的执行,转而执行intercept()方法。

  2. 进一步查看intercept()方法,该方法创建了一个ProxyChain()对象并调用了doProxyChain()方法,查看ProxyChain代码可知,当我们拿到一个目标类代理链时(即如下代码中的proxyList对象),判断代理链中的切面是否全部执行完毕,如果没有执行完毕,则执行切面中的doProxy()方法。

public Object doProxyChain() throws Throwable {
      Object methodResult;
      if(proxyIndex < proxyList.size()) {
          //调用代理方法
          methodResult = proxyList.get(proxyIndex++).doProxy(this);
      } else {
          //调用原方法
          methodResult = methodProxy.invokeSuper(targetObject,methodParams);
      }
      return methodResult;
  }
  1. 查看切面类中的doProxy()方法,发现不管有没有符合切点定义,都会执行proxyChain.doProxyChain()方法。
if(intercept(targetClass,method,params)) {
              before(targetClass,method,params);
              result = proxyChain.doProxyChain();
              after(targetClass,method,params,result);
          } else {
              result = proxyChain.doProxyChain();
          }

举例子来说,假如我们代理链中有两个切面A和B,首先执行切面A的doProxy(),执行完前置增强(假如没有定义切点,默认对所有方法进行拦截)后,执行proxyChain.doProxyChain()方法,这时代理链中还有一个切面B,继续执行切面B的增强,切面B完成前置之后,仍然调用proxyChain.doProxyChain()方法,这时代理链中所有切面都执行了一遍,开始执行目标类的原方法(即doProxyChain()的else分支),然后依次执行各个切面的后置增强。

总之,执行过程就是等待所有切面的所有前置增强执行完毕之后执行目标类的原方法,原方法执行完毕之后,依次执行切面的后置增强。

框架的初始化

上文为我们介绍了框架是如何进行代理的,简单来说就是拿着一个目标类和其对应的代理链,然后使用CGLib框架为我们提供的方法进行代理。

那么初始化的工作就是返回一个目标类和代理链的映射。

我们将这块工作交个 AOPHelper 来完成。

以下为AOPHelper类的声明:

public final class AopHelper {
    static {
        try {
            Map<Class<?>,Set<Class<?>>> proxyMap = createProxyMap();
            Map<Class<?>,List<Proxy>> targetMap = createTargetMap(proxyMap);
            for(Map.Entry<Class<?>,List<Proxy>> targetEntry : targetMap.entrySet()) {
                Class<?> targetClass = targetEntry.getKey();
                List<Proxy> proxyList = targetEntry.getValue();
                //通过CGLib框架生成代理类
                Object proxy = ProxyManager.createProxy(targetClass,proxyList);
                //使用代理类替换容器中的普通类
                BeanHelper.setBean(targetClass,proxy);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回目标类的集合,根据@Aspect中的注解查找目标类
     * @param aspect
     * @return
     */
    private static Set<Class<?>> createTargetClassSet(Aspect aspect) {
        Set<Class<?>> targetClassSet = new HashSet<>();
        //获取Aspect注解中的值(被代理的类)
        Class<? extends Annotation> annotation = aspect.value();
        if(annotation != null && !annotation.equals(Aspect.class)) {
            //获取被代理的类的集合
            targetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation));
        }
        return targetClassSet;
    }

    /**
     * 返回切面类和目标类的映射关系
     * key->继承自AspectProxy的类(切面),value->目标类集合
     * @return
     */
    private static Map<Class<?>,Set<Class<?>>> createProxyMap() {
        Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<>();
        Set<Class<?>> classSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
        for(Class<?> proxyClass : classSet) {
            if(proxyClass.isAnnotationPresent(Aspect.class)) {
                Aspect aspect = proxyClass.getAnnotation(Aspect.class);
                Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
                //key->切面类,value->切面要切入的类的集合(目标类的集合)
                proxyMap.put(proxyClass,targetClassSet);
            }
        }
        return proxyMap;
    }

    /**
     * 返回目标类和代理类的映射关系,一个目标类可能由多个代理类
     * key->目标类,value->代理类的集合
     * @param proxyMap
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private static Map<Class<?>,List<Proxy>> createTargetMap(Map<Class<?>,Set<Class<?>>> proxyMap) throws IllegalAccessException, InstantiationException {
        Map<Class<?>,List<Proxy>> targetMap = new HashMap<>();
        for(Map.Entry<Class<?>,Set<Class<?>>> proxyEntry : proxyMap.entrySet()) {
            //切面类
            Class<?> proxyClass = proxyEntry.getKey();
            //目标类的集合
            Set<Class<?>> targetClassSet = proxyEntry.getValue();
            for(Class<?> targetClass : targetClassSet) {
                Proxy proxy = (Proxy) proxyClass.newInstance();
                if(targetMap.containsKey(targetClass)) {
                    //targetMap中含有目标类,直接增加proxy至proxyList中
                    targetMap.get(targetClass).add(proxy);
                } else {
                    //targetMap中没有目标类,新建proxyList,并将proxy添加至proxyList中
                    List<Proxy> proxyList = new ArrayList<>();
                    proxyList.add(proxy);
                    targetMap.put(targetClass,proxyList);
                }
            }
        }
        return targetMap;
    }

}

然后将AopHelper设置为随着tomcat的启动而加载即可。
如上代码注解较为详细,这里不再详细讲解。

框架的使用

框架的使用很简单,只需定义我们想要的切面和要代理的目标类即可。如下为自定义切面示例。

//代理所有被@Controller注解标记的类
@Aspect(Controller.class)
public class ControllerAspect extends AspectProxy {
    private static final Logger logger = LoggerFactory.getLogger(ControllerAspect.class);

    private long begin;
    public void before(Class<?> cls, Method method,Object[] params) {
        logger.debug("--------------");
        logger.debug(cls.getName());
        logger.debug(method.getName());
        begin = System.currentTimeMillis();
    }

    public void after(Class<?> cls,Method method,Object[] params,Object result) {
        logger.debug("time : " + (System.currentTimeMillis() - begin));
        logger.debug("-----end------");
    }

    public boolean intercept(Class<?> cls,Method method,Object[] params) {
        //对controller类中的所有被@Action注解标注的方法进行拦截
        if(method.isAnnotationPresent(Action.class)) {
            return true;
        }
        return false;
    }
}

相关文章

  • 使用代理链自己实现AOP框架

    写在前面 今年寒假已经接近尾声,寒假给自己定下的目标--完成一个简单的MVC框架也已经快完成了,在实现一些进阶的功...

  • Spring AOP实现

    使用SpringBoot实现AOP动态代理 1 使用CGLIB实现AOP动态代理 .properties .xml

  • 实现一个简单的基于动态代理的 AOP

    实现一个简单的基于动态代理的 AOP Intro 上次看基于动态代理的 AOP 框架实现,立了一个 Flag, 自...

  • springboot aop

    springboot怎样使用aop呢?我们知道aop的实现一种是jdk动态代理实现aop,一种是cglib动态代理...

  • 细说Spring——AOP详解(使用CGLIB实现AOP)

    一、动态代理实现AOP的缺陷 在上一篇文章细说Spring——AOP详解(动态代理实现AOP)中讲解了如何使用动态...

  • Spring的两种代理方式

    AOP是Spring的重要组成部分,而AOP正是通过代理实现的。如果代理对象实现了接口,则默认使用jdk动态代理,...

  • 2018-03-21

    与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码...

  • Spring揭秘-AOP

    AOP的实现机制Spring AOP默认使用动态代理实现AOP机制,在运行期间为相应的接口生成对应的代理对象,当S...

  • AOP-AspectJ / JDK Proxy / CGLIB

    Spring AOP采用动态代理的方式,在运行期生成代理类来实现AOP,不修改原类的实现 Aspectj 使用编译...

  • Spring Aop、拦截器、过滤器的区别

    Spring AOP Spring AOP,是AOP的一种实现,使用的是代理模式。 Filter Filter(过...

网友评论

    本文标题:使用代理链自己实现AOP框架

    本文链接:https://www.haomeiwen.com/subject/uovrxftx.html