第13节 Spring源码之 AOP 实例化过程

本篇笔记将重点整理 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 的结构中包含了 MethodLocatingFactoryBeanSimpleBeanFactoryAwareAspectInstanceFactory 和 切点表达式

所以最后注册到 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 对象时,又要先创建 MethodAspectJExpressionPointcutAspectInstanceFactory 对象

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;
    }     
}      

重点逻辑在AspectJAwareAdvisorAutoProxyCreatorshouldSkip 方法里

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;
}
相关推荐
LuckyLay2 分钟前
Spring学习笔记_27——@EnableLoadTimeWeaving
java·spring boot·spring
Stringzhua8 分钟前
【SpringCloud】Kafka消息中间件
spring·spring cloud·kafka
AskHarries1 小时前
Java字节码增强库ByteBuddy
java·后端
佳佳_1 小时前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
许野平2 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
BiteCode_咬一口代码3 小时前
信息泄露!默认密码的危害,记一次网络安全研究
后端
齐 飞4 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod4 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
成富4 小时前
文本转SQL(Text-to-SQL),场景介绍与 Spring AI 实现
数据库·人工智能·sql·spring·oracle
码农派大星。5 小时前
Spring Boot 配置文件
java·spring boot·后端