美文网首页Java 杂谈
自己动手实现Spring之Spring-Toy重构v0.2

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

作者: bdqfork | 来源:发表于2019-08-04 10:58 被阅读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 查看。笔者技术水平有限,如果有问题,请联系笔者,感谢大家匹配指正。

    相关文章

      网友评论

        本文标题:自己动手实现Spring之Spring-Toy重构v0.2

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