Spring 的 Aop 支持

news2024/11/27 2:33:00

Spring 的 Aop 支持

  • 一、AOP基本概念
    • 1_AOP基本术语
    • 2_通知类型回顾
    • 3_AOP基于代理
    • 4_SpringBoot整合AOP
  • 二、整体流程剖析
    • 1_AspectJAutoProxyRegistrar
    • 2_AnnotationAwareAspectJAutoProxyCreator
      • 1_继承结构
      • 2_初始化时机
      • 3_AnnotationAwareAspectJAutoProxyCreator的作用时机
    • 3_收集增强器的逻辑
    • 4_收集原生增强器
      • 1_检查现有的增强器 bean 对象
      • 2_初始化原生增强器
    • 5_解析Aspect]切面封装增强器
      • 1_逐个解析 IOC 容器中所有的 Bean 类型
      • 2_解析 Aspect切面类,构造增强器
      • 3_判断Class 是否是通知类
      • 4_构造增强器
      • 5_收集切面类中的通知方法
      • 6_创建增强器
      • 7_解析通知注解上的切入点表达式
    • 6_原型切面 Bean 的处理
    • 7_增强器汇总
    • 8_代理对象生成的核心:wrapIfNecessary
      • 1_getAdvicesAndAdvisorsForBean
      • 2_createProxy – 创建代理对象
    • 9_代理对象的底层执行逻辑
    • 10_AOP通知的执行顺序对比
  • 三、模拟实现
    • 1_底层切点、通知、切面
    • 2_切点匹配
    • 3_从 @Aspect 到 Advisor
      • 1_代理创建器
      • 2_代理创建时机
      • 3_@Before 对应的低级通知
    • 4_静态通知调用
      • 1_通知调用过程
      • 2_模拟 MethodInvocation
    • 5_动态通知调用
  • 四、总结

一、AOP基本概念

Spring Framework 的两大核心特性中,除了IOC,面向切面编程(AOP)也非常重要。AOP是OOP的补充,OOP的核心是对象,AOP的核心是切面(Aspect)。AOP可以在不修改功能代码本身的前提下,使用运行时动态代理技术对已有代码逻辑进行增强。AOP可以实现组件化、可插拔式的功能扩展,通过简单配置即可将功能增强到指定的切人点。

1_AOP基本术语

  • Target:目标对象,即被代理的对象;
  • Proxy:代理对象,即经过代理后生成的对象(如 Proxy.newProxyInstance 返回的结果);
  • JoinPoint:连接点,即目标对象的所属类中,定义的所有方法;
  • Pointcut:切入点,即那些被拦截 / 被增强的连接点;
    • 切入点与连接点的关系应该是包含关系:切入点可以是 0 个或多个(甚至全部)连接点的组合
    • 注意,切入点一定是连接点,连接点不一定是切入点
  • Advice:通知,即增强的逻辑,也就是增强的代码;
    • Proxy 代理对象 = Target 目标对象 + Advice 通知
  • Aspect:切面,即切入点与通知组合之后形成的产物;
    • Aspect 切面 = Pointcut 切入点 + Advice 通知
    • 实际上切面不仅仅是包含通知,还有一个不常见的部分是引介
  • Weaving:织入,它是将 Advice 通知应用到 Target 目标对象,进而生成 Proxy 代理对象的过程;
    • Proxy 代理对象 = Target 目标对象 + Advice 通知,这个算式中的加号,就是织入
  • Introduction:引介,这个概念对标的是 Advice 通知,通知是针对切入点提供增强的逻辑,而引介是针对 Class 类,它可以在不修改原有类的代码的前提下,在运行期为原始类动态添加新的属性 / 方法。

2_通知类型回顾

  • Before 前置通知:目标对象的方法调用之前触发;
  • After 后置通知:目标对象的方法调用之后触发;
  • AfterReturning 返回通知:目标对象的方法调用完成,在返回结果值之后触发;
  • AfterThrowing 异常通知:目标对象的方法运行中抛出 / 触发异常后触发;
    • 注意一点,AfterReturning 与 AfterThrowing 两者是互斥的!如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around 环绕通知:编程式控制目标对象的方法调用;
    • 环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后扩展逻辑,甚至不调用目标对象的方法。

3_AOP基于代理

AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能,常用的实现有两种:Cglib 代理和 jdk 动态代理。——在这篇结构型模式中有对这两种代理实现详细介绍。

除此以外,aspectj 提供了两种另外的 AOP 底层实现:

  • 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
  • 第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能
  • 作为对比,之前学习的代理是运行时生成新的字节码

简单比较的话:

  • aspectj 在编译和加载时,修改目标字节码,性能较高
  • aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
  • 但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行

jdk 和 cglib 在 Spring 中的统一

Spring 中对切点、通知、切面的抽象如下

  • 切点:接口 Pointcut,典型实现 AspectJExpressionPointcut
  • 通知:典型接口为 MethodInterceptor 代表环绕通知
  • 切面:Advisor,包含一个 Advice 通知,PointcutAdvisor 包含一个 Advice 通知和一个 Pointcut
«interface»
Advice
«interface»
MethodInterceptor
«interface»
Advisor
«interface»
PointcutAdvisor
«interface»
Pointcut
AspectJExpressionPointcut

代理相关类图

  • AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
  • AopProxy 通过 getProxy 创建代理对象
  • 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
  • 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor
使用
创建
创建
«interface»
Advised
ProxyFactory
proxyTargetClass : boolean
Target
Advisor
«interface»
AopProxyFactory
«interface»
AopProxy
+getProxy() : Object
基于CGLIB的Proxy
ObjenesisCglibAopProxy
advised : ProxyFactory
JdkDynamicAopProxy
advised : ProxyFactory
基于JDK的Proxy

4_SpringBoot整合AOP

导入spring-boot-starter-aop 依赖,并在主启动类上标注@EnableAspectJAutoProxy注解,即可开启基于注解驱动的AOP。

AOP的开关:@EnableAspectJAutoProxy

在这里插入图片描述

@EnableAspectJAutoProxy 注解中包含两个属性,分别是proxyTargetClass是否直接代理目标类(即强制使用Cglib代理),以及exposeProxy是否暴露当前线程的 AOP上下文(开启后,通过 AopContext 可以获取到当前的代理对象本身)除了注解属性,@EnableAspectJAutoproxy最重要的作用是使用@Import 注解导入了一个AspectJAutoProxyRegistrar

二、整体流程剖析

1_AspectJAutoProxyRegistrar

从类名上可以简单理解为,它是一个基于AspectJ 支持的自动代理注册器.

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    	//此处会有注册新BeanDefinition的动作
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        //给AnnotationAwareAspectJAutoProxyCreator设置属性值
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }

    }
}

AspectJAutoProxyRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,具备编程式注册新BeanDefinition 的能力,核心的registerBeanDefinitions方法中,除后面对@EnableAspectJAutoproxy注解的属性进行获取和设置以外,注册组件的核心逻辑是方法体中的第一行: AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

在这里插入图片描述

@Nullable
    private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        //后置处理器的等级升级机制
        if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {
            BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }

            return null;
        } else {
        	//构建BeanDefinition,注册到 BeanDefinitionRegistry 中
            RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
            beanDefinition.setSource(source);
            //注意代理对象创建的优先级是 BeanPostProcessor 中最高的
            beanDefinition.getPropertyValues().add("order", Integer.MIN_VALUE);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
            return beanDefinition;
        }
    }

静态的方法调用中,它已经指明了最终要注册的AOP代理创建器的落地实现AnnotationAwareAspectJAutoProxyCreator,而在下面最终注册的方法registerOrEscalateApcAsRequired中,手动创建了一个RootBeanDefinition,将 AOP 代理创建器的类型传入并设置了最高级别的优先级等其他属性和配置项,随后注册到BeanDefinitionRegistry中。

2_AnnotationAwareAspectJAutoProxyCreator

注册的核心动作本身没有太多的研究价值,重点还是AOP动态代理创建器本身。它才是 AOP 特性的核心后置处理器。

AnnotationAwareAspectJAutoProxyCreator可以兼顾AspectJ风格的切面声明,以及SpringFramework原生的AOP编程。

1_继承结构

在这里插入图片描述

  • BeanPostProcessor:用于在 postProcessAfterInitialization 方法中生成代理对象。

  • InstantiationAwareBeanPostProcessor:拦截Bean的正常 doCreateBear创建流程。

  • SmartInstantiationAwareBeanPostprocessor:提前预测 Bean 的类型、暴Bean 的引用

  • AopInfrastructureBean;实现了该接口的Bean 永远不会被代理(防止反复被代理导致逻辑死循环)。

2_初始化时机

AnnotationAwareAspectJAutoProxyCreator本身是一个后置处理器,后置处理器的初始化时机是在AbstractApplicationContext 刷新动作的第6步registerBeanPostProcessors方法中

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        // ....
        try {
            this.postProcessBeanFactory(beanFactory);
            this.invokeBeanFactoryPostProcessors(beanFactory);
            //6.注册、初始化BeanPostProcessor
            this.registerBeanPostProcessors(beanFactory);
            this.initMessageSource();
            this.initApplicationEventMulticaster();
            //...
        }
    }
}

registerBeanPostProcessors 方法会按照既定的排序规则初始化所有 BeanPostProcessor,此处有一个细节, AnnotationAwareAspectJAutoProxyCreator 实现了Ordered接口,并且声明了最高优先级,这就意味着它会提前于其他 BeanPostProcessor 创建,从而也会干预这些普通 BeanPostProcessor 的初始化(即也有可能被 AOP 代理增强)。

3_AnnotationAwareAspectJAutoProxyCreator的作用时机

既然IOC 容器的刷新动作中的第六步 registerBeanPostProcessors 方法已经把AnnotationAwareAspectJAutoProxyCreator创建就绪,接下来的bean对象在初始化阶段中它就会干预。

1.getBean - doCreateBean

bean 对象的创建流程从 getBean 开始依次是 doGetBean、createBean 和 doCreateBean,在 doCreateBean 方法中会真正地创建对象、属性赋值、依赖注入以及初始化流程的执行。在bean 对象本身的初始化流程全部执行完毕后,下一步要执行的是BeanPostProcessor的postProcessAfterIntialization方法。在这之前有一个小插曲,即 createBean 到 doCreateBean 的环节,该环节有一个InstantiationAwareBeanPostProcessor的拦截初始化动作,需要先注意一下这个动作。

2.postProcessBeforeInstantiation

postProcessBeforeInstantiation环节会检查是否提前增强bean

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
    Object cacheKey = getCacheKey(beanClass, beanName);    
    // 决定是否要提前增强当前bean
    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        // 被增强过的bean不会再次被增强
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
        // 基础类型的bean不会被提前增强、被跳过的bean不会被提前增强
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }    
    // 原型bean的额外处理:TargetSource
    // 此处的设计与自定义 TargetSource 相关,单实例bean 对象必定返回 nu11
    TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            this.targetSourcedBeans.add(beanName);
        }
        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
        Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    } else {
        return null;
    }
}

InfrastructureClass

Infrastructureclass直译为“基础类型”,它指代的是IOC容器中注册的基础类,包括切面类、切入点、增强器等 bean 对象。通过代码就可以了解到,如果一个 bean 对象本身是切面类/切人点/增强器等,那么它本身是参与 AOP底层的成员,不应该参与具体的被增强对象中。

protected boolean isInfrastructureClass(Class<?> beanClass) {
    boolean retVal = Advice.class.isAssignableFrom(beanClass) 
    || Pointcut.class.isAssignableFrom(beanClass) 
    || Advisor.class.isAssignableFrom(beanClass) 
    || AopInfrastructureBean.class.isAssignableFrom(beanClass);
    //logger ..:.

    return retVal;
}

被跳过的 bean 对象

检查完基础类的 bean 对象,紧接着要检查准备增强的bean 对象是否需要被跳过,如何理解“被跳过的 bean 对象”?

//父类AspectJAwareAdvisorAutoProxyCreator
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
	//加载增强器
    List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
    //逐个匹配,如果发现当前bean 对象的名称与增强器的名称一致,则认为不能被增强
    for (Advisor advisor : candidateAdvisors) {
		if (advisor instanceof AspectJPointcutAdvisor &&
				((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
			return true;
		}
	}
	return super.shouldSkip(beanClass, beanName);
}
// AbstractAutoProxyCreator
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
	//检查beanName代表的是不是原始对象(以.ORIGINAL结尾)
    return AutoProxyUtils.isOriginalInstance(beanName, beanClass);
}

这段逻辑的用意,它要检查的点是当前对象的名称是否与增强器的名称一致,换句话说,shouldSkip方法要检查当前准备增强的 bean 对象是不是一个还没有经过任何代理的原始对象,而检查的规则是观察bean 对象的名称是否带有.ORIGINAL的后缀,一般情况下项目中创建的bean不可能带有.ORIGINAL后缀,所以shouldSkip方法相当于判断当前创建的bean对象名称是否与增强器名称一致。

增强器的概念:这里简单解释一下。一个 Advisor 可以视为一个切入点+一个通知方法的结合体,对于 Aspect切面类中定义的通知方法,方法体+方法上的通知注解就可以看作一个 Advisor 增强器。

findCandidateAdvisors加载增强器的方法行完成后能获取到一个增强器,也就是我们自定义的 Aspect 中定义的通知方法。

TargetSource

有关 TargetSource 的设计,这里只简单解释一句:AOP 的代理其实不是代理目标对象本身,而是目标对象经过包装后的 TargetSource 对象

3.postProcessAfterInitialization

前面的 postProcessBeforeInstantiation方法拦截判断结束后,AnnotationAwareAspectJAutoProxyCreator 再发挥作用就要等到最后一步的postProcessAfterInitialization方法,该方法是真正地生成代理对象。

与其他 BeanPostProcessor 相似,AnnotationAwareAspectJAutoProxyCreator的作用时机通常是在 bean 对象的初始化阶段时介入处理,而代理对象的创建时机在初始化逻辑之后执行(即 postProcessAfterInitialization),这是由于Spring Framework 考虑到尽可能保证 Bean 的完整性,

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {            
            // 核心:构造代理            
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

postProcessAfterInitialization方法中的核心动作是中间的wrapIfNecessary这个动作从方法名上就很容易理解,如果有必要的话,wrapIfNecessary方法会给当前对象包装生成代理对象。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // ......    
    // 尝试决定是否需要进行代理对象的创建    
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 创建代理对象的动作        
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    // 记录缓存
    this.advisedBeans.put(cacheKey,Boolean.FALSE);
    return bean;
}

在这里插入图片描述

从源码逻辑中概括起来,创建代理对象的核心动作分为三个步骤。

  1. 判断决定是否是一个不会被增强的 bean 对象。
  2. 根据当前正在创建的 bean 对象去匹配增强器
  3. 如果有增强器,创建 bean 对象的代理对象。

整体了解了 AnnotationAwareAspectJAutoProxyCreator 的设计后,下面分阶段讲解整个 AOP 生命周期中的核心步骤。

3_收集增强器的逻辑

回到 AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation 方法中,前面已经提到了 findCandidateAdvisors 方法是收集候选增强器的动作,而这个方法本身又分为两个部分,分别是收集Spring Framework原生的增强器以及BeanFactory中所有 AspectJ 形式的切面并封装为增强器。

//AspectJAwareAdvisorAutoProxyCreator.class
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
        //...
}
protected List<Advisor> findCandidateAdvisors() {
	//根据父类的规则添加所有找到的spring原生的增强器
    List<Advisor> advisors = super.findCandidateAdvisors();
    //解析 BeanFactory中所有的AspectJ切面,并构建增强器
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }

    return advisors;
}

4_收集原生增强器

通过观察父类 AbstractAdvisorAutoProxyCreator,可以发现它委托了一个advisorRetrievalHelper 来处理 Spring Framework 原生的 AOP 增强器,这个 findAdvisorBeans 方法的幅比较长,下面拆解出核心的主干逻辑研究。

private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;
protected List<Advisor> findCandidateAdvisors() {
    Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
    return this.advisorRetrievalHelper.findAdvisorBeans();
}

1_检查现有的增强器 bean 对象

findAdvisorBeans 方法的第一部分源码主要是将IOC容器中所有类型为 Advisor 的实现类对象都获取到,并检查 IOC容器内部是否注册有增强器,如果没有注册增强器则不会行后续逻辑。注意这个 BeanFactoryUtils的beanNamesForTypeIncludingAncestors方法,底层会使用 getBeanNamesForType 方法来寻找 bean 对象的名称(单纯地寻找 bean 对象的名称不会创建具体的 bean对象,Spring Framework在此设计得很谨慎 ),感兴趣可以借助 IDE 自行研究,这里不展开探讨。

public List<Advisor> findAdvisorBeans() {
	//确定增强器 bean 对象名称的列表(如果尚未缓存)
    String[] advisorNames = this.cachedAdvisorBeanNames;
    if (advisorNames == null) {
    	//不要在这里初始化FactoryBeans:
    	//我们需要保留所有未初始化的常规bean 对象,以使自动代理创建者对其应用
        advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);
        this.cachedAdvisorBeanNames = advisorNames;
    }
    //如果当前IOC容器中没有任何增强器类型的bean 对象,直接返回
    if (advisorNames.length == 0) {
        return new ArrayList();
    } 
    //......

2_初始化原生增强器

如果上面检查到 IOC 容器中注册有原生的 Advisor 增强器,则下面会使用 BeanFactory的 getBean 方法初始化这些增强器。Sping Framework 原生的增强器模型因其设计和编写比较复杂,目前已经被淘汰,主流的通知编写还是以AspectJ 形式为主,所以这里仅需一般了解。

//.......
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
	if (isEligibleBean(name)) {
		if (this.beanFactory.isCurrentlyInCreation(name)) {
			//logger...
		}
		else {
			try {
				advisors.add(this.beanFactory.getBean(name, Advisor.class));
			}//catch.....
return advisors;

以上就是在父类 AbstractAdvisorAutoProxyCreator 中的 findCandidateAdvisors方法的逻辑,下面来看另一部分的委托aspectJAdvisorsBuilder

5_解析Aspect]切面封装增强器

从方法名上理解,aspectJAdvisorsBuilder.buildAspectJAdvisors 方法可以将Aspect 切面类转换为一个个增强器。既然是转换切面类,就必然有通知方法的解析、增强器的构造等步骤,这部分逻辑更加复杂,下面逐步探究。

1_逐个解析 IOC 容器中所有的 Bean 类型

buildAspectJAdvisors方法的第一部分核心逻辑是将 IOC 容器以及父容器中所有 bean 对象的名称全部提取出(直接声明父类为Object,显然是全部提取),之后会逐个解析这些 bean 对象对应的 class。通过 Debug 可以发现,获取的 bean 对象名称中包括 IOC 容器内部的一些组件(主启动类、自动配置类等)在内的所有 bean 对象名称。

public List<Advisor> buildAspectJAdvisors() {
	List<String> aspectNames = this.aspectBeanNames;
	if (aspectNames == null) {
		synchronized (this) {
			aspectNames = this.aspectBeanNames;
			if (aspectNames == null) {
				List<Advisor> advisors = new ArrayList<>();
				aspectNames = new ArrayList<>();
				//获取OC容器中的所有bean对象
				String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
						this.beanFactory, Object.class, true, false);
				for (String beanName : beanNames) {
					if (!isEligibleBean(beanName)) {
						continue;
					}
					//原文注释:我们必须小心,不要急于实例化bean,因为在这种情况下,IOC容器会缓存它们,但不会被
					//织入增强器
					Class<?> beanType = this.beanFactory.getType(beanName, false);
					if (beanType == null) {
						continue;
					}

Spring Framework 在这里控制得很好,它借助BeanFactory提取bean 对象的类型,而不是先 getBean 后再提取类型,这样可以确保 bean 对象不会被提前创建。而要在没有初始化 bean 对象的前提下获取bean 对象的Class,只能依靠 BeanDefinition 中封装的信息,所以在 AbstractBeanFactory 的getType 方法中可以看到合并 RootBeanDefinition 的动作,随后调用 RootBeanDefinition 的 getBeanclass 方法获取 bean对象的 class。

2_解析 Aspect切面类,构造增强器

紧接着第二部分逻辑要完成的工作是判断当前解析的 bean 对象的所属类型是否为切面类如果是则会进入if结构内部,将这个类中的通知方法都转换为 Advisor 增强器。

在这里插入图片描述

//buildAspectJAdvisors.......
//....
if (this.advisorFactory.isAspect(beanType)) {
	//当前解析bean 对象的所属类型是一个切面类
	aspectNames.add(beanName);
	AspectMetadata amd = new AspectMetadata(beanType, beanName);
	//下面是单实例切面 bean 对象会执行的流程
	if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
		MetadataAwareAspectInstanceFactory factory =
				new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
		//解析生成增强器
		List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
		if (this.beanFactory.isSingleton(beanName)) {
			this.advisorsCache.put(beanName, classAdvisors);
		}
		else {
			this.aspectFactoryCache.put(beanName, factory);
		}
		advisors.addAll(classAdvisors);
	}
	// .··

3_判断Class 是否是通知类

上述代码最上面的if结构中this.advisorFactory.isAspect(beanType)用来判断当前 Class 是不是通知类(切面类),除了检査类上是否标注了@Aspect 注解,源码中还多判断了一步,如下所示。

public boolean isAspect(Class<?> clazz) {
	//@Aspect 注解并且不是被 ajc编译器编译的
	return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}

官方文档:

只有当它有@Aspect 注解并且不是由 ajc 编译的,我们才认为这个 Class 是适合 SpringAOP系统使用的 AspectJ 切面。不用 ajc 编译的原因是,以代码风格(Aspect]语言)编写的切面在由带有 -1.5 标志的 ajc 编译时也存在注解,但它们不能被 Spring AOP 使用。

简单地说,Spring Framework的AOP 有整合 AspectJ 的部分,而原生的 AspectJ 也可以编写 Aspect 切面类,而这种切面在特殊的编译条件下生成的字节码中在类上也会标注@Aspect注解,但是 Spring Framework 并不能利用它,所以 isAspect 方法做了一个额外的判断处理避免了这种 Class 被误加载。

4_构造增强器

构造增强器的调用动作是结构中的advisorFactory.getAdvisors,这部分需要跳转到 ReflectiveAspectJAdvisorFactory中来看:

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
	// Aspect 切面类的class
	Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
	String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
	//校验.....
	validate(aspectClass);
	// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
	// so that it will only instantiate once.
	//此处利用 Decorator 装饰者模式,目的是确保Advisor 增强器不会被多次实例化
	MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
			new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
	List<Advisor> advisors = new ArrayList<>();
	//逐个解析通知方法,并封装为增强器
	for (Method method : getAdvisorMethods(aspectClass)) {
		// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
		// to getAdvisor(...) to represent the "current position" in the declared methods list.
		// However, since Java 7 the "current position" is not valid since the JDK no longer
		// returns declared methods in the order in which they are declared in the source code.
		// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
		// discovered via reflection in order to support reliable advice ordering across JVM launches.
		// Specifically, a value of 0 aligns with the default value used in
		// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
		Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
		if (advisor != null) {
			advisors.add(advisor);
		}
	}
	// If it's a per target aspect, emit the dummy instantiating aspect.
	//通过在装饰者内部的开始加入SyntheticInstantiationAdvisor 增强器
	//达到延迟初始化切面bean对象的目的
	if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
		Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
		advisors.add(0, instantiationAdvisor);
	}
	// Find introduction fields.
	//对@DeclareParent注解功能的支持(AspectJ的引介)
	for (Field field : aspectClass.getDeclaredFields()) {
		Advisor advisor = getDeclareParentsAdvisor(field);
		if (advisor != null) {
			advisors.add(advisor);
		}
	}
	return advisors;
}

整段源码阅读下来,方法执行的核心逻辑是解析 Aspect 切面类中的通知方法,只不过在上下文的逻辑中补充了一些额外的校验、处理等逻辑。这里面重点关注两个小动作:通知方法是如何收集的;增强器的创建需要哪些关键信息。

5_收集切面类中的通知方法

对于通知方法的收集动作,需要进人 getAdvisorMethods 方法中分析,如下列代码所示。getAdvisorMethods方法本身不难,就是把开发者定义的切面类中除通用的切入点表达式以外的所有方法都提取出来,并且在取出之后进行排序(排序的原则是按照 Unicode 编码),最后将通知方法返回。

获取一个切面类的所有通知方法:

private List<Method> getAdvisorMethods(Class<?> aspectClass) {
	final List<Method> methods = new ArrayList<>();
	ReflectionUtils.doWithMethods(aspectClass, method -> {
		// Exclude pointcuts
		//除pointcut之外
		if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
			methods.add(method);
		}
	}, ReflectionUtils.USER_DECLARED_METHODS);
	if (methods.size() > 1) {
		methods.sort(METHOD_COMPARATOR);
	}
	return methods;
}

6_创建增强器

getAdvisor 方法对应的逻辑是创建 Advisor 增强器。有人可能在此处会产生疑惑,上面的 getAdvisorMethods 方法仅提取了当前类中定义的非@Pointcut 方法,对于没有声明切入点表达式的方法是否会一并返回?对于这个问题其实不必多虑,源码在此处又做了一次过滤,如代码所示。

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
		int declarationOrderInAspect, String aspectName) {
	validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
	//解析切入点表达式
	AspectJExpressionPointcut expressionPointcut = getPointcut(
			candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
	//没有声明通知注解的方法也会被过滤
	if (expressionPointcut == null) {
		return null;
	}
	return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
			this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

Spring Framework本身设计得很严谨,注意看最后一行的构造方法中传入的关键参数,分别如下。

  • expressionPointcut:AspectJ 切人点表达式的封装。
  • candidateAdviceMethod:通知方法本体。
  • this::当的ReflectiveAspectJAdvisorFactory。
  • aspectInstanceFactory:上面的装饰的MetadataAwareAspectInstanceFactory。

去掉工厂本身后,其实增强器的结构就是一个切入点表达式 + 一个通知方法,与之前的推测完全一致。

7_解析通知注解上的切入点表达式

至此其实增强器本身已经没有什么问题,除此之外可以再关注一下创建增强器时 getPointcut 方法对应的切人点表达式解析,进入 getPointcut 方法中。

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
	//检索通知方法上的注解
	AspectJAnnotation<?> aspectJAnnotation =
			AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
	if (aspectJAnnotation == null) {
		return null;
	}
	//根据注解的类型,构造切入点表达式模型
	AspectJExpressionPointcut ajexp =
			new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
	ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
	if (this.beanFactory != null) {
		ajexp.setBeanFactory(this.beanFactory);
	}
	return ajexp;
}

//在检索范围内的注解
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
		Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
		
//检索通知方法上的注解
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
	for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
		AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
		if (foundAnnotation != null) {
			return foundAnnotation;
		}
	}
	return null;
}

