Spring AOP作为Spring中两大最要特性跟IOC一样重要,看了很多书籍,都没有把这个东西的来龙去脉讲清楚,网上很多文章标题也都是一知半解,甚至很多直接就切入到动态代理这块来讲。本文旨在从源码级别来分析Spring AOP的设计过程,用一个例子来从头到尾分析Spring AOP实现过程。
我们先上一个简单的例子:
public interface ShopService { public String getShopName(); } public class ShopServiceImpl implements ShopService{ public String getShopName() { System.out.println("nike shop"); return "Nike"; } } public class TimeHandler { public void printTime() { System.out.println("当前时间:" + System.currentTimeMillis()); } }
XML配置:
<bean id="shopService" class="com.dianping.aop.ShopServiceImpl"/> <bean id="timeHandler" class="com.dianping.aop.TimeHandler"/> <aop:config proxy-target-class="true"> <aop:aspect id="time" ref="timeHandler"> <aop:pointcut id="point" expression="execution(* com.dianping.aop.ShopService.*(..))"/> <aop:before method="printTime" pointcut-ref="point"/> <aop:after method="printTime" pointcut-ref="point"/> </aop:aspect> </aop:config>
测试类:
public class Main { public static void main(String[] args) { BeanFactory beanFactory = new FileSystemXmlApplicationContext("classpath:appcontext.xml"); ShopService shopService = (ShopService) beanFactory.getBean("shopService"); shopService.getShopName(); } }
输出:
当前时间:1514042119262
nike shop
当前时间:1514042119283
可以看见在getShopName()方法执行前后都打印了当前时间,也就是执行了TimeHandler中的printTime()方法。
这是一个简单的AOP使用,下面将从这个例子触发解析Spring AOP设计实现。
可以发现打印时间是在getBean()方法之后执行的,说明AOP被Spring 容器处理过了,那么只有两个地方处理:
1.在Spring IOC容器初始化过程中处理。
2.在获取bean的过程中被处理。
前几篇文章分析过Spring IOC的初始化过程,在Spring IOC出事化过程中会调用XmlBeanDefinitionReader对XML文件中的Bean定义进行读取获取到Dom文档后进行解析,最终的解析BeanDefinition在DefaultBeanDefinitionDocumentReader的下述方法中。(本文源码均基于Spring 4.3.9)
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
在Spring IOC容器初始化过程中调用的是这句代码parseDefaultElement(ele, delegate),因为我们的bean都是通过<bean id="">声明的,这属于Spring默认命名,从上面配置Spring AOP XML中可以看出AOP的配置为<aop:>,这种配置不属于Spring默认命名,故不会执行parseDefaultElement(ele, delegate),而会执行delegate.parseCustomElement(ele),这句代码是解析自定义命名bean定义,<aop:>就属于自定义命名。
进一步进去:
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //获取命名空间并根据命名空间获取相应的处理 String namespaceUri = getNamespaceURI(ele); NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }