[TOC]
前言
回顧2017年大部分時間在做業務功能開發,在這一年裡接觸到了spring底層技術實現。在專案開發過程中,專案負責人會根據業務實際需求寫一些通用基礎元件,方便開發人員使用;spring aop在Java專案中被大量使用,在spring中使用aop有兩種方式(註解、xml配置),這兩種方式切入點都需要手動配置,顯然不能滿足通用元件便捷的特性(靈活、便捷)。
本篇文章結合基於自定義註解實現資料來源動態切換基礎元件實現思路講解spring aop配置檔案在spring底層如何解析為BeanDefinition,感興趣的朋友點選檢視元件介紹,原始碼已上傳github。
怎樣才能做到便捷,AOP切入點不需要手動配置程式自動獲取呢?
**應用技術:**自定義註解+BeanFactoryPostProcessor **思路:*註解獲取切入點資訊(expression="execution( com.yz.study.aop...(..))"),當然也可以繫結業務引數(比如在實現多資料來源切換時標註資料庫名稱);通過實現BeanFactoryPostProcessor介面在spring容器bean初始化之前手動新增或修改AOP 相關類BeanDefinition定義。
基礎元件使用Spring AOP改為程式碼實現Beandefination定義有什麼好處呢?
1.基礎元件使用更加靈活,在Java類或方法使用註解即可,不需要手動配置切點 2.基礎元件中可繼續使用Spring框架所有特性,只需在專案spring-context配置檔案新增掃描配置 例如:<context:component-scan base-package="基礎元件包名"/>
基於XML Spring AOP 配置示例
<bean id="advisor" class="com.yz.study.aop.Advisor"/>
<aop:config proxy-target-class="false">
<!-- 需要手動配置切點,在通用元件中不夠靈活 -->
<aop:pointcut id="advisorPointCut" expression="execution(* com.yz.study.aop..*.*(..))"/>
<aop:aspect ref="advisor">
<aop:before method="before" pointcut-ref="advisorPointCut"/>
<aop:after method="after" pointcut-ref="advisorPointCut"/>
<aop:after-returning method="returning" pointcut-ref="advisorPointCut"/>
<aop:around method="around" pointcut-ref="advisorPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="advisorPointCut"/>
</aop:aspect>
</aop:config>
複製程式碼
原始碼解析BeanDefinition
Spring aop核心概念aspect、pointcut、advisor,最終都會解析為BeanDefinition
aspect:AspectComponentDefinition pointcut:AspectJExpressionPointcut advisor: before -- AspectJMethodBeforeAdvice after -- AspectJAfterAdvice after-returning -- AspectJAfterReturningAdvice after-throwing -- AspectJAfterThrowingAdvice around -- AspectJAroundAdvice 下面從spring讀取xml開始到BeanDefinition解析完成完整原始碼示例,可跳過中間步驟,檢視ConfigBeanDefinitionParser
BeanDefinitionDocumentReader
位於org.springframework.beans.factory.xml包下,實現類DefaultBeanDefinitionDocumentReader
/**
* 解析包含Spring bean定義的XML文件
*/
public interface BeanDefinitionDocumentReader {
/**
* 註冊BeanDefinition
*/
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
throws BeanDefinitionStoreException;
}
複製程式碼
DefaultBeanDefinitionDocumentReader
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
//將xml解析轉換BeanDefinition
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
/**
* 不同的標籤使用對應的解析器
* "import", "alias", "bean".
*/
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 {
//AOP 使用 BeanDefinitionParserDelegate代理類自定義解析器
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
複製程式碼
BeanDefinitionParserDelegate
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//namespaceUri = http://www.springframework.org/schema/aop
String namespaceUri = getNamespaceURI(ele);
//handler = 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));
}
複製程式碼
AopNamespaceHandler
AopNamespaceHandler繼承NamespaceHandlerSupport並重寫了init方法
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Override
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());
}
}
複製程式碼
NamespaceHandlerSupport
具體解析方法在ConfigBeanDefinitionParser中
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//localName = config
String localName = parserContext.getDelegate().getLocalName(element);
// parser = ConfigBeanDefinitionParser
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
複製程式碼
ConfigBeanDefinitionParser
在parse方法中終於看到了AOP的核心pointcut、advisor、aspect
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;
}
//切入點解析方法
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;
}
//增強器解析方法
private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
String id = advisorElement.getAttribute(ID);
try {
this.parseState.push(new AdvisorEntry(id));
String advisorBeanName = id;
if (StringUtils.hasText(advisorBeanName)) {
parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
}
else {
advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
}
Object pointcut = parsePointcutProperty(advisorElement, parserContext);
if (pointcut instanceof BeanDefinition) {
advisorDef.getPropertyValues().add(POINTCUT, pointcut);
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
}
else if (pointcut instanceof String) {
advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
parserContext.registerComponent(
new AdvisorComponentDefinition(advisorBeanName, advisorDef));
}
}
finally {
this.parseState.pop();
}
}
//切面解析方法
private void parseAspect(Element aspectElement, ParserContext parserContext) {
String aspectId = aspectElement.getAttribute(ID);
String aspectName = aspectElement.getAttribute(REF);
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
List<BeanReference> beanReferences = new ArrayList<BeanReference>();
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));
}
// 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;
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));
}
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);
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
parserContext.popAndRegisterContainingComponent();
}
finally {
this.parseState.pop();
}
}
複製程式碼
以上屬於原創文章,轉載請註明作者@怪咖
QQ:208275451