getPointcut 方法的步骤很简单,它首先会搜寻通知方法上标注的注解,然后提取切人点表达式的信息并返回,而寻找AspectJ注解的 findAspectJAnnotationOnMethod 方法逻辑则是直接在 AbstractAspectJAdvisorFactory 中提前定义好了所有可以声明切入点表达式的注解(@Around、@Before、@After等),并在此处一一寻找,如果可以成功找到则返回。

getPointcut 方法只是把切入点表达式的内容以及参数等信息封装到 AspectJExpressionPointcut 中而已

6_原型切面 Bean 的处理

上面的逻辑只是对单实例切面 Bean 的处理和解析,下面的 else 部分是原型切面 Bean 的处理逻辑,如下所示。对于原型切面 Bean 的解析,核心解析动作依然是 advisorFactory.getAdvisors 方法,只是原型切面 Bean 的解析不会再使用advisorsCache 这个缓存区,这也说明原型切面 Bean 的解析是多次执行的。

buildAspectJAdvisors方法if逻辑的部分:

			else {
				// 检查单实例 Bean 并抛出异常
				MetadataAwareAspectInstanceFactory factory =
						new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
				this.aspectFactoryCache.put(beanName, factory);
				//解析 Aspect 切面类,构造增强器
				advisors.addAll(this.advisorFactory.getAdvisors(factory));
			}
		}
	}
	this.aspectBeanNames = aspectNames;
	return advisors;
}
//.....

7_增强器汇总

最后一部分是整理的环节,前面已经把所有的切面类都解析完毕,最后只需把这些构造好的增强器都集中到一个 List 中返回,如代码所示。经过以上一系列的步骤后,切人点表达式解析完毕,通知方法收集完毕,Advisor 增强器也顺利地创建完毕。创建好的增强器将会在普通 bean 对象的初始化阶段中待命,等待织入的动作。

buildAspectJAdvisors 方法最后的部分:

	//....
	// 如果 aspectNames 为空,返回一个空的列表
	if (aspectNames.isEmpty()) {
		return Collections.emptyList();
	}
	// 创建一个新的列表,用于存储 Advisor 对象
	List<Advisor> advisors = new ArrayList<>();
	
	// 遍历 aspectNames 列表中的每一个 aspectName
	for (String aspectName : aspectNames) {
		// 从缓存中获取对应的 Advisor 列表
		List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
		// 如果缓存中存在 Advisor 列表,则将其添加到 advisors 列表中
		if (cachedAdvisors != null) {
			advisors.addAll(cachedAdvisors);
		}
		else {
			// 如果缓存中不存在,获取对应的 MetadataAwareAspectInstanceFactory
			MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
			// 从 advisorFactory 创建 Advisor 列表并添加到 advisors 列表中
			advisors.addAll(this.advisorFactory.getAdvisors(factory));
		}
	}
	// 返回包含所有 Advisor 的列表
	return advisors;
}

8_代理对象生成的核心:wrapIfNecessary

回忆一下 此方法的主要步骤:1)获取增强器、2)创建代理对象

1_getAdvicesAndAdvisorsForBean

仅从方法名上就可以理解 getAdvicesAndAdvisorsForBean 方法要完成的工作,该方法会根据当前正在初始化的 bean 对象,匹配可供织入通知的增强器,这个方法又会调用下面的 findEligibleAdvisors 方法,而 findEligibleAdvisors 方法又分为3个步骤:

  • 获取增强器;
  • 筛选对当前bean对象有效的增强器;
  • 附加一些额外的增强器。
protected Object[] getAdvicesAndAdvisorsForBean(
		Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
	List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
	if (advisors.isEmpty()) {
		return DO_NOT_PROXY;
	}
	return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	//获取所有增强器 3_中 返回的结果即所谓的“候选增强器”。
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	//筛选出可以切入当前 bean 对象的增强器
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	//添加额外的增强器
	extendAdvisors(eligibleAdvisors);
	if (!eligibleAdvisors.isEmpty()) {
		//增强器排序
		eligibleAdvisors = sortAdvisors(eligibleAdvisors);
	}
	return eligibleAdvisors;
}

2_createProxy – 创建代理对象

创建代理对象的核心,底层仍然使用jdk / Cglib动态代理


protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
        @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    // ......
    // 代理工厂的初始化
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    // 根据AOP的设计,决定是否强制使用Cglib
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            // Cglib动态代理直接记录被代理bean的所属类即可
            proxyFactory.setProxyTargetClass(true);
        } else {
            // 解析被代理bean所属类的所有实现的接口
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    // 构造整合所有增强器
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);
    // ......
    // 创建代理对象
    return proxyFactory.getProxy(getProxyClassLoader());
}

9_代理对象的底层执行逻辑

核心逻辑:获取代理对象的增强器链,并逐个执行增强器,最后执行目标对象的方法。

在这里插入图片描述

10_AOP通知的执行顺序对比

在这里插入图片描述

三、模拟实现

1_底层切点、通知、切面


注意点:

  1. 底层的切点实现
  2. 底层的通知实现
  3. 底层的切面实现
  4. ProxyFactory 用来创建代理
    • 如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy
    • 如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy
    • 例外:如果目标是接口类型或已经是 jdk 代理,使用 JdkDynamicAopProxy
public class A15 {
    public static void main(String[] args) {
        /*
            两个切面概念
            aspect =
                通知1(advice) +  切点1(pointcut)
                通知2(advice) +  切点2(pointcut)
                通知3(advice) +  切点3(pointcut)
                ...
            advisor = 更细粒度的切面,包含一个通知和切点
         */

        // 1. 备好切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        // 2. 备好通知
        MethodInterceptor advice = invocation -> {
            System.out.println("before...");
            Object result = invocation.proceed(); // 调用目标
            System.out.println("after...");
            return result;
        };
        // 3. 备好切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        /*
           4. 创建代理
                a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现
                b. proxyTargetClass = false,  目标没有实现接口, 用 cglib 实现
                c. proxyTargetClass = true, 总是使用 cglib 实现
         */
        Target2 target = new Target2();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisor(advisor);
        factory.setInterfaces(target.getClass().getInterfaces());
        factory.setProxyTargetClass(false);
        Target2 proxy = (Target2) factory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();
        /*
            学到了什么
                a. Spring 的代理选择规则
                b. 底层的切点实现
                c. 底层的通知实现
                d. ProxyFactory 是用来创建代理的核心实现, 用 AopProxyFactory 选择具体代理实现
                    - JdkDynamicAopProxy
                    - ObjenesisCglibAopProxy
         */
    }

    interface I1 {
        void foo();

        void bar();
    }

    static class Target1 implements I1 {
        public void foo() {
            System.out.println("target1 foo");
        }

        public void bar() {
            System.out.println("target1 bar");
        }
    }

    static class Target2 {
        public void foo() {
            System.out.println("target2 foo");
        }

        public void bar() {
            System.out.println("target2 bar");
        }
    }
}

2_切点匹配


切点匹配:

  1. 常见 aspectj 切点用法
  2. aspectj 切点的局限性,实际的 @Transactional 切点实现
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;

public class A16 {
    public static void main(String[] args) throws NoSuchMethodException {
//        AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();
//        pt1.setExpression("execution(* bar())");
//        System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class));
//        System.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class));
//
//        AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
//        pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
//        System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class));
//        System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class));

        StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                // 检查方法上是否加了 Transactional 注解
                MergedAnnotations annotations = MergedAnnotations.from(method);
                if (annotations.isPresent(Transactional.class)) {
                    return true;
                }
                // 查看类上是否加了 Transactional 注解
                annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
                if (annotations.isPresent(Transactional.class)) {
                    return true;
                }
                return false;
            }
        };

        System.out.println(pt3.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class));
        System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class));
        System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class));

        /*
            学到了什么
                a. 底层切点实现是如何匹配的: 调用了 aspectj 的匹配方法
                b. 比较关键的是它实现了 MethodMatcher 接口, 用来执行方法的匹配
         */
    }


    static class T1 {
        @Transactional
        public void foo() {
        }
        public void bar() {
        }
    }

    @Transactional
    static class T2 {
        public void foo() {
        }
    }

    @Transactional
    interface I3 {
        void foo();
    }
    static class T3 implements I3 {
        public void foo() {
        }
    }
}

3_从 @Aspect 到 Advisor

1_代理创建器


注意点:

  1. AnnotationAwareAspectJAutoProxyCreator 的作用
    • 将高级 @Aspect 切面统一为低级 Advisor 切面。
    • 在合适的时机创建代理。
  2. findEligibleAdvisors 找到有【资格】的 Advisors
    • 有【资格】的 Advisor 一部分是低级的, 可以由自己编写, 如本例 A17 中的 advisor3。
    • 有【资格】的 Advisor 另一部分是高级的, 由解析 @Aspect 后获得。
  3. wrapIfNecessary
    • 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理。
    • 它的调用时机通常在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行。
package org.springframework.aop.framework.autoproxy;


import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author shenyang
 * @version 1.0
 * @info TestAop
 * @since 2024/8/17 20:26
 */
public class A17 {

    @Aspect//高级切面
    static class Aspect1{
        @Before("execution(* foo())")
        public void before(){
            System.out.println("aspect1 before.....");
        }
        @After("execution(* foo())")
        public void after(){
            System.out.println("aspect1 after.....");
        }
    }

    @Configuration
    static class Config {
        @Bean//低级切面
        public Advisor advisor3(MethodInterceptor advice3) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut, advice3);
        }
        @Bean
        public MethodInterceptor advice3(){
            return new MethodInterceptor() {
                @Override
                public Object invoke(MethodInvocation methodInvocation) throws Throwable {
                    System.out.println("advice3 before.....");
                    Object proceed = methodInvocation.proceed();
                    System.out.println("advice3 after.....");
                    return proceed;
                }
            };
        }
    }

    static class T1 {
        public void foo() {
            System.out.println("target1 foo");
        }
    }

    static class T2 {
        public void bar() {
            System.out.println("target2 bar");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("aspect1",Aspect1.class);
        context.registerBean("config",Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        context.refresh();
        AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        List<Advisor> advisors = creator.findEligibleAdvisors(T2.class, "target2");
        System.out.println("====================");
//        advisors.forEach(System.out::println);
        T1 o1 = (T1) creator.wrapIfNecessary(new T1(), "target1", "target1");
        T2 o2 = (T2) creator.wrapIfNecessary(new T2(), "target2", "target2");
        System.out.println(o1.getClass()+" "+o2.getClass());
        o1.foo();
        o2.bar();
    }

}

2_代理创建时机


注意点:

  1. 代理的创建时机
    • 初始化之后 (无循环依赖时)
    • 实例创建后, 依赖注入前 (有循环依赖时), 并暂存于二级缓存。
  2. 依赖注入与初始化不应该被增强, 仍应被施加于原始对象
package org.springframework.aop.framework.autoproxy;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

import javax.annotation.PostConstruct;

public class A17_1 {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(Config.class);
        context.refresh();
        context.close();
        // 创建 -> (*) 依赖注入 -> 初始化 (*)
        /*
            学到了什么
                a. 代理的创建时机
                    1. 初始化之后 (无循环依赖时)
                    2. 实例创建后, 依赖注入前 (有循环依赖时), 并暂存于二级缓存
                b. 依赖注入与初始化不应该被增强, 仍应被施加于原始对象
         */
    }

    @Configuration
    static class Config {
        @Bean // 解析 @Aspect、产生代理
        public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean // 解析 @Autowired
        public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
            return new AutowiredAnnotationBeanPostProcessor();
        }

        @Bean // 解析 @PostConstruct
        public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {
            return new CommonAnnotationBeanPostProcessor();
        }

        @Bean
        public Advisor advisor(MethodInterceptor advice) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut, advice);
        }

        @Bean
        public MethodInterceptor advice() {
            return (MethodInvocation invocation) -> {
                System.out.println("before...");
                return invocation.proceed();
            };
        }

        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean1 {
        public void foo() {

        }
        public Bean1() {
            System.out.println("Bean1()");
        }
        @Autowired public void setBean2(Bean2 bean2) {
            System.out.println("Bean1 setBean2(bean2) class is: " + bean2.getClass());
        }
        @PostConstruct public void init() {
            System.out.println("Bean1 init()");
        }
    }

    static class Bean2 {
        public Bean2() {
            System.out.println("Bean2()");
        }
        @Autowired public void setBean1(Bean1 bean1) {
            System.out.println("Bean2 setBean1(bean1) class is: " + bean1.getClass());
        }
        @PostConstruct public void init() {
            System.out.println("Bean2 init()");
        }
    }
}

3_@Before 对应的低级通知


注意点:

  1. @Before 前置通知会被转换为原始的 AspectJMethodBeforeAdvice 形式, 该对象包含了如下信息
    • 通知代码从哪儿来
    • 切点是什么(这里为啥要切点, 后面解释)
    • 通知对象如何创建, 本例共用同一个 Aspect 对象
  2. 类似的还有
    • AspectJAroundAdvice (环绕通知)
    • AspectJAfterReturningAdvice
    • AspectJAfterThrowingAdvice (环绕通知)
    • AspectJAfterAdvice (环绕通知)
package org.springframework.aop.framework.autoproxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class A17_2 {

    static class Aspect {
        @Before("execution(* foo())")
        public void before1() {
            System.out.println("before1");
        }

        @Before("execution(* foo())")
        public void before2() {
            System.out.println("before2");
        }

        public void after() {
            System.out.println("after");
        }

        public void afterReturning() {
            System.out.println("afterReturning");
        }

        public void afterThrowing() {
            System.out.println("afterThrowing");
        }

        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            try {
                System.out.println("around...before");
                return pjp.proceed();
            } finally {
                System.out.println("around...after");
            }
        }
    }

    static class Target {
        public void foo() {
            System.out.println("target foo");
        }
    }

    @SuppressWarnings("all")
    public static void main(String[] args) throws Throwable {

        AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
        // 高级切面转低级切面类
        List<Advisor> list = new ArrayList<>();
        for (Method method : Aspect.class.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Before.class)) {
                // 解析切点
                String expression = method.getAnnotation(Before.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                // 通知类
                AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
                // 切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            }
        }
        for (Advisor advisor : list) {
            System.out.println(advisor);
        }
        /*
            @Before 前置通知会被转换为下面原始的 AspectJMethodBeforeAdvice 形式, 该对象包含了如下信息
                a. 通知代码从哪儿来
                b. 切点是什么(这里为啥要切点, 后面解释)
                c. 通知对象如何创建, 本例共用同一个 Aspect 对象
            类似的通知还有
                1. AspectJAroundAdvice (环绕通知)
                2. AspectJAfterReturningAdvice
                3. AspectJAfterThrowingAdvice
                4. AspectJAfterAdvice (环绕通知)
         */

    }
}

4_静态通知调用


代理对象调用流程如下(以 JDK 动态代理实现为例)

  • 从 ProxyFactory 获得 Target 和环绕通知链,根据他俩创建 MethodInvocation,简称 mi
  • 首次执行 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
  • 进入环绕通知1,执行前增强,再次调用 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
  • 进入环绕通知2,执行前增强,调用 mi.proceed() 发现没有环绕通知,调用 mi.invokeJoinPoint() 执行目标方法
  • 目标方法执行结束,将结果返回给环绕通知2,执行环绕通知2 的后增强
  • 环绕通知2继续将结果返回给环绕通知1,执行环绕通知1 的后增强
  • 环绕通知1返回最终的结果

图中不同颜色对应一次环绕通知或目标的调用起始至终结

Proxy InvocationHandler MethodInvocation ProxyFactory MethodInterceptor1 MethodInterceptor2 Target invoke() 获得 Target 获得 MethodInterceptor 链 创建 mi mi.proceed() invoke(mi) 前增强 mi.proceed() invoke(mi) 前增强 mi.proceed() mi.invokeJoinPoint() 结果 后增强 结果 后增强 结果 Proxy InvocationHandler MethodInvocation ProxyFactory MethodInterceptor1 MethodInterceptor2 Target

1_通知调用过程


代理方法执行时会做如下工作:

  1. 通过 proxyFactory 的 getInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知
    • MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
    • AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
    • 这体现的是适配器设计模式
  2. 所谓静态通知,体现在上面方法的 Interceptors 部分,这些通知调用时无需再次检查切点,直接调用即可
  3. 结合目标与环绕通知链,创建 MethodInvocation 对象,通过它完成整个调用
package org.springframework.aop.framework;

import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.*;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class A18 {

    static class Aspect {
        @Before("execution(* foo())")
        public void before1() {
            System.out.println("before1");
        }

        @Before("execution(* foo())")
        public void before2() {
            System.out.println("before2");
        }

        public void after() {
            System.out.println("after");
        }

        @AfterReturning("execution(* foo())")
        public void afterReturning() {
            System.out.println("afterReturning");
        }

        @AfterThrowing("execution(* foo())")
        public void afterThrowing(Exception e) {
            System.out.println("afterThrowing " + e.getMessage());
        }

        @Around("execution(* foo())")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            try {
                System.out.println("around...before");
                return pjp.proceed();
            } finally {
                System.out.println("around...after");
            }
        }
    }

    static class Target {
        public void foo() {
            System.out.println("target foo");
        }
    }

    @SuppressWarnings("all")
    public static void main(String[] args) throws Throwable {

        AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
        // 1. 高级切面转低级切面类
        List<Advisor> list = new ArrayList<>();
        for (Method method : Aspect.class.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Before.class)) {
                // 解析切点
                String expression = method.getAnnotation(Before.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                // 通知类
                AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
                // 切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(AfterReturning.class)) {
                // 解析切点
                String expression = method.getAnnotation(AfterReturning.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                // 通知类
                AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);
                // 切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(Around.class)) {
                // 解析切点
                String expression = method.getAnnotation(Around.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                // 通知类
                AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);
                // 切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            }
        }
        for (Advisor advisor : list) {
            System.out.println(advisor);
        }

        /*
            @Before 前置通知会被转换为下面原始的 AspectJMethodBeforeAdvice 形式, 该对象包含了如下信息
                a. 通知代码从哪儿来
                b. 切点是什么
                c. 通知对象如何创建, 本例共用同一个 Aspect 对象
            类似的通知还有
                1. AspectJAroundAdvice (环绕通知)
                2. AspectJAfterReturningAdvice
                3. AspectJAfterThrowingAdvice (环绕通知)
                4. AspectJAfterAdvice (环绕通知)
         */

        // 2. 通知统一转换为环绕通知 MethodInterceptor
        /*

            其实无论 ProxyFactory 基于哪种方式创建代理, 最后干活(调用 advice)的是一个 MethodInvocation 对象
                a. 因为 advisor 有多个, 且一个套一个调用, 因此需要一个调用链对象, 即 MethodInvocation
                b. MethodInvocation 要知道 advice 有哪些, 还要知道目标, 调用次序如下

                将 MethodInvocation 放入当前线程
                    |-> before1 ----------------------------------- 从当前线程获取 MethodInvocation
                    |                                             |
                    |   |-> before2 --------------------          | 从当前线程获取 MethodInvocation
                    |   |                              |          |
                    |   |   |-> target ------ 目标   advice2    advice1
                    |   |                              |          |
                    |   |-> after2 ---------------------          |
                    |                                             |
                    |-> after1 ------------------------------------
                c. 从上图看出, 环绕通知才适合作为 advice, 因此其他 before、afterReturning 都会被转换成环绕通知
                d. 统一转换为环绕通知, 体现的是设计模式中的适配器模式
                    - 对外是为了方便使用要区分 before、afterReturning
                    - 对内统一都是环绕通知, 统一用 MethodInterceptor 表示

            此步获取所有执行时需要的 advice (静态)
                a. 即统一转换为 MethodInterceptor 环绕通知, 这体现在方法名中的 Interceptors 上
                b. 适配如下
                  - MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
                  - AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
         */
        Target target = new Target();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE); // 准备把 MethodInvocation 放入当前线程
        proxyFactory.addAdvisors(list);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);
        for (Object o : methodInterceptorList) {
            System.out.println(o);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        // 3. 创建并执行调用链 (环绕通知s + 目标)
        MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
                null, target, Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList
        );
        methodInvocation.proceed();



        /*
            学到了什么
                a. 无参数绑定的通知如何被调用
                b. MethodInvocation 编程技巧: 拦截器、过滤器等等实现都与此类似
                c. 适配器模式在 Spring 中的体现
         */

    }
}

2_模拟 MethodInvocation


注意点:

  1. proceed() 方法调用链中下一个环绕通知
  2. 每个环绕通知内部继续调用 proceed()
  3. 调用到没有更多通知了, 就调用目标方法

MethodInvocation 的编程技巧在实现拦截器、过滤器时能用上

package org.springframework.aop.framework;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/*
    模拟调用链过程, 是一个简单的递归过程
        1. proceed() 方法调用链中下一个环绕通知
        2. 每个环绕通知内部继续调用 proceed()
        3. 调用到没有更多通知了, 就调用目标方法
 */
public class A18_1 {

    static class Target {
        public void foo() {
            System.out.println("Target.foo()");
        }
    }

    static class Advice1 implements MethodInterceptor {
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("Advice1.before()");
            Object result = invocation.proceed();// 调用下一个通知或目标
            System.out.println("Advice1.after()");
            return result;
        }
    }

    static class Advice2 implements MethodInterceptor {
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("Advice2.before()");
            Object result = invocation.proceed();// 调用下一个通知或目标
            System.out.println("Advice2.after()");
            return result;
        }
    }


    static class MyInvocation implements MethodInvocation {
        private Object target;  // 1
        private Method method;
        private Object[] args;
        List<MethodInterceptor> methodInterceptorList; // 2
        private int count = 1; // 调用次数

        public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {
            this.target = target;
            this.method = method;
            this.args = args;
            this.methodInterceptorList = methodInterceptorList;
        }

        @Override
        public Method getMethod() {
            return method;
        }

        @Override
        public Object[] getArguments() {
            return args;
        }

        @Override
        public Object proceed() throws Throwable { // 调用每一个环绕通知, 调用目标
            if (count > methodInterceptorList.size()) {
                // 调用目标, 返回并结束递归
                return method.invoke(target, args);
            }
            // 逐一调用通知, count + 1
            MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
            return methodInterceptor.invoke(this);
        }

        @Override
        public Object getThis() {
            return target;
        }

        @Override
        public AccessibleObject getStaticPart() {
            return method;
        }
    }

    public static void main(String[] args) throws Throwable {
        Target target = new Target();
        List<MethodInterceptor> list = new ArrayList<>();

        list.add(new Advice1());
        list.add(new Advice2());
        MyInvocation invocation = new MyInvocation(target, Target.class.getMethod("foo"), new Object[0], list);
        invocation.proceed();
    }
}

5_动态通知调用


注意点:

  1. 通过 proxyFactory 的 getInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知
  2. 所谓动态通知,体现在上面方法的 DynamicInterceptionAdvice 部分,这些通知调用时因为要为通知方法绑定参数,还需再次利用切点表达式
  3. 动态通知调用复杂程度高,性能较低
package org.springframework.aop.framework.autoproxy;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.Field;
import java.util.List;

public class A19 {

    @Aspect
    static class MyAspect {
        @Before("execution(* foo(..))") // 静态通知调用,不带参数绑定,执行时不需要切点
        public void before1() {
            System.out.println("before1");
        }

        @Before("execution(* foo(..)) && args(x)") // 动态通知调用,需要参数绑定,执行时还需要切点对象
        public void before2(int x) {
            System.out.printf("before2(%d)%n", x);
        }
    }

    static class Target {
        public void foo(int x) {
            System.out.printf("target foo(%d)%n", x);
        }
    }

    @Configuration
    static class MyConfig {
        @Bean
        AnnotationAwareAspectJAutoProxyCreator proxyCreator() {
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean
        public MyAspect myAspect() {
            return new MyAspect();
        }
    }

    public static void main(String[] args) throws Throwable {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(MyConfig.class);
        context.refresh();

        AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        List<Advisor> list = creator.findEligibleAdvisors(Target.class, "target");

        Target target = new Target();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisors(list);
        Object proxy = factory.getProxy(); // 获取代理

        List<Object> interceptorList = factory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo", int.class), Target.class);
        for (Object o : interceptorList) {
            showDetail(o);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>");
        ReflectiveMethodInvocation invocation = new ReflectiveMethodInvocation(
                proxy, target, Target.class.getMethod("foo", int.class), new Object[]{100}, Target.class, interceptorList
        ) {};

        invocation.proceed();

        /*
            学到了什么
                a. 有参数绑定的通知调用时还需要切点,对参数进行匹配及绑定
                b. 复杂程度高, 性能比无参数绑定的通知调用低
         */
    }

    public static void showDetail(Object o) {
        try {
            Class<?> clazz = Class.forName("org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher");
            if (clazz.isInstance(o)) {
                Field methodMatcher = clazz.getDeclaredField("methodMatcher");
                methodMatcher.setAccessible(true);
                Field methodInterceptor = clazz.getDeclaredField("interceptor");
                methodInterceptor.setAccessible(true);
                System.out.println("环绕通知和切点:" + o);
                System.out.println("\t切点为:" + methodMatcher.get(o));
                System.out.println("\t通知为:" + methodInterceptor.get(o));
            } else {
                System.out.println("普通环绕通知:" + o);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四、总结

本章从 AOP 的开启注解@EnableAspectJAutoProxy出发,详细探讨注解驱动 AOP 底层的支撑组件,并针对核心组件AnnotationAwarespectJAutoProxyCreator 的初始化时机、作用机制进行深入剖析。AOP代理对象的工作离不开增强器、代理对象执行链等核心组件的支撑,掌握核心组件的工作时机和作用机制可以更容易地理解 AOP的工作原理。

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

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

相关文章

【C++】_string类字符串详细解析(1)

假如没有给你生命&#xff0c;你连失败的机会都没有。你已经得到了最珍贵的&#xff0c;还需要抱怨什么!&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;什么是string&#xff1f; •&#x1f330;1.string类的概念 •&#x1…

嘉立创PCB4层板

视频&#xff1a; 四层板PCB设计保姆级教程&#xff08;1&#xff09;&#xff1a;3.0HUB设计概述_哔哩哔哩_bilibili&#xff08;虽然是四层板实际这个还是两层板&#xff01;&#xff09; 不太建议看这个。 四层PCB 最简单终教学 高校培训课程 深入浅出 不会电路也能学会 设…

Nginx的核心!!! 负载均衡、反向代理

目录 负载均衡 1.轮询 2.最少连接数 3.IP哈希 4.加权轮询 5.最少时间 6.一致性哈希 反向代理 测试 之前讲过Nginx 的简介和正则表达式&#xff0c;那些都是Nginx较为基础的操作&#xff0c;Nginx 最重要的最核心的功能&#xff0c;当属反向代理和负载均衡了。 负载均…

YOLOv8实例分割+双目相机实现物体尺寸测量

1&#xff0c;YOLOv8实例分割原理介绍 YOLOv8是YOLO系列的最新版本&#xff0c;它在目标检测和实例分割方面都进行了显著的改进和创新。以下是YOLOv8实例分割原理的一些关键点&#xff1a; 先进的骨干和颈部架构&#xff1a;YOLOv8采用了先进的骨干和颈部架构来提高特征提取和物…

分治,1875C - Jellyfish and Green Apple

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1875C - Jellyfish and Green Apple 二、解题报告 1、思路分析 n 个苹果…

小阿轩yx-Kubernetes Pod入门

小阿轩yx-Kubernetes Pod入门 前言 Kubernetes 中 一个重要的概念就是 Pod&#xff08;豆荚&#xff09;并不直接管理容器&#xff0c;它的最小管理单元叫做 Pod。 Docker 应用中 把一个应用程序封装在一个镜像中&#xff0c;之后启动这个镜像并映射个宿主机端口号&#x…

03、Redis实战:商户查询缓存、缓存更新策略、缓存穿透、缓存雪崩、缓存击穿

2、商户查询缓存 2.1 什么是缓存? 什么是缓存? 就像自行车,越野车的避震器 举个例子:越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样; 同样,实际开发中,系统也需要"避震器&qu…

2、Unity【基础】Mono中的重要内容

Unity基础 MonoBehavior中的重要内容 文章目录 Mono中的重要内容1、延迟函数1、延迟函数概念2、延迟函数使用3、延迟函数受对象失活销毁影响思考1 利用延时函数实现计时器思考2 延时销毁 2、协同程序1、Unity是否支持多线程2、协同程序概念3、协同程序和线程的区别4、协程的使用…

APP架构设计_1.官方应用架构指南

1.官方应用架构指南 1.1架构的原则 应用架构定义了应用的各个部分之间的界限以及每个部分应承担的职责。谷歌建议按照以下原则设计应用架构。 分离关注点通过数据模型驱动界面单一数据源单向数据流 1.2谷歌推荐的应用架构 每个应用应至少有两个层&#xff1a; 界面层 - 在屏…

近视防控明星:蔡司小乐圆中期临床数据详解

近视防控明星&#xff1a;蔡司小乐圆中期临床数据详解 小乐圆镜片作为近视防控镜片里的明星产品&#xff0c;从22年5月上市以来防控效果就一直备受大家的关注。而最近中期临床试验的结果&#xff0c;给家长孩子吃了一颗定心丸。 本次实验中&#xff0c;240位受试者被随机分成三…

基于springboot框架的电影订票系统_wqc3k

TOC springboot611基于springboot框架的电影订票系统_wqc3k--论文 绪 论 1.1研究背景和意义 随着科学技术的不断发展&#xff0c;计算机现在已经成为了社会的必需品&#xff0c;人们通过网络可以获得海量的信息&#xff0c;这些信息可以和各行各业进行关联&#xff0c;电影…

你应该停止使用的 7 个已弃用的 Python 库

欢迎来到雲闪世界。升级您的 Python 工具包&#xff1a;发现 7 个应停止使用的过时库以及替代它们的功能。最近&#xff0c;我回顾了 Python 的新特性&#xff0c;发现每个版本都引入了创新&#xff0c;使我们的日常开发工作变得更加轻松。 这让我意识到科技是一门永无止境的艺…

8.21 QT

1.思维导图 2. 服务器端 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer>//服务器类 #include <QMessageBox> #include <QDebug> #include <QList> #include <QTcpSocket>QT_BEGIN_NAMESPACE names…

免费高画质提取PPT/Word/Excel中的图片工具

下载地址&#xff1a;https://pan.quark.cn/s/134ccc35b8a2 软件简介&#xff1a; 好不容易搞到一个几十上百MB的ppt&#xff0c;想导出里面的图片进行二次加工&#xff0c;却被ppt超低画质的图片另存为功能劝退&#xff0c;明知里面全是高清图片&#xff0c;走时却是两手空空…

【C++从练气到飞升】14---深入浅出继承

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书&#x1f389; 目录 ⛳️推荐 一、继承的概念及定义 1.1 继承的概念 1.2 继承定义 1.2.1 定义格式 1.2.2 继承方式和访问限定符…

重新认识AbstractQueuedSynchronizer

开篇之前&#xff0c;烦请诸位允许我附庸风雅一次。近期因诸事繁杂&#xff0c;心情颇低落&#xff0c;遂于喜马拉雅APP中收听《老子》一文。其中的第八十一章《结天道》一文于我感悟颇深&#xff1a;和大怨&#xff0c;必有余怨&#xff0c;报怨以德&#xff0c;焉可以为善&am…

C++,std::bind 详解

文章目录 1. 概述2. 基本用法2.1 使用占位符2.2 示例 3. 总结 1. 概述 std::bind 是 C11 引入的一个功能&#xff0c;它允许你将函数&#xff08;或成员函数、函数对象&#xff09;与其参数绑定&#xff0c;生成一个新的可调用对象。这个功能在需要将函数及其参数一起传递给其…

DNF攻略:护石符文体系辅助详解,VMOS云手机助攻核心玩法!

在DNF游戏中&#xff0c;护石符文系统是提升角色实力的重要部分。当前版本中&#xff0c;护石符文体系经过了优化&#xff0c;使得获取方式更加便捷。以下是护石符文体系的详细介绍&#xff0c;以及如何使用VMOS云手机来更高效地管理和利用这一系统。 一、护石符文体系简介 护…

HarmonyOS 地图服务:深度解析其丰富功能与精准导航实力

目录 前期准备打造个性化地图&#xff1a;聚焦创建地图功能导入Map Kit相关模块通过MapOptions初始化地图切换地图类型设置地图中心点及层级展示定位按钮展示比例尺指定地图的日间夜间模式 通过MapComponentController对象方法控制地图切换地图类型开启3D建筑图层在指定的持续时…

【安当产品应用案例100集】008-UKEY在工业自动化数据传输中应用

工业自动化中的数据传输是确保生产过程高效、稳定运行的关键环节。工业自动化系统中&#xff0c;一般会有一个远程的客户端&#xff0c;负责将各个传感器、控制器等设备产生的信息传递到服务端&#xff0c;以实现生产过程的自动化控制和监控。它对于提高生产效率、降低生产成本…