转载

自己动手实现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包下的定义的类:

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

其中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的封装,核心实现类如下:

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

代码比较简单,就不过多介绍了。然后是对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 查看。笔者技术水平有限,如果有问题,请联系笔者,感谢大家匹配指正。

原文  http://www.bdqfork.cn/post/82946b03.html
正文到此结束
Loading...