本篇笔记将重点整理 Spring 是如何解析AOP的配置,并将配置实例化注入到容器中,包括xml配置、注解配置。
一、Aop的简单案例
- 定义公共业务类
java
@Service
public class PrintMsgService {
public String printMsg() {
System.out.println("&&& PrintMsgService , 打印日志业务 ... ");
System.out.println(1 / 0);
return "success";
}
}
1. 基于xml文件
- 定义切面类
java
public class LogXmlAspect {
public void before() {
System.out.println("!!! LogXmlAspect , before ...");
}
public void after() {
System.out.println("!!! LogXmlAspect , after ...");
}
public void afterReturning(String result) {
System.out.println("!!! LogXmlAspect , after returning , " + result);
}
public void afterThrowing(Exception e) {
System.out.println("!!! LogXmlAspect , after throwing , " + e.getMessage());
}
public void around(ProceedingJoinPoint pjp) {
Signature signature = pjp.getSignature();
Object result = null;
try {
System.out.println("!!! LogXmlAspect Around start , the method is " + signature.getName());
// 通过反射方式调用目标方法
result = pjp.proceed(pjp.getArgs());
System.out.println("!!! LogXmlAspect Around end , the method is " + signature.getName() + ", res :" + result);
} catch (Throwable e) {
System.out.println("!!! LogXmlAspect Around exception , the method is " + signature.getName() + " , " + e.getMessage());
}
}
}
- 配置xml文件
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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.sff.demo.aop"/>
<!--注入切面类-->
<bean id="logXmlAspect" class="com.sff.demo.aop.LogXmlAspect"></bean>
<!--aop配置-->
<aop:config>
<aop:aspect id="logAspect" ref="logXmlAspect">
<!--定义切点-->
<aop:pointcut id="pointcut" expression="execution(* com.sff.demo.aop.PrintMsgService.*(..))"/>
<!--定义通知-->
<aop:before pointcut-ref="pointcut" method="before"/>
<aop:after pointcut-ref="pointcut" method="after"/>
<aop:around pointcut-ref="pointcut" method="around"/>
<aop:after-returning pointcut-ref="pointcut" method="afterReturning" returning="result"/>
<aop:after-throwing pointcut-ref="pointcut" method="afterThrowing" throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
java
public class TestAop {
public static void main(String[] args) {
testAopXml();
}
private static void testAopXml() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
PrintMsgService aop = ac.getBean(PrintMsgService.class);
aop.printMsg();
}
}
运行结果:
2. 基于注解方式
- 定义切面
java
@Aspect
@Component
public class LogAnnoAspect {
// 定义切点
@Pointcut("execution(* com.sff.demo.aop.PrintMsgService.*(..))")
public void pointCut() {
}
@Before(value = "pointCut()")
private void before(JoinPoint joinPoint) {
// 获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println(">>>> LogAnnoAspect Before , Test Aop , the method is " + signature.getName());
}
@After("pointCut()")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
System.out.println(">>>> LogAnnoAspect After , Test Aop , the method is " + signature.getName());
}
@AfterReturning("pointCut()")
public void afterReturning(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
System.out.println(">>>> LogAnnoAspect AfterReturning , Test Aop , the method is " + signature.getName());
}
@AfterThrowing("pointCut()")
public void afterThrowing(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
System.out.println(">>>> LogAnnoAspect AfterThrowing , Test Aop , the method is " + signature.getName());
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) {
Signature signature = pjp.getSignature();
Object result = null;
try {
System.out.println(">>>> LogAnnoAspect Around start, Test Aop , the method is " + signature.getName());
// 通过反射方式调用目标方法
result = pjp.proceed(pjp.getArgs());
System.out.println(">>>> LogAnnoAspect Around end, Test Aop , the method is " + signature.getName());
} catch (Throwable e) {
System.out.println(">>>> LogAnnoAspect Around exception, Test Aop , the method is " + signature.getName() + " , " + e.getMessage());
}
return result;
}
}
- 定义配置类
java
@Configuration
@ComponentScan(basePackages = "com.sff.demo.aop")
@EnableAspectJAutoProxy
public class AopConfig {
}
java
public class TestAop {
public static void main(String[] args) {
testAopAnno();
}
private static void testAopAnno() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AopConfig.class);
AopAnnoService aop = ac.getBean(AopAnnoService.class);
aop.printMsg();
}
}
运行结果:
二、Spring Aop 中几个重要概念
在 Spirng Aop 中,已经形成了自己固定的术语,描述切面的常用术语有通知(advice)、切点(pointcut)和 连接点(joinpoint)
- 通知(advice)
通知定义了切面要执行的逻辑,还定义了何时执行这个逻辑,通常有以下几个时机
(1)前置通知(Before):在目标方法被调用之前执行该通知逻辑
(2)后置通知(After):在目标方法被调用之后执行该通知逻辑
(3)环绕通知(After-returning):在目标方法被调用之前和之后执行通知逻辑
(4)返回通知(After-throwing):在目标方法成功执行之后执行通知逻辑
(5)异常通知(Around):在目标方法抛出异常后调用通知逻辑
- 连接点(joinpoint)
通知逻辑能被执行的地方都可以称之为连接点
- 切点(pointcut)
切点可以理解成具体的目标类和方法 ,并且要在这些目标类和方法上执行通知的逻辑,切点有助于切面缩小通知的连接点的范围,我们通常使用切点表达式来选择需要通知的类和方法
- 切面
切面是通知和切点的结合,定义了具体的通知逻辑以及何时执行通知逻辑
三、Aop 的 xml 配置是如何解析成 BeanDefinition 的
xml
<!--注入切面类-->
<bean id="aopService" class="com.sff.demo.aop.LogXmlAspect"></bean>
<!--aop配置-->
<aop:config>
<aop:aspect id="logAspect" ref="aopService">
<!--定义切点-->
<aop:pointcut id="pointcut" expression="execution(* com.sff.demo.aop.PrintMsgService.*(..))"/>
<!--定义通知-->
<aop:before pointcut-ref="pointcut" method="before"/>
<aop:after pointcut-ref="pointcut" method="after"/>
<aop:around pointcut-ref="pointcut" method="around"/>
<aop:after-returning pointcut-ref="pointcut" method="afterReturning" returning="result"/>
<aop:after-throwing pointcut-ref="pointcut" method="afterThrowing" throwing="e"/>
</aop:aspect>
</aop:config>
<aop:config>
标签在Spring中属于自定义标签,关于自定义标签的解析逻辑请参考笔记:parseCustomElement() 方法解析自定义标签逻辑。
- 解析Aop自定义标签的命名空间
spring.handlers
handler
http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
java
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// In 2.0 XSD as well as in 2.5+ XSDs
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace in 2.5+
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
所以标签的解析器使用的是ConfigBeanDefinitionParser
,解析方法逻辑如下:
java
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 注册自动代理模式创建器,即 AspectJAwareAdvisorAutoProxyCreator
configureAutoProxyCreator(parserContext, element);
// 解析 aop:config 子节点下的 aop:pointcut、aop:advice、aop:aspect 标签
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;
}
Spring 是如何将<aop:before>、<aop:after>、<aop:around>、<aop:after-returning> 和 <aop:after-throwing>
这些通知节点封装成 BeanDefinition
的呢?
- Spring 对每一种通知都有对应的封装类,即
markdown
1. before 通知 -> AspectJMethodBeforeAdvice
2. after 通知 -> AspectJAfterAdvice
3. after-returning 通知 -> AspectJAfterReturningAdvice
4. after-throwing 通知 -> AspectJAfterThrowingAdvice
5. around 通知 -> AspectJAroundAdvice
- Spring 首先解析的是切面标签
<aop:aspect>
,然后循环处理该标签的所有子标签,即<aop:before>、<aop:after>、<aop:around>、<aop:after-returning> 和 <aop:after-throwing>
。具体的实现逻辑封装在parseAspect()
方法中
java
private void parseAspect(Element aspectElement, ParserContext parserContext) {
// <aop:aspect> id属性
String aspectId = aspectElement.getAttribute(ID);
// <aop:aspect> ref属性,指向切面类
String aspectName = aspectElement.getAttribute(REF);
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<>();
List<BeanReference> beanReferences = new ArrayList<>();
// 解析<aop:aspect>下的declare-parents节点,采用的是DeclareParentsAdvisor作为beanClass加载
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.
// 解析<aop:aspect>下的 advice 通知节点
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// 是否为before、after、after-returning、after-throwing、around 通知节点
if (isAdviceNode(node, parserContext)) {
// 校验aop:aspect必须有ref属性,否则无法对切入点进行观察操作
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));
}
// 解析 advice 节点并注册到bean工厂中
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);
// 解析 <aop:point-cut> 节点并注册到bean工厂
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
parserContext.popAndRegisterContainingComponent();
} finally {
this.parseState.pop();
}
}
将每个标签转化成 BeanDefiniton
,并注册到Spring容器中,该逻辑是在 parseAdvice()
方法中完成的
java
/**
* 解析通知类并注册到bean工厂
* Parses one of '{@code before}', '{@code after}', '{@code after-returning}',
* '{@code after-throwing}' or '{@code around}' and registers the resulting
* BeanDefinition with the supplied BeanDefinitionRegistry.
*
* @return the generated advice RootBeanDefinition
*/
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
// 解析advice节点中的"method"属性,并包装为 MethodLocatingFactoryBean 对象
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
// 关联 aspectName ,包装为 SimpleBeanFactoryAwareAspectInstanceFactory 对象
RootBeanDefinition aspectFactoryDef = new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
aspectFactoryDef.setSynthetic(true);
// register the pointcut
// 涉及 point-cut 属性 解析,并结合上述的两个 methodDefinition 、aspectFactoryDef 最终包装为 AbstractAspectJAdvice通知对象
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();
}
}
通过该 parseAdvice()
方法,我们发现每个标签封装的 BeanDefinition
的结构中包含了 MethodLocatingFactoryBean
、SimpleBeanFactoryAwareAspectInstanceFactory
和 切点表达式
所以最后注册到 Spring 容器中的 BeanDefinition
可以简单概括成如下形式:
四、Spring 是如何将<aop:before>、<aop:after>、<aop:around>、<aop:after-returning> 和 <aop:after-throwing>
这些标签的 BeanDefinition
转化成完整的 bean 对象的?
在前面的笔记中讲到:在 resolveBeforeInstantiation()
方法中完成创建bean,其实 Aop 标签的 BeanDefiniton
转化成 bean 对象的过程也是在该方法中完成的。创建 Bean 对象过程中的几个核心对象关系如下图所示:
- 创建
AspectJPointcutAdvisor
对象时,需要先创建AspectJAroundAdvice
对象 - 创建
AspectJAroundAdvice
对象时,又要先创建Method
、AspectJExpressionPointcut
和AspectInstanceFactory
对象
AbstractAutoProxyCreator
实现了 SmartInstantiationAwareBeanPostProcessor
接口,它是 InstantiationAwareBeanPostProcessor
的子接口,它的执行时机如下图所示:
java
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
/**
* 1. 判断 beanClass 是否是 Advice.class 、Pointcut.class、Advisor.class 和 AopInfrastructureBean.class 类型
* 2. shouldSkip 是否跳过,不跳过则创建 Advisor 对象
*/
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
}
重点逻辑在AspectJAwareAdvisorAutoProxyCreator
的 shouldSkip
方法里
java
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
// 这里未进行任何的 Advisor 对象的实例化,所以在 findCandidateAdvisors() 方法中必定会有 Advisor 对象实例化的操作
List<Advisor> candidateAdvisors = findCandidateAdvisors();
for (Advisor advisor : candidateAdvisors) {
if (advisor instanceof AspectJPointcutAdvisor
&& ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
return true;
}
}
return super.shouldSkip(beanClass, beanName);
}
方法里的 findCandidateAdvisors()
方法首次执行时会查找容器里面的 Advisor
接口并实例化,其实此时容器里面有:
- AspectJPointcutAdvisor#0
- AspectJPointcutAdvisor#1
- AspectJPointcutAdvisor#2
- AspectJPointcutAdvisor#3
- AspectJPointcutAdvisor#4
所以也就是要找到它们并实例化。但是 Spring Aop 除了基于xml配置外,还有基于注解开发,它们的解析逻辑都在这个 findCandidateAdvisors()
方法中
AbstractAdvisorAutoProxyCreator
中的 findCandidateAdvisors()
实现了xml配置的实例化
kotlin
protected List<Advisor> findCandidateAdvisors() {
Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
return this.advisorRetrievalHelper.findAdvisorBeans();
}
AnnotationAwareAspectJAutoProxyCreator
中的 findCandidateAdvisors()
实现了注解配置的实例化
java
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
// 找到系统中实现了 Advisor 接口的 bean
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
if (this.aspectJAdvisorsBuilder != null) {
// 找到系统中使用 @Aspect 标注的 bean,并且找到该 bean 中使用 @Before、@After 等标注的方法,最后将这些方法封装成一个个 Advisor
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}