【Spring成神之路】老兄,来一杯Spring AOP源码吗?

news2024/11/15 21:41:11

文章目录

  • 一、引言
  • 二、Spring AOP的使用
  • 三、Spring AOP的组件
    • 3.1 Pointcut源码
    • 3.2 Advice源码
    • 3.3 Advisor源码
    • 3.4 Aspect源码
  • 四、Spring AOP源码刨析
    • 4.1 configureAutoProxyCreator源码解析
    • 4.2 parsePointcut源码解析
    • 4.3 parseAdvisor源码解析
    • 4.4 parseAspect源码解析
    • 4.5 小总结
    • 4.6 代理创建
  • 五、总结

一、引言

AOPSpring框架的重点之一,AOP全称为Aspect-Oriented Programming,意思为面向切面编程,这种编程方式是一种编程的范式。

AOP允许开发者将横向关注点(如日志记录、事务管理等操作)与业务逻辑分开来,从而提高代码的模块化和可维护性。

下面从浅入深彻底搞懂Spring AOP的底层原理!

注意,本篇文章中使用的Spring框架的版本是4.0.0.RELEASE,不同版本之间源码会有一点点不同,但是大体逻辑差不多。

推荐阅读者需要对动态代理以及Spring IOC源码有一定了解,如果不了解可以阅读以下文章

  1. 都2024年了,还有人不懂动态代理么?
  2. 【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.0.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.0.0.RELEASE</version>
</dependency>

二、Spring AOP的使用

UserService接口的定义

public interface UserService {
    String selectList();
}

UserService接口实现类

public class UserServiceImpl implements UserService {
    @Override
    public String selectList() {
        System.out.println("小明, 小红, 小蓝");
        return "小明, 小红, 小蓝";
    }
}

AOP前置处理与后置处理

public class AOPUtil {
    private void before(JoinPoint joinPoint) {
        //获取方法签名
        Signature signature = joinPoint.getSignature();
        //获取参数信息
        Object[] args = joinPoint.getArgs();
        System.out.println("log---" + signature.getName() + "I am before");
    }

    private void after(JoinPoint joinPoint) {
        //获取方法签名
        Signature signature = joinPoint.getSignature();
        //获取参数信息
        Object[] args = joinPoint.getArgs();
        System.out.println("log---" + signature.getName() + "I am after");
    }
}

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"
       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="messageService" class="spring.aop.UserServiceImpl"/>
    <bean id="aopUtil" class="spring.aop.AOPUtil"/>

    <aop:config>
        <aop:aspect ref="aopUtil">
            <aop:pointcut id="myPoint"  expression="execution(* spring.aop.UserService.selectList(..))"/>
            <aop:before method="before" pointcut-ref="myPoint"/>
            <aop:after method="after" pointcut-ref="myPoint"/>
        </aop:aspect>
    </aop:config>

</beans>

测试类

public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

        System.out.println("context 启动成功");

        UserService userService = context.getBean(UserService.class);

        userService.selectList();
    }
}

image-20240707174827569

我们只需要编写好切面的逻辑即可,然后在xml配置文件配置后切面的方法的执行顺序即可实现在目标方法的前面或后面运行某个逻辑。

这当中到底发生了什么呢?这些切面方法是何时被调用呢?别急接着往下看!


三、Spring AOP的组件

在阅读Spring AOP源码之前,先来看看Spring AOP的几个组件,这里源码会采取点到为止的方式,并不深入讲解

  1. Pointcut:定义切面的匹配点,主要是类和方法。比如上面例子的spring.aop.UserService.selectList(..)方法就是匹配点。
  2. Advice:定义切面的行为,即在匹配点执行的操作。也就是上面例子的aop:beforeaop:after
  3. Advisor:将PointcutAdvice组合成一个对象,也就是一个完整的切面。
  4. Aspect:使用注解或XML配置方式定义的切面,通常包含了多个Advisor

3.1 Pointcut源码

Pointcut是一个接口,提供了getClassFilter方法和getMethodMatcher方法,用于获取当前匹配的类和方法

public interface Pointcut {

    /**
     * 返回一个ClassFilter对象, 用于确定哪些类被匹配,返回的对象不能为null
     */
    ClassFilter getClassFilter();

    /**
     * 返回一个MethodMatcher对象,用于确定哪些方法被匹配
     */
    MethodMatcher getMethodMatcher();


    /**
     * Canonical Pointcut instance that always matches.
     */
    Pointcut TRUE = TruePointcut.INSTANCE;

}

AspectJExpressionPointcutPointcut的实现,除了上面两个方法,还提供了matches方法,用于判断是否匹配。

public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
    // 类的匹配
    public boolean matches(Class<?> targetClass) {}
    
    // 方法的匹配 
    public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {}
}


3.2 Advice源码

Advice接口没有需要实现的方法,它是用于定义切面的行为的,比如方法前切入、方法后切入、环绕切入等等。

public interface Advice {

}
// 前置切入实现
public interface BeforeAdvice extends Advice {}

// 后置切入实现
public interface AfterAdvice extends Advice {}

3.3 Advisor源码

Advisor接口定义了getAdvice方法和isPerInstance方法

public interface Advisor {


    /**
     * 返回与该Advisor关联的Advice
     */
    Advice getAdvice();

    /**
     * 返回这个Advice是否与特定的实例关联。
     */
    boolean isPerInstance();

}

3.4 Aspect源码

Spring AOP并没有Aspect这个接口,但是Aspect通常指下面这个

<aop:aspect ref="aopUtil">
</aop:aspect>

四、Spring AOP源码刨析

阅读Spring AOP源码和阅读Spring IOC源码的步骤一样,都是从读取application.xml配置文件开始。

因为AOP的配置信息是写在XML配置文件的,所以肯定需要读取XML配置文件获取AOP相关的信息,那么我们就看看这其中都做了什么吧。

直接定位到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions这个方法,不了解的可以先看看【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!

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)) {
                 // 如果是bean 这些就走这个逻辑
                parseDefaultElement(ele, delegate);
             }
             else {
                 // AOP走这个逻辑
                delegate.parseCustomElement(ele);
             }
          }
       }
    }
    else {
       delegate.parseCustomElement(root);
    }
}

既然知道parseCustomElement方法是处理aop标签的,那么我们就进去看看是怎么个事?

public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 获取当前的命名空间
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    // 根据命名空间获取对应的处理器
    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));
}

parseCustomElement方法干三件事:

  1. 获取元素的命名空间
  2. 根据命名空间获取对应的处理器
  3. 调用处理器的parse方法进行处理

继续DEBUG,定位到了org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse方法

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 获取适合解析当前元素的BeanDefinitionParser
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    // 进行解析并返回一个BeanDefinition
    return (parser != null ? parser.parse(element, parserContext) : null);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 获取元素的本地名称, 比如aop:config,本地名称就是config
    String localName = parserContext.getDelegate().getLocalName(element);
    // 获取该本地名称的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
            "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
    CompositeComponentDefinition compositeDef =
          new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    parserContext.pushContainingComponent(compositeDef);
	// 设置自动代理创建器,这是Spring AOP实现的核心
    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;
}

4.1 configureAutoProxyCreator源码解析

configureAutoProxyCreator方法是Spring IOC的核心,主要作用是确保 AspectJ 自动代理创建器被正确注册到 Spring 容器中!

private void configureAutoProxyCreator(ParserContext parserContext, Element element) {
    AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);
}

public static void registerAspectJAutoProxyCreatorIfNecessary(
    ParserContext parserContext, Element sourceElement) {

    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
        parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    registerComponentIfNecessary(beanDefinition, parserContext);
}

该方法的目的是往Spring IOC容器中注册一个AspectjAwareAdvisorAutoProxyCreator,其负责创建代理对象和实现切面编程。


4.2 parsePointcut源码解析

parsePointcut方法的主要作用是负责解析aop:pointcut标签

<aop:pointcut id="myPoint"  expression="execution(* spring.aop.UserService.selectList(..))"/>
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
    // 获得
    String id = pointcutElement.getAttribute(ID);
    // 获得切点表达式
    String expression = pointcutElement.getAttribute(EXPRESSION);
	// 定义切点存储
    AbstractBeanDefinition pointcutDefinition = null;

    try {
        // 将当前解析的切点ID压入解析状态栈
       this.parseState.push(new PointcutEntry(id));
        // 根据切点表达式创建切点定义
       pointcutDefinition = createPointcutDefinition(expression);
        // 设置切点的来源
       pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
		// 切点的Bean名称
       String pointcutBeanName = id;
       if (StringUtils.hasText(pointcutBeanName)) {
           // 如果设置了id, 将切点注册到spring ioc容器中
          parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
       }
       else {
           // 否则也是注入进spring ioc容器中并设置别名
          pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
       }
		// 注册一个PointcutComponentDefinition组件
       parserContext.registerComponent(
             new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
    }
    finally {
        // 从栈弹出
       this.parseState.pop();
    }

    return pointcutDefinition;
}

4.3 parseAdvisor源码解析

parseAdvisor方法负责解析<advisor>标签

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);
        // 根据pointcut的类型,将其添加到advisorDef的属性值中,并注册相关的组件。
       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();
    }
}

4.4 parseAspect源码解析

parseAspect负责解析aspect标签

private void parseAspect(Element aspectElement, ParserContext parserContext) {
    // 获取ID值
    String aspectId = aspectElement.getAttribute(ID);
    // 获取ref属性
    String aspectName = aspectElement.getAttribute(REF);

    try {
       this.parseState.push(new AspectEntry(aspectId, aspectName));
       List<BeanDefinition> beanDefinitions = new ArrayList<>();
       List<BeanReference> beanReferences = new ArrayList<>();
		// 获得当前节点下的所有<declare-parents>子元素
       List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
       for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
          Element declareParentsElement = declareParents.get(i);
           // 解析节点并注册进IOC容器中
          beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
       }
		
        // 获取当前节点的子节点
       NodeList nodeList = aspectElement.getChildNodes();
       boolean adviceFoundAlready = false;
       for (int i = 0; i < nodeList.getLength(); i++) {
          Node node = nodeList.item(i);
           // 是否是advice节点
          if (isAdviceNode(node, parserContext)) {
              // 是否是第一个advice节点
             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;
                }
                 // 添加一个运行时bean引用到beanReferences
                beanReferences.add(new RuntimeBeanReference(aspectName));
             }
              // 如果节点是advice,则调用parseAdvice方法解析它,并创建一个顾问(advisor)的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);
		// 获取所有<pointcut>子元素。
       List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
       for (Element pointcutElement : pointcuts) {
           // 解析切点
          parsePointcut(pointcutElement, parserContext);
       }
		// 从解析上下文中弹出并注册包含的组件。
       parserContext.popAndRegisterContainingComponent();
    }
    finally {
       this.parseState.pop();
    }
}

总得来说,就是解析标签,将标签的内容包装成BeanDefinition并注册进IOC容器。

前面说到Advice就是一个切面的行为,而parseAdvice方法就是负责解析这个切面行为的

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

       // 解析advice节点中的 method 属性,将其创建成一个 beanDefinition 对象
       RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
       methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
       methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
       methodDefinition.setSynthetic(true);

       // 关联aspectName(对应的就是案例中的aopUtil),包装为 SimpleBeanFactoryAwareAspectInstanceFactory 对象
       RootBeanDefinition aspectFactoryDef =
             new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
       aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
       aspectFactoryDef.setSynthetic(true);

       // 解析切点并综合上面两个bean包装成AbstractAspectJAdvice
       AbstractBeanDefinition adviceDef = createAdviceDefinition(
             adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
             beanDefinitions, beanReferences);

       // 最终包装成AspectJPointcutAdvisor
       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
       parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

       return advisorDefinition;
    }
    finally {
       this.parseState.pop();
    }
}

4.5 小总结

好吧,估计有点绕晕了。就parseAspect方法来说,主要的目的是想组装一个Advisor,但是得先把把advice初始化,这个时候就需要解析标签中的method等实例化出来methodDefinition

有了methodDefinition就知道这个方法是前置增还是后置增强。接着实例化Pointcut等这些,都是实例化完成后,再再按照下图的组件关系组装出一个完整的Advisor,这个AdvisorAOP的所有信息,并保存在了IOC容器中。

image-20240708215753514

既然切点、切面等信息以及包装好了,代理对象是怎么创建返回的呢?接着往下看。不过在此需要先加一点前置知识。


4.6 代理创建

IOC源码篇的时候,有讲解过org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization方法。

image-20240708221659137

org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons会实例化所有非延迟初始化的单例Bean

在这个方法中大致内容就是获取所有beanDefinitionNames,然后进行遍历,如果是属于FactoryBean则进行FactoryBean的处理,否则正常处理。

List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
       if (isFactoryBean(beanName)) {
			// ....省略
       }
       else {
          getBean(beanName);
       }
    }
}

因为AOP包装的Bean肯定不是FactoryBean,因此直接进入getBean方法

public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(
       String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
       throws BeansException {
	// 转化别名
    String beanName = transformedBeanName(name);
    Object bean;

    // 提前检查单例缓存中是否有手动注册的单例对象,这和循环依赖有关联,以后会讲到
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
		// 省略....
    }

    else {
       // 当对象都是单例的时候会尝试解决循环依赖的问题,但是原型模式下如果存在循环依赖的情况,那么直接抛出异常
       if (isPrototypeCurrentlyInCreation(beanName)) {
          throw new BeanCurrentlyInCreationException(beanName);
       }
		// 省略若干行代码
        RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        String[] dependsOn = mbd.getDependsOn();
        // 是单例
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    // 创建单例
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }

    return (T) bean;
}

整体代码比较啰嗦,这里省略一些代码,大体内容如下:

  1. 转化bean的名字
  2. 是否有循环依赖,有的话解决循环依赖问题
  3. Bean属于单例,进行创建单例
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)

    try {
       Object beanInstance = doCreateBean(beanName, mbdToUse, args);
       if (logger.isTraceEnabled()) {
          logger.trace("Finished creating instance of bean '" + beanName + "'");
       }
       return beanInstance;
    }

}

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
    // 根据执行bean使用对应的策略创建新的实例,如,工厂方法,构造函数主动注入、简单初始化
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    // 对bean的属性进行填充,将各个属性值注入,其中,可能存在依赖于其他bean的属性,则会递归初始化依赖的bean
    populateBean(beanName, mbd, instanceWrapper);
    
    // 执行初始化逻辑
    exposedObject = initializeBean(beanName, exposedObject, mbd);
}

ok!终于定位到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)这个方法了,这个方法就是负责初始化Bean。

以上的内容属于IOC源码知识的补充,下面正式开始AOP代理创建的源码解析!!!

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 执行前置通知
       wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
       invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
       throw new BeanCreationException(
             (mbd != null ? mbd.getResourceDescription() : null),
             beanName, "Invocation of init method failed", ex);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 执行后置通知
       wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

前置通知和AOP没半毛钱关系,主要关注执行后置通知

为什么AOP与后置通知有关系?

还记不记得,前面说过会向容器注入AspectjAwareAdvisorAutoProxyCreator,这是类负责代理的创建

image-20240708223931696

是的,你没看错AspectjAwareAdvisorAutoProxyCreator是一个实现了BeanPostProcessor的类

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization方法的源码很简单,就是调用postProcessAfterInitialization返回一个bean,对当前的bean进行包装

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
       throws BeansException {

    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
       Object current = processor.postProcessAfterInitialization(result, beanName);
       if (current == null) {
          return result;
       }
       result = current;
    }
    return result;
}

AspectjAwareAdvisorAutoProxyCreatorpostProcessAfterInitialization的实现是怎么样的?

其实AspectjAwareAdvisorAutoProxyCreator并没有该方法的实现,而是在其父类实现了org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        // 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型,
		// 前面还会添加&符号,如果beanName为空,则以当前bean对应的class为key
       Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 判断这个bean是否正在在被代理,如果正在被代理则不处理
       if (this.earlyProxyReferences.remove(cacheKey) != bean) {
           // 进入需要被代理,进行代理处理
          return wrapIfNecessary(bean, beanName, cacheKey);
       }
    }
    return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 获取当前Bean的Advices和Advisors
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // 对Bean的代理状态缓存
    if (specificInterceptors != DO_NOT_PROXY) {
       this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 创建代理
       Object proxy = createProxy(
             bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 缓存代理Bean的类型
       this.proxyTypes.put(cacheKey, proxy.getClass());
       return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

着重关心一下createProxy,看看创建代理是怎么个事

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
       @Nullable Object[] specificInterceptors, TargetSource targetSource) {
	// 给bean定义设置暴露属性
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
       AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
	
    // 创建代理工厂
    ProxyFactory proxyFactory = new ProxyFactory();
    // 将当前实例的配置复制到proxyFactory
    proxyFactory.copyFrom(this);
	
    // 决定对于给定的Bean是否应该试应targetClass而不是接口代理
    if (!proxyFactory.isProxyTargetClass()) {
        // 检查使用JDK代理还是cglib代理
       if (shouldProxyTargetClass(beanClass, beanName)) {
          proxyFactory.setProxyTargetClass(true);
       }
       else {
           // 添加代理接口
          evaluateProxyInterfaces(beanClass, proxyFactory);
       }
    }
	// 构建增强器
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    // 设置要代理的类
    proxyFactory.setTargetSource(targetSource);
    // 定制代理
    customizeProxyFactory(proxyFactory);

    // 控制代理工程被配置之后,是否还允许修改通知,默认值是false
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
       proxyFactory.setPreFiltered(true);
    }
	// 完成上面这些之后开始创建代理对象
    return proxyFactory.getProxy(getProxyClassLoader());
}

createAopProxy方法是根据代理目标选择JDK代理或者是Cglib代理,代码挺容易看懂的,就不解释了

public Object getProxy(@Nullable ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
       Class<?> targetClass = config.getTargetClass();
       if (targetClass == null) {
          throw new AopConfigException("TargetSource cannot determine target class: " +
                "Either an interface or a target is required for proxy creation.");
       }
       if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
          return new JdkDynamicAopProxy(config);
       }
       return new ObjenesisCglibAopProxy(config);
    }
    else {
       return new JdkDynamicAopProxy(config);
    }
}

至于getProxy方法,JDK代理和CGLib代码都有不同的实现,在都2024年了,还有人不懂动态代理么?有讲述过JDK代理和CGLIB代理的实现原理,具体请看这篇文章。


五、总结

经过上面这么一步步过来,这个Bean的代理对象就成功被内放进Spring IOC容器中,当我们下次从容器getBean的时候就可以获取到代理的对象了。

虽然源码很复杂,但是其实不需要记住每一步,只需要记住Spring AOP实现的大体步骤思路即可

  1. 读取并解析配置文件,将AOP的四大组件,解析封装成Bean,存放进IOC中
  2. IOC注入一个AspectjAwareAdvisorAutoProxyCreator,这是一个实现了BeanPostProcessor接口。这个非常重要,这个是Spring AOP的核心
  3. Spring IOC完成刷新之后,会进行单例Bean的懒加载,在懒加载的过程中会获取容器中的所有BeanPostProcessor实现类,然后调用其postProcessAfterInitialization方法
  4. AspectjAwareAdvisorAutoProxyCreator的实现会判断当前Bean需要被代理,然后根据目标Bean是否实现接口等条件判断使用JDK代理还是CGlib代理,然后返回代理类并放进容器

总体核心就这四点,但里面的细节还有很多很多,这篇文章就先写这吧。明天还要上班呢~


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1915484.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【题目/算法训练】:单调队列单调栈

