1.引子
通過前面幾篇的學(xué)習(xí)藐鹤,我們已經(jīng)知道了aop實(shí)現(xiàn)的基本原理,但是定義一個切面的過程是比較繁瑣的杂靶,我們需要自己是實(shí)現(xiàn)特定的接口持搜。事實(shí)上,Spring提供了更加簡單的方式來定義切面辞居,比如說使用@AspectJ注解或者基于Schema配置的方式楷怒。今天我們就基于Schema的方式,看一下這種方式下aop的配置時(shí)如何被加載的瓦灶。話不多說鸠删,先上一個例子看看如何基于Schema配置一個切面。
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="adviceMethods" class="com.youzan.shys.advisor.AdviceMethods" />
<bean id="waiterTarget" class="com.youzan.shys.advisor.NaiveWaiter" />
<aop:config proxy-target-class="true">
<aop:aspect ref="adviceMethods">
<aop:before method="preGreeting" pointcut="target(com.youzan.shys.advisor.NaiveWaiter) and execution(* greetTo(..))" />
</aop:aspect>
</aop:config>
</beans>
在這個例子中贼陶,我們通過切點(diǎn)表達(dá)式刃泡,對NaiveWaiter類的greetTo()方法添加了一個前置增強(qiáng),具體增強(qiáng)為AdviceMethods類的preGreeting()方法碉怔。接下來我們主要就來看看這些配置是如何被加載的烘贴。
2.配置文件讀取流程
Location ——> ResourceLoader.getResource
Resource ——> DocumentLoader.loadDocument
Document/Element ——> BeanDefinitonParser
BeanDefinition
2.1獲取命名空間
熟悉Spring IOC的朋友都知道,Spring從配置文件中讀取bean配置并將其轉(zhuǎn)換成BeanDefinition大致上需要經(jīng)過以上幾個步驟撮胧,本文重點(diǎn)關(guān)注最后一步桨踪,即Spring是如何將Element轉(zhuǎn)化為BeanDefinition的,代碼直接定位到DefaultBeanDefinitionDocumentReader#parseBeanDefinitions:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 是否為默認(rèn)的命名空間趴樱,即判斷root的命名空間uri是否為空或者等于http://www.springframework.org/schema/beans
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)) {
// 默認(rèn)命名空間馒闷,比如<bean id="xxx" />
parseDefaultElement(ele, delegate);
}
else {
// 非默認(rèn)命名空間酪捡,比如<aop:config />
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在解析bean定義的時(shí)候,根據(jù)命名空間分成了兩個分支纳账。顯然逛薇,由于在我們的例子中不是默認(rèn)命名空間,因此會進(jìn)入第二個分支疏虫,進(jìn)去看一看:
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 獲取命名空間uri永罚,即在xml頭部中配置的這一串內(nèi)容xmlns:aop="http://www.springframework.org/schema/aop"
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 這里比較簡單,就是根據(jù)namespaceUri實(shí)例化對應(yīng)的namespaceHandler卧秘,在這里就是AopNamespaceHandler
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));
}
在解析自定義元素的時(shí)候呢袱,首先獲取命名空間uri,根據(jù)uri獲取對應(yīng)的hander翅敌,然后再進(jìn)行解析羞福。AopNamespaceHandler中針對每一個標(biāo)簽,都有一個具體的解析器來負(fù)責(zé)解析蚯涮。
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
2.2 解析具體標(biāo)簽
可以看到治专,AopNamespace中一共只有4個標(biāo)簽,在我們的例子中遭顶,首先就是config標(biāo)簽张峰,于是定位到ConfigBeanDefinitionParser#parse:
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 注冊一個名為org.springframework.aop.config.internalAutoProxyCreator的bean,默認(rèn)實(shí)現(xiàn)為AspectJAwareAdvisorAutoProxyCreator
// 同時(shí)根據(jù)配置設(shè)置一些代理屬性棒旗,比如proxy-target-class和expose-proxy
configureAutoProxyCreator(parserContext, element);
// 獲取config的子標(biāo)簽喘批,并分別進(jìn)行解析
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;
}
config一共只有3種類型的子標(biāo)簽,分別為advisor铣揉、pointcut和aspect饶深。在我們的例子中,首先解析的應(yīng)該就是aspect老速,于是進(jìn)入parseAspect方法粥喜,該方法主要做了兩件事:
- 加載增強(qiáng)節(jié)點(diǎn)bean定義(6種增強(qiáng)凸主,包括引介增強(qiáng))
- 獲取aspect的所有子標(biāo)簽并進(jìn)行解析
private void parseAspect(Element aspectElement, ParserContext parserContext) {
String aspectId = aspectElement.getAttribute(ID);
String aspectName = aspectElement.getAttribute(REF);
try {
// ParseState是一個基于LinkedList的結(jié)構(gòu)橘券,每個LinkedList里存放的是Entry類型的對象
// 在解析bean的過程中,每次操作開始時(shí)將一個Entry入棧卿吐,每次操作結(jié)束將Entry出棧
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<>();
List<BeanReference> beanReferences = new ArrayList<>();
// 如果配置了引介增強(qiáng)旁舰,獲取其bean定義
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
Element declareParentsElement = declareParents.get(i);
beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
}
// 獲取aspect的所有子標(biāo)簽并進(jìn)行解析
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// 如果是增強(qiáng)節(jié)點(diǎn),將aspect節(jié)點(diǎn)對應(yīng)的bean引用加入beanReferences列表
if (isAdviceNode(node, parserContext)) {
if (!adviceFoundAlready) {
adviceFoundAlready = true;
if (!StringUtils.hasText(aspectName)) {
parserContext.getReaderContext().error(
"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
aspectElement, this.parseState.snapshot());
return;
}
beanReferences.add(new RuntimeBeanReference(aspectName));
}
// 解析增強(qiáng)節(jié)點(diǎn)
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);
}
}
AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
parserContext.pushContainingComponent(aspectComponentDefinition);
// aspect節(jié)點(diǎn)下可以配置pointcut屬性嗡官,這里獲取所有的切點(diǎn)并進(jìn)行解析
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
parserContext.popAndRegisterContainingComponent();
}
finally {
this.parseState.pop();
}
}
2.3 advice標(biāo)簽解析
下面分別看下這里涉及到的幾個主要方法箭窜,也就是isAdviceNode、parseAdvice和parsePointcut衍腥。
isAdviceNode方法很簡單磺樱,就是用來判斷該節(jié)點(diǎn)是否是5中增強(qiáng)節(jié)點(diǎn)之一:
private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
if (!(aNode instanceof Element)) {
return false;
}
else {
String name = parserContext.getDelegate().getLocalName(aNode);
return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
}
}
parseAdvice方法邏輯其實(shí)也很簡單:先根據(jù)增強(qiáng)類型生成bean定義纳猫,然后將增強(qiáng)封裝成切面,最后將切面注冊到beanFactory中竹捉。
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);
methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
methodDefinition.setSynthetic(true);
// create instance factory definition
RootBeanDefinition aspectFactoryDef =
new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
aspectFactoryDef.setSynthetic(true);
// 根據(jù)增強(qiáng)類型生成bean定義
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
beanDefinitions, beanReferences);
// 將advice封裝成advisor芜辕,同時(shí)設(shè)置order屬性
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));
}
// 將advisor注冊到DefaultListableBeanFactory中
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
return advisorDefinition;
}
finally {
this.parseState.pop();
}
}
2.3.1 生成增強(qiáng)的bean定義
根據(jù)增強(qiáng)類型生成bean定義主要包含兩部分內(nèi)容:
1.根據(jù)增強(qiáng)類型如<aop:before />選擇對應(yīng)的增強(qiáng)類對象,這部分在getAdviceClass中完成块差;
2.解析<aop:before />標(biāo)簽中的pointcut屬性侵续,這部分在parsePointcutProperty中完成。
private AbstractBeanDefinition createAdviceDefinition(
Element adviceElement, ParserContext parserContext, String aspectName, int order,
RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
// 根據(jù)增強(qiáng)類型生成bean定義
RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
adviceDefinition.setSource(parserContext.extractSource(adviceElement));
......中間是一堆屬性設(shè)置憨闰,代碼省略......
// 解析pointcut屬性
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;
}
getAdviceClass代碼如下状蜗,一共5種增強(qiáng)類型,與前面isAdviceNode方法一致
private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {
String elementName = parserContext.getDelegate().getLocalName(adviceElement);
if (BEFORE.equals(elementName)) {
return AspectJMethodBeforeAdvice.class;
}
else if (AFTER.equals(elementName)) {
return AspectJAfterAdvice.class;
}
else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
return AspectJAfterReturningAdvice.class;
}
else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
return AspectJAfterThrowingAdvice.class;
}
else if (AROUND.equals(elementName)) {
return AspectJAroundAdvice.class;
}
else {
throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
}
}
parsePointcutProperty方法如下鹉动,從這里可以看出轧坎,在aspect標(biāo)簽中,必須且只能設(shè)置pointcut泽示、pointcut-ref中的一個屬性眶根。
private Object parsePointcutProperty(Element element, ParserContext parserContext) {
// 不能同時(shí)設(shè)置pointcut屬性和pointcut-ref屬性
if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) {
parserContext.getReaderContext().error(
"Cannot define both 'pointcut' and 'pointcut-ref' on <advisor> tag.",
element, this.parseState.snapshot());
return null;
}
else if (element.hasAttribute(POINTCUT)) {
// Create a pointcut for the anonymous pc and register it.
String expression = element.getAttribute(POINTCUT);
AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
pointcutDefinition.setSource(parserContext.extractSource(element));
return pointcutDefinition;
}
else if (element.hasAttribute(POINTCUT_REF)) {
String pointcutRef = element.getAttribute(POINTCUT_REF);
if (!StringUtils.hasText(pointcutRef)) {
parserContext.getReaderContext().error(
"'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot());
return null;
}
return pointcutRef;
}
// pointcut屬性和pointcut-ref屬性必須設(shè)置其中一個
else {
parserContext.getReaderContext().error(
"Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
element, this.parseState.snapshot());
return null;
}
}
2.3.2 將增強(qiáng)bean定義封裝成切面bean定義
這一步比較簡單,只是把a(bǔ)dviceDef用AspectJPointcutAdvisor類型重新包裝了一下边琉,同時(shí)設(shè)置了order屬性
2.3.3 將切面bean注冊到BeanFactory中
這就是parseAdvice()最后一步的工作
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
// 獲取bean注冊的名字
String generatedName = generateBeanName(beanDefinition);
// 注冊bean定義
getRegistry().registerBeanDefinition(generatedName, beanDefinition);
return generatedName;
}
獲取bean名稱時(shí)属百,如果有重復(fù)的bean名稱存在,默認(rèn)會在原始bean名稱后面通過增加“#+數(shù)字”的方式來生成新的bean名稱以示區(qū)分变姨。具體可參考我的另外一篇文章Spring配置文件id/name重復(fù)定義問題
public static String generateBeanName(
BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
throws BeanDefinitionStoreException {
......
String id = generatedBeanName;
if (isInnerBean) {
// Inner bean: generate identity hashcode suffix.
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
else {
// Top-level bean: use plain class name.
// Increase counter until the id is unique.
int counter = -1;
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
}
return id;
}
至此族扰,parseAdvice分析完成,另外兩個標(biāo)簽的解析parseAdvisor和parsePointCut思路類似定欧,大家可以自行分析??