Spring中AOP的实现主要分为两个部分,一是将在xml中定义的切面信息解析并且注册到BeanFactory中,二是根据切面的相关的信息生成动态代理,实现在运行时对相关对象行为的修改。本篇博客主要介绍Spring是如何将在xml中定义的切面信息解析出来并注册到BeanFactory中的。
本次测试的xml配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<bean id="timeHandler1" class="com.aop.TimeHandler"/>
<bean id="helloWorld" class="com.pointcut.HelloWorld"/>
<bean id="before_advisor" class="com.aop.BeforeAdvisor"/>
<aop:config>
<aop:aspect id="time" ref="timeHandler1">
<aop:pointcut id="addAllMethod" expression="execution(* com.pointcut.HelloWorld.printHelloWorld(..))"/>
<aop:before method="printTimeStamp" pointcut-ref="addAllMethod"/>
<aop:after method="printTimeStamp" pointcut-ref="addAllMethod"/>
</aop:aspect>
</aop:config>
</beans>
直接从开始解析的代码出进行分析:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
RuntimeException runtimeException = new RuntimeException("test");
logger.info("",runtimeException);
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);
}
}
由于在解析aop的时候,这种Element不是namespace中的,因此会执行parseCustomElement(ele);方法。
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
logger.info("namespaceUri = "+namespaceUri);
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));
}
这个会根据aop的NamespaceUri来获取对应的NamespaceHandler,其中部分NamespaceHandler与NamespaceUri对应关系如下:
http://www.springframework.org/schema/aop-----org.springframework.aop.config.AopNamespaceHandler
http://www.springframework.org/schema/task-----org.springframework.scheduling.config.TaskNamespaceHandler
http://www.springframework.org/schema/lang-----org.springframework.scripting.config.LangNamespaceHandler
http://www.springframework.org/schema/c-----org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http://www.springframework.org/schema/jee-----org.springframework.ejb.config.JeeNamespaceHandler
http://www.springframework.org/schema/cache-----org.springframework.cache.config.CacheNamespaceHandler
http://www.springframework.org/schema/jdbc-----org.springframework.jdbc.config.JdbcNamespaceHandler
http://www.springframework.org/schema/p-----org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http://www.springframework.org/schema/util-----org.springframework.beans.factory.xml.UtilNamespaceHandler
http://www.springframework.org/schema/tx-----org.springframework.transaction.config.TxNamespaceHandler
http://www.springframework.org/schema/context-----org.springframework.context.config.ContextNamespaceHandler
接下来将会执行Handler的parser方法,在AopNamespaceHandler中parser方法没有实现,程序调用的是其父类(NamespaceHandlerSupport)的方法
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
//获取的parser为ConfigBeanDefinitionParser对象
return (parser != null ? parser.parse(element, parserContext) : null);
}
根据不同的Element查询不同的Parser,在解析的时候首先解析的为config,根据AopNamespaceHandler初始化的结果,根据config获取的Parser为ConfigBeanDefinitionParser,使用ConfigBeanDefinitionParser解析config元素。
ConfigBeanDefinitionParser中的parser方法:
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
configureAutoProxyCreator(parserContext, element);
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
String localName = parserContext.getDelegate().getLocalName(elt);
if (POINTCUT.equals(localName)) {
parsePointcut(elt, parserContext);
}
else if (ADVISOR.equals(localName)) {
parseAdvisor(elt, parserContext);
}
else if (ASPECT.equals(localName)) {
parseAspect(elt, parserContext);
}
}
parserContext.popAndRegisterContainingComponent();
return null;
}
这里有一个入栈和出栈的操作,之后在解析<aop:config>中每一个子元素是都会出现这种操作,这种操作的目的一方面是因为元素的解析具有一定的顺序要求,另一方是为了绑定元素之间的关系,具体的来说就是切面与切点之间的关系。
首先解析<aop:aspect>标签:
最终是实现向BeanFactory中注册一个BeanDefinition的操作
private void parseAspect(Element aspectElement, ParserContext parserContext) {
String aspectId = aspectElement.getAttribute(ID);//根据配置文件id="time"
String aspectName = aspectElement.getAttribute(REF);//根据配置文件ref="timeHandler1"
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<>();
List<BeanReference> beanReferences = new ArrayList<>();
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);//根据declare-parent解析出来的bean信息
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
Element declareParentsElement = declareParents.get(i);
beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
}
// We have to parse "advice" and all the advice kinds in one loop, to get the
// ordering semantics right.
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (isAdviceNode(node, parserContext)) {
if (!adviceFoundAlready) {
adviceFoundAlready = true;
beanReferences.add(new RuntimeBeanReference(aspectName));
}
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);//解析<aop:after>等指明切点和切入时机的标签
}
}
//创建一个切面组件的定义,主要是根据前面解析封装好的通知器信息创建一个切面的信息
AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
parserContext.pushContainingComponent(aspectComponentDefinition);
//解析出这个切面的左右的切点
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
//绑定切入点与切入面直接的关系
parserContext.popAndRegisterContainingComponent();
}
finally {
this.parseState.pop();
}
}
首先在测试的配置文件中并没有使用declare-parent标签,关于解析declare-parent标签部分就不分析了。
先看一下如何解析出一个切面的所有通知器,代码如下:
private AbstractBeanDefinition parseAdvice(
String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
try {
this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));
// create the method factory bean
RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
methodDefinition.getPropertyValues().add("targetBeanName", aspectName);//设置切面bean的名称
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));//获取切面方法
methodDefinition.setSynthetic(true);
// create instance factory definition
//这个工厂主要用来辅助生成通知
//工厂类为:SimpleBeanFactoryAwareAspectInstanceFactory
RootBeanDefinition aspectFactoryDef =
new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
aspectFactoryDef.setSynthetic(true);
// register the pointcut
//创建一个通知
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
beanDefinitions, beanReferences);
// configure the advisor
//根据创建的通知,创建一个通知器
RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
}
// register the final advisor
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
return advisorDefinition;
}
finally {
this.parseState.pop();
}
}
最终会将每个切入点通知器封装成一个RootBeanDefinition,其内部包含AspectJPointcutAdvisor类相关属性信息。生成advisorDefinition 的时候需要知道这个切入点通知器所关注的切入点信息,这个信息有adviceDef 提供,在createAdviceDefinition方法将会对pointcut和pointcut-ref属性进行解析,以获取有效的切入点信息。
方法如下:
开始创建一个通知:
private AbstractBeanDefinition createAdviceDefinition(
Element adviceElement, ParserContext parserContext, String aspectName, int order,
RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
//根据Element获取具体的通知实现类
RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
adviceDefinition.setSource(parserContext.extractSource(adviceElement));
//将标签中解析出来的内容填充到新建的adviceDefinition对象中
adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);
if (adviceElement.hasAttribute(RETURNING)) {
adviceDefinition.getPropertyValues().add(
RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));
}
if (adviceElement.hasAttribute(THROWING)) {
adviceDefinition.getPropertyValues().add(
THROWING_PROPERTY, adviceElement.getAttribute(THROWING));
}
if (adviceElement.hasAttribute(ARG_NAMES)) {
adviceDefinition.getPropertyValues().add(
ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));
}
ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
//解析出当前通知器关注的点,关注点分为:pointcut和pointcut-ref.
Object pointcut = parsePointcutProperty(adviceElement, parserContext);
if (pointcut instanceof BeanDefinition) {
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
beanDefinitions.add((BeanDefinition) pointcut);
}
else if (pointcut instanceof String) {
RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
beanReferences.add(pointcutRef);
}
cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);
return adviceDefinition;
}
在将所有的通知器解析出来之后,开始生成切面对象。关于切面的对象,只是简单的使用相关的信息实例化一个对象:
private AspectComponentDefinition createAspectComponentDefinition(
Element aspectElement, String aspectId, List<BeanDefinition> beanDefs,
List<BeanReference> beanRefs, ParserContext parserContext) {
BeanDefinition[] beanDefArray = beanDefs.toArray(new BeanDefinition[0]);
//这里的beanDefArray每个元素都是一个切入点通知器,可以通过打印元素数量判断分析的是否正确,以本次测试的xml为例,beanDefArray的长度为2
BeanReference[] beanRefArray = beanRefs.toArray(new BeanReference[0]);
Object source = parserContext.extractSource(aspectElement);
return new AspectComponentDefinition(aspectId, beanDefArray, beanRefArray, source);
}
下面开始解析切入点标签<aop:pointcut>,代码如下:
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
String id = pointcutElement.getAttribute(ID);
String expression = pointcutElement.getAttribute(EXPRESSION);
AbstractBeanDefinition pointcutDefinition = null;
try {
this.parseState.push(new PointcutEntry(id));
pointcutDefinition = createPointcutDefinition(expression);
pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
String pointcutBeanName = id;
if (StringUtils.hasText(pointcutBeanName)) {
parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
}
else {
pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
}
parserContext.registerComponent(
new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
}
finally {
this.parseState.pop();
}
return pointcutDefinition;
}
最终构建一个pointcutDefinition,并将pointcutDefinition 添加到parserContext的componentContian中,实际上就是绑定切入点与切面之间的关系。
在解析aspect或者config元素后,执行了parserContext.popAndRegisterContainingComponent方法,这个方法主要是触发一个事件监听,告诉监听器,那个切面已经解析完成了。但是在代码中并没有使用代码设置那个事件监听器,所以并没有什么问,具体的不是怎么清楚,可能是分析的还不够仔细吧
总结一下:
1、通过AOP的namespaceUri获取对应的NameSpaceHandler。
2、通过在Handler初始化时生成的Parser去解析对应的标签。
3、使用ConfigBeanDefinitionParser解析<aop:config>标签
4、将aspect标签的内容解析封装成AspectComponentDefinition,对应的class为:
5、将<aop:after>等标签封装成RootBeanDefinition,对应的class为:AspectJAfterAdvice,RootBeanDefinition也内部也包含了这个方法执行的切入点。
6、将<aop:pointcut>解析为一个以以id为name的AspectJExpressionPointcut对象属性信息并将相关切面的信息都注册到BeanFactory中。以便在后期生成动态代理时使用。
以上都是自己分析源码后所得,博客中难免会存在错误的地方,如有发现还请告诉我一下,谢谢。