自己动手实现Spring之Spring-Toy重构v0.2

在上一篇文章自己动手实现Spring中,介绍了本人自己实现的一个简单的IOC容器spring-toy。spring-toy的v0.1版本初步实现了IOC容器,但并没有实现AOP 功能。

在v0.2版本中,实现了以下功能:

  1. 支持通过FactoryBean注入单实例到容器中。
  2. 支持AOP。可以通过将目标实例,Advisor或者Advice配置到ProxyFactoryBean实例,并将ProxyFactoryBean实例注入到容器中的方式实现AOP拦截。
  3. 同时支持使用AspectJ的注解,声明Aspect以及通知,实现AOP拦截。

v0.2版本基本实现了SpringAOP的基本功能,在本篇文章中,将介绍笔者是如何实现的AOP功能。

FactoryBean

首先,需要介绍的是FactoryBean,这是一个接口,用来将实例注入到容器中。之所以先介绍这个接口,是因为笔者并没有实现XML的方式声明AOP切面以及切点的功能,只能通过注入Bean的方式实现AOP。以下是FactoryBean的定义:

/**
 * 用于直接将Bean注入到容器中
 *
 * @author bdq
 * @since 2019-08-01
 */
public interface FactoryBean<T> {

    /**
     * 返回对象实例
     *
     * @return T 对象实例
     * @throws BeansException bean异常
     */
    T getObject() throws BeansException;


    /**
     * 返回Bean的类型
     *
     * @return Class<?> Bean的类型
     */
    Class<?> getObjectType();

    /**
     * 是否单例
     *
     * @return boolean
     */
    default boolean isSingleton() {
        return true;
    }
}

将Bean注入到容器中只需要通过调用ApplicationContext的registerSingleBean(FactoryBean factoryBean)方法,将实现了该接口的类传入即可。

ApplicationContext将会分析注入的Bean,将BeanDefinition以及factoryBean一起注入到BeanFactory中。代码如下:

public void registerSingleBean(FactoryBean factoryBean) throws BeansException {
        String beanName = this.beanNameGenerator.generateBeanName(factoryBean.getObjectType());
        Class<?> objectType = factoryBean.getObjectType();
        BeanDefinition beanDefinition = new BeanDefinition(objectType, ScopeType.SINGLETON, beanName, false);
        beanFactory.registerBeanDefinition(beanName, beanDefinition);
        beanFactory.registerSingleBean(beanName, factoryBean);
}

其中,registerBeanDefinition()registerSingleBean()代码实现如下:

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeansException {
        if (beanDefinitions.containsKey(beanName)) {
            throw new ConflictedBeanException(String.format("the entity named %s has conflicted ! ", beanName));
        }
        beanDefinitions.put(beanName, beanDefinition);
}

@Override
public void registerSingleBean(String beanName, FactoryBean factoryBean) throws BeansException {
        instances.put(beanName, factoryBean);
}

将factoryBean注入到instances容器之后,可以通过getBean()方法获取Bean实例,关键代码如下:

Object instance = instances.get(beanName);

if (instance instanceof FactoryBean) {

  try {
    FactoryBean factoryBean = (FactoryBean) instance;
    return factoryBean.getObject();
  } catch (Exception e) {
    throw new BeansException(e);
  }

}

return instance;

在了解了FactoryBean机制之后,再来介绍一下AOP具体实现。

Advisor和Advice

在Spring中,通过配置Advisor和Advice声明一个切面,从而实现AOP功能。笔者参考这个机制,实现了一个简单版本。首先看一下aop包下的定义的类:

image

其中Advice的定义,与Spring相同,就不做过多介绍。Advisor接口的定义如下:

/**
 * 顾问接口,通知接口的增强,可以实现更复杂的通知
 *
 * @author bdq
 * @since 2019-07-29
 */
public interface Advisor extends Advice {
    /**
     * 设置切点
     *
     * @param pointcut 切点表达式
     */
    void setPointcut(String pointcut);

    /**
     * 获取切点表达式
     *
     * @return String
     */
    String getPointcut();

    /**
     * 设置通知
     *
     * @param advice 通知
     */
    void setAdvice(Advice advice);

    /**
     * 获取通知
     *
     * @return Advice
     */
    Advice getAdvice();

    /**
     * 代理方法是否匹配通知
     *
     * @param method     代理方法
     * @param adviceType 通知类型
     * @return boolean
     */
    boolean isMatch(Method method, Class<?> adviceType);
}

Advisor定义了切点,以及切点对应的通知,同时定义了匹配方法,方便进行通知匹配。AbstractAdvisor是Advisor的抽象实现,主要实现了get以及set方法。

Advisor最终实现类之一为RegexpMethodAdvisor,RegexpMethodAdvisor表示通过正则表达式来定义切面,实现通知方法的匹配,主要是实现了isMatch()方法。RegexpMethodAdvisor代码如下:

@Override
public boolean isMatch(Method method, Class<?> adviceType) {
  MethodSignature methodSignature = new MethodSignature(adviceType, method);
  String fullyMethodName = methodSignature.toLongString();
  return adviceType.isAssignableFrom(getAdvice().getClass()) && fullyMethodName.matches(getPointcut());
}

其中MethodSignature是方法签名类,方便获取方法签名,在此不做过多介绍。

MethodInvocation是切点实现类,封装了切点信息,通过调用MethodInvocation的proceed()方法,执行前置通知和具体的代理方法。代码如下:

@Override
public Object proceed() throws Throwable {
  for (MethodBeforeAdvice methodBeforeAdvice : beforeAdvices) {
    if (methodBeforeAdvice instanceof AspectAdvice) {
      AspectAdvice aspectAdvice = (AspectAdvice) methodBeforeAdvice;
      aspectAdvice.setJoinPoint(this);
    }
    methodBeforeAdvice.before(method, args, target);
  }
  return method.invoke(target, args);
}

代理增强

在定义了相关的Advisor和Advice之后,需要在代理中获取,并按照通知类型按顺序调用。这部分的功能,主要定义在AdvisorInvocationHandler接口中,代码如下:

/**
 * 用于处理通知的执行
 *
 * @author bdq
 * @since 2019-07-31
 */
public interface AdvisorInvocationHandler {
    /**
     * 设置advisors
     *
     * @param advisors 所有顾问
     */
    void setAdvisors(List<Advisor> advisors);

    /**
     * 执行代理方法以及通知方法
     *
     * @param target 代理实例
     * @param method 代理方法
     * @param args   代理方法参数
     * @return Object 执行结果
     * @throws Throwable 异常
     */
    Object invokeWithAdvice(Object target, Method method, Object[] args) throws Throwable;
}

其实现类AdvisorInvocationHandlerImpl实现了invokeWithAdvice()用于执行通知方法逻辑,代码如下:

@Override
public Object invokeWithAdvice(Object target, Method method, Object[] args) throws Throwable {
  MethodSignature methodSignature = new MethodSignature(target.getClass(), method);

  MethodBeforeAdvice[] beforeAdvices = getMethodBeforeAdvices(method, methodSignature);

  MethodInvocation invocation = new MethodInvocation(target, method, args, beforeAdvices);

  MethodInterceptor[] aroundAdvices = getAroundAdvices(method, methodSignature);

  if (aroundAdvices.length > 0) {
    //执行环绕通知
    for (MethodInterceptor aroundAdvice : aroundAdvices) {
      try {
        Object returnValue = doAround(aroundAdvice, invocation);
        doAfterReturning(invocation, returnValue);
        return returnValue;
      } catch (Exception e) {
        doThrows(invocation, e);
      }
    }
  } else {
    try {
      Object returnValue = invocation.proceed();
      doAfterReturning(invocation, returnValue);
      return returnValue;
    } catch (Exception e) {
      doThrows(invocation, e);
    }
  }
  return null;
}

从代码中可以看出,首先执行环绕通知,然后通过MethodInvocation的proceed()方法调用前置通知以及具体的代理方法,最后依次调用后置通知和异常通知。

动态代理

动态代理主要有两种方式,一种是通过jdk进行代理,另一种是通过cglib进行代理,具体可以参考proxy包下的JdkInvocationHandler和CglibMethodInterceptor。通过在动态代理回调中,调用AdvisorInvocationHandler的invokeWithAdvice()方法执行增强通知,由此实现了AOP功能。以JdkInvocationHandler为例,主要代码如下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  Object targetObject = getTargetObject();
  Object result = invokeObjectMethod(targetObject, method, args);
  if (result == null) {
    result = advisorInvocationHandler.invokeWithAdvice(targetObject, method, args);
  }
  return result;
}

代理工厂

ProxyFactory是用来生成代理实例的类,其封装了生成代理实例的方法,以及将通知注入到AdvisorInvocationHandler,并传入JdkInvocationHandler或者CglibMethodInterceptor中。主要代码如下:

/**
 * 获取代理实例
 *
 * @return Object 代理实例
 * @throws BeansException bean异常
 */
public Object getProxy() throws BeansException {

  AdvisorInvocationHandler advisorInvocationHandler = getAdviceInvocationHandler();

  return createProxyInstance(advisorInvocationHandler);
}

private Object createProxyInstance(AdvisorInvocationHandler advisorInvocationHandler) throws BeansException {
  ProxyInvocationHandler invocationHandler;
  //实例化代理生成类
  if (interfaces != null && interfaces.length > 0) {
    invocationHandler = new JdkInvocationHandler(advisorInvocationHandler);
  } else {
    invocationHandler = new CglibMethodInterceptor(advisorInvocationHandler);
  }

  invocationHandler.setTarget(target);
  invocationHandler.setInterfaces(interfaces);

  return invocationHandler.newProxyInstance();
}

private AdvisorInvocationHandler getAdviceInvocationHandler() {
  AdvisorInvocationHandler advisorInvocationHandler = new AdvisorInvocationHandlerImpl();
  if (advisors.size() > 0) {
    advisorInvocationHandler.setAdvisors(advisors);
  }
  if (beanFactory != null) {
    //从BeanFactory获取Aspect通知
    AdvisorBeanFactoryImpl advisorBeanFactoryImpl = (AdvisorBeanFactoryImpl) beanFactory;
    advisorInvocationHandler.setAdvisors(advisorBeanFactoryImpl.getAdvisors());
  }
  return advisorInvocationHandler;
}

最后,通过注入ProxyFactoryBean到容器中,实现AOP功能。ProxyFactoryBean是一个FactoryBean实现,其功能是声明目标类以及需要的增强通知。代码如下:

/**
 * FactoryBean,代理实例直接注入到BeanFactory
 *
 * @author bdq
 * @since 2019-07-30
 */
public class ProxyFactoryBean implements FactoryBean<Object> {
    /**
     * 目标实例
     */
    private Object target;
    /**
     * 代理类型
     */
    private Class<?>[] interfaces;
    /**
     * 代理工厂
     */
    private ProxyFactory proxyFactory;


    public ProxyFactoryBean() {
        proxyFactory = new ProxyFactory();
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setInterfaces(Class<?>... proxyInterfaces) {
        this.interfaces = proxyInterfaces;
    }

    @Override
    public Object getObject() throws BeansException {
        proxyFactory.setTarget(target);
        proxyFactory.setInterfaces(target.getClass().getInterfaces());
        if (interfaces != null) {
            if (!(interfaces.length == 1 && interfaces[0] == target.getClass())) {
                proxyFactory.setInterfaces(interfaces);
            }
        }
        return proxyFactory.getProxy();
    }

    @Override
    public Class<?> getObjectType() {
        return target.getClass();
    }

    /**
     * 添加通知
     *
     * @param advice 通知
     */
    public void addAdvice(Advice advice) {
        proxyFactory.addAdvice(advice);
    }

}

使用示例如下:

@Test
public void testProxyFactoryBean() throws ApplicationContextException {
  ApplicationContext applicationContext = new AnnotationApplicationContext("test.cn.bdqfork.ioc.factorybean");
  ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
  proxyFactoryBean.setInterfaces(UserDao.class);
  proxyFactoryBean.setTarget(new UserDao());
  proxyFactoryBean.addAdvice(new Before());
  applicationContext.registerSingleBean(proxyFactoryBean);
  UserDao userDao = applicationContext.getBean(UserDao.class);
  userDao.test();
}

Aspect注解

通过ProxyFactoryBean,只能实现外部实例的增强,且需要大量的手动注入,十分的不方便。因此,笔者参考Spring,引入Aspect注解,实现了通过注解的方式配置AOP。Aspect注解的支持,本质上是对Advice和Advisor的封装,核心实现类如下:

image

代码比较简单,就不过多介绍了。然后是对Aspect注解的解析,这部分代码可以查看AspectResolver类的resolve()方法,解析Aspect注解修饰的类,并生成AspectAdvisor以及AspectAdvice,并注入到BeanFactory中,ProxyFactory将会从BeanFactory中获取相关的Advisor生成代理实例。

Aspect注解方式的使用方法如下:

/**
 * @author bdq
 * @since 2019-07-28
 */
@Scope(ScopeType.PROTOTYPE)
@Component
@Aspect
public class Log {
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("执行前置通知方法");
    }

    @AfterReturning(value = "pointcut()", returning = "result")
    public void after(JoinPoint joinPoint, Object result) {
        System.out.println("执行后置通知方法,return : " + result);
    }

    @Around("execution(test.cn.bdqfork.ioc.aop.*)")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("执行环绕通知方法,目标方法执行之前");
        Object result = pjp.proceed();
        System.out.println("执行环绕通知方法,目标方法执行之后");
        if (result != null) {                                //可以修改目标方法的返回结果
            result = ((String) result).toUpperCase();
        }
        return result;
    }

    @AfterThrowing(value = "pointcut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("执行异常抛出通知方法,Exception : " + ex);
    }

    @Pointcut("execution(test.cn.bdqfork.ioc.aop.*)")
    public void pointcut() {

    }

}

/**
 * @author bdq
 * @since 2019-07-30
 */
public class TestProxyFactory {

    @Test
    public void testAspect() throws ApplicationContextException {
        ApplicationContext applicationContext = new AnnotationApplicationContext("test.cn.bdqfork.ioc.aop");
        UserDaoImpl userDao = applicationContext.getBean(UserDaoImpl.class);
        userDao.testAop();
        System.out.println("----------------------------------------");
        userDao.testThrowing();
    }
}

以上是笔者实现AOP的思路,具体细节,限于篇幅原因,没有一一介绍,可以通过查看源码进行了解。笔者的代码已经上传到Github中,点击 spring-toy 查看。笔者技术水平有限,如果有问题,请联系笔者,感谢大家匹配指正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容