&#x1f680; 前言&#xff1a; 【算法】单调队列&&单调栈 可以在看完这篇文章后&#xff0c;再来写下面的题目 一、绝对差不超过限制的最长连续子数组 思路&#xff1a; 1&#xff09; 就相当于滑动窗口&#xff0c;维护滑动窗口内的两个值&#xff0c;一个是最大值…

溶解氧(DO)理论指南(3)

转载自梅特勒官网资料&#xff0c;仅用于学习交流&#xff0c;侵权则删&#xff01; 溶解氧理论指南 设备操作3.1 DO电极准备3.2 DO电极校准3.3 进行DO测量3.4 转换单位3.5 维护和储存 设备操作 本章总结了 DO电极日常使用的一些建议。它们基于普遍接受的操作规则。 3.1 DO电…

【数据结构——链表的深度探索】从实现到应用,保姆级攻略

【数据结构——链表深度探索】从实现到应用&#xff0c;保姆级攻略 &#x1f341;1. 链表的介绍&#x1f341;2. 链表的实现&#x1f341;2.1 单向链表&#x1f341;2.1.1 size()&#x1f341;2.1.2 display()&#x1f341;2.1.3 contains(int key)&#x1f341;2.1.4 addFirst…

本地部署,强大的面部修复与增强网络CodeFormer

目录 什么是 CodeFormer&#xff1f; 技术原理 主要功能 应用场景 本地部署 运行结果 结语 Tip&#xff1a; 在图像处理和计算机视觉领域&#xff0c;面部修复和增强一直是一个备受关注的研究方向。近年来&#xff0c;深度学习技术的飞速发展为这一领域带来了诸多突破性…

C++ 语法习题(3)

字符串 1.字符串长度 给定一行长度不超过 100 的非空字符串&#xff0c;请你求出它的具体长度。 输入格式 输入一行&#xff0c;表示一个字符串。注意字符串中可能包含空格。 输出格式 输出一个整数&#xff0c;表示它的长度。 数据范围 1≤字符串长度≤100 字符串末尾…

2024学生党蓝牙耳机什么牌子好?品牌高性价比蓝牙耳机推荐

2024年&#xff0c;对于追求性价比和品质的学生党来说&#xff0c;选择一款合适的蓝牙耳机是提升学习和生活品质的重要一环。面对市场上琳琅满目的蓝牙耳机产品&#xff0c;2024学生党蓝牙耳机什么牌子好&#xff1f;如何找到既满足音质需求又具备高性价比的款式呢&#xff1f;…

Odoo免费开源ERP如何处理汽车零部件企业的OE编码问题

业务背景 汽车零部件企业在每个汽配零件都有OE编号&#xff0c;即原厂编号&#xff0c;Original Equipment Number。一个配件&#xff0c;可能可以在多个车型上使用&#xff0c;对应的&#xff0c;就有多个可兼容的OE编号。 客户下单时候&#xff0c;直接报OE编号&#xff0c…

MT3054 搭积木

1.思路&#xff1a; 把二维矩阵转化成一维编号&#xff0c;之后将编号使用并查集&#xff0c;看最后是否在同一个集合中即可。 2.代码&#xff1a; #include <bits/stdc.h> using namespace std; const int N 1e3 10; int n, m, cnt, root; int fa[N * N]; int dx[…

[机器学习]-人工智能对程序员的深远影响——案例分析

机器学习和人工智能对未来程序员的深远影响 目录 机器学习和人工智能对未来程序员的深远影响1. **自动化编码任务**1.1 代码生成1.2 自动调试1.3 测试自动化 2. **提升开发效率**2.1 智能建议2.2 项目管理 3. **改变编程范式**3.1 数据驱动开发 4. **职业发展的新机遇**4.1 AI工…

【代码随想录】【算法训练营】【第63天】 [卡码53]寻宝

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 63&#xff0c;周二&#xff0c;ding~ 题目详情 [卡码53] 寻宝 题目描述 卡码53 寻宝 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 prim算法 kruskal…

UDP协议介绍和作用

什么是UDP? UDP是User Datagram Protocol的简称&#xff0c;中文名是用户数据报协议&#xff0c;是OSI参考模型中的传输层协议&#xff0c;它是一种无连接的传输层协议&#xff0c;提供面向事务的简单不可靠信息传送服务。 UDP的正式规范是IETF RFC768。UDP在IP报文的协议号是…

java数组之线性查找、二分法查找

一、线性查找 思想&#xff1a;如果想在一个数组中查找是否有某个元素&#xff0c;最容易想到的办法就是遍历数组&#xff0c;将数组中元素与想要查找的元素逐个对比&#xff0c;如果相等表示找到了&#xff0c;如果不等&#xff0c;则表示没找到。这就是线性查找的思想。 案例…

Chat2DB:AI引领下的全链路数据库管理新纪元

一、引言 随着数据驱动决策成为现代企业和组织的核心竞争力&#xff0c;数据库管理工具的重要性日益凸显。然而&#xff0c;传统的数据库管理工具往往存在操作复杂、功能单一、不支持多类型数据库管理等问题&#xff0c;限制了数据的有效利用。为了打破这一局面&#xff0c;Ch…

东方通Tongweb发布vue前端

一、前端包中添加文件 1、解压vue打包文件 以dist.zip为例&#xff0c;解压之后得到dist文件夹&#xff0c;进入dist文件夹&#xff0c;新建WEB-INF文件夹&#xff0c;进入WEB-INF文件夹&#xff0c;新建web.xml文件&#xff0c; 打开web.xml文件&#xff0c;输入以下内容 …

代码随想录算法训练营第四十九天| 647. 回文子串、 516.最长回文子序列

647. 回文子串 题目链接&#xff1a;647. 回文子串 文档讲解&#xff1a;代码随想录 状态&#xff1a;不会 思路&#xff1a; dp[i][j] 表示字符串 s 从索引 i 到索引 j 这一段子串是否为回文子串。 当s[i]与s[j]不相等&#xff0c;那没啥好说的了&#xff0c;dp[i][j]一定是fa…

Chromium编译指南2024 Linux篇-编译Chromium(五)

1.引言 在完成环境配置之后&#xff0c;我们需要开始实际的编译工作。编译 Chromium 是一个相对复杂且耗时的过程&#xff0c;尤其是第一次编译时。为了保证顺利完成&#xff0c;我们将使用 GN 和 Ninja 工具来生成和管理构建文件。接下来&#xff0c;我们会详细介绍如何生成构…

Cesium中实现全球体积云效果的一种方案

原生 Cesium 提供了一种积云的效果&#xff0c;云的物理特征和渲染性能都还不错&#xff0c;这种方案适合表达小范围相对离散的云朵&#xff0c;但是用来实现全球范围下相对连续、柔和渐变的云层比较困难。本文在体渲染的基础上&#xff0c;参考了开源社区中 shadertoy 和 thre…

软件架构之软件架构概述及质量属性

软件架构之软件架构概述及质量属性 第 9 章&#xff1a;软件架构设计9.1 软件架构概述9.1.1 软件架构的定义9.1.2 软件架构的重要性9.1.3 架构的模型 9.2 架构需求与软件质量属性9.2.1 软件质量属性9.2.2 6 个质量属性及实现 第 9 章&#xff1a;软件架构设计 像学写文章一样&…

java通过poi-tl导出word实战详细步骤

文章目录 与其他模版引擎对比1.引入maven依赖包2.新建Word文档exportWprd.docx模版3.编写导出word接口代码4.导出成果 poi-tl是一个基于Apache POI的Word模板引擎&#xff0c;也是一个免费开源的Java类库&#xff0c;你可以非常方便的加入到你的项目中&#xff0c;并且拥有着让…

模板语法之插值语法{{}}——01

<主要研究&#xff1a;{{ 这里可以写什么}} 1.在data中声明的变量函数都可以 2.常量 3.只要是合法的JavaScript的表达式&#xff0c;都可以 4. 模板表达式都被放在沙盒中&#xff0c;只能访问全局变量的一个白名单&#xff0c;如 Math 和 Date <body> <div i…