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

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

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

坚持原创技术分享,您的支持将鼓励我继续创作!
  • 本文作者:bdqfork
  • 本文链接:/articles/40
  • 版权声明:本博客所有文章除特别声明外,均采用BY-NC-SA 许可协议。转载请注明出处!
表情 |预览
快来做第一个评论的人吧~