Spring之我见 - 从IOC谈到AOP实现原理

news2024/12/21 3:53:08

前言

以前写过一篇文章, 专门讲了 Spring 的动态代理实现原理 从代理模式再出发!Proxy.newProxyInstance的秘密, 这一次我们探究下动态代理模式比较重量级的应用 – Spring AOP 的源码实现。 本文重在讲主流程, 但为了让流程更清楚,会从源头讲起,所以会补上很多Spring IOC 的知识。

前菜:IOC的相关知识

如何找到需要被AOP注解的类?

ConfigurationClassPostProcessor(BeanDefinitionRegistryPostProcessor)

大部分时候我们只知道注解扫描注入的方便性,但是细究的话还是可以看看 Spring 怎么找到这些类的,比如在这个题目下如何找到需要被代理的对象?

首先我们介绍IOC组件中比较重要的类 - BeanDefinitionRegistryPostProcessor , 先看一下类注释:

拓展了标准的 BeanFactoryPostProcessor SPI 机制, 允许在 BeanFactoryPostProcessor 介入操作前 注册 bean definition 到容器中。 

Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in。 In particular, BeanDefinitionRegistryPostProcessor may register further bean definitions which in turn define BeanFactoryPostProcessor instances。

然后总结下必须要知道的三个类:

  • BeanDefinitionRegistryPostProcessor 侧重于注册 BeanDefinition 对象的信息。 这是 Spring 对外开放的一个拓展(SPI),方便第三方注册自己的组件到 Spring IOC。
  • BeanFactoryPostProcessor 更侧重于对已经注册的 BeanDefinition 的属性进行修改,虽然也可以注册bean。
  • BeanDefinitionRegistry 是管理 BeanDefinition 的大总管,当然也可以注册修改 BeanDefinition 对象的信息 ,是 beanFactory 必须实现的接口。

知道了 BeanDefinitionRegistryPostProcessor 的作用, 然后我们介绍本节的主角 - ConfigurationClassPostProcessor 。

ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor, 它的功能在于处理@Configuration class,试图通过 @Configuration class 找到更多需要被IOC管理的类对象。 这个类是整个 Spring 启动流程最先注册实例化的类。下面是它的注册过程:

  • 从 SpringApplication main 方法出发,跟踪到createApplicationContext()方法,这是在创建 ApplicationContext。
- class SpringApplication 
    - run(String... args) 
        - createApplicationContext()
  • 然后初始化了一个 AnnotationConfigApplicationContext(),这个初始化方法很重要,它往Spring容器注册了ConfigurationClassPostProcessor。
public AnnotationConfigApplicationContext() {
    StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
    this.reader = new AnnotatedBeanDefinitionReader(this);
    createAnnotatedBeanDefReader.end();
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}
  • 继续初始化 AnnotatedBeanDefinitionReader,最后执行 registerAnnotationConfigProcessors(),这个方法直接把ConfigurationClassPostProcessor 注册成 Bean Definition 放到 beanDefinitionMap 中。
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
@Configuration

到目前为止, 已经把 ConfigurationClassPostProcessor 的由来说明白了, 现在继续讲ConfigurationClassPostProcessor 干的事情。之前说过, ConfigurationClassPostProcessor 主要是处理 @Configuration class ,那理论上第一个应该被 ConfigurationClassPostProcessor 处理的类是什么? 应该是 执行main函数的启动类

在启动类上, @SpringBootApplication 内部其实有几个关键的注解, 其中一个就是 @Configuration ,说明 DemoApplication 类也是一个配置类,需要被 ConfigurationClassPostProcessor 处理。 那么 DemoApplication 什么时候注册成 bean definition,然后放到 beanDefinitionMap 中呢?

  • run()方法有两个参数,参照注释, 一个是启动类的 class 对象(primarySource), 一个是启动参数。 这个 class 对象就很关键。
/**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified source using default settings。
     * @param primarySource the primary source to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }
}
  • 构造器里把启动类赋值给局部变量 primarySources。
    public SpringApplication(ResourceLoader resourceLoader, Class<?>。。。 primarySources) {
        ....
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        ....
    }
  • 经过一系列调用链把 primarySources 注册成 bean definition,这里省略很多,大家可以自行调试。
- class SpringApplication 
    - run(String... args) 
        - prepareContext(...)
            - load(...)
                - loader.load()
                    ...
                        - registerBean

至此,启动类也被注册成了 bean definition,供下文 ConfigurationClassPostProcessor 扫描使用。

@ComponentScan

在这里插入图片描述

到此为止, IOC 容器一共注册了 7 个 BeanDefinition, 包括了2个对目前流程最重要的类

  • org.springframework.context.annotation.internalConfigurationAnnotationProcessor(ConfigurationClassPostProcessor)
  • com.example.demo.DemoApplication (启动类)

现在我们看看 ConfigurationClassPostProcessor 是如何起作用的。 对于 Spring 的 IOC 流程, 我们应该知道大名鼎鼎的 refresh() 方法,它解析我们给的 ioc 配置(java-base config,xml etc),并生成对象。 在执行 invokeBeanFactoryPostProcessors 的时候, 就会遍历之前预置的 BeanDefinitionRegistryPostProcessor 对象(包括ConfigurationClassPostProcessor)。

    private static void invokeBeanDefinitionRegistryPostProcessors(
            Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {

        for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
            StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
                    .tag("postProcessor", postProcessor::toString);
            postProcessor.postProcessBeanDefinitionRegistry(registry);
            postProcessBeanDefRegistry.end();
        }
    }  

  • 正式进入 ConfigurationClassPostProcessor 的 processConfigBeanDefinitions 方法。
//正式进入ConfigurationClassPostProcessor 的 processConfigBeanDefinitions 方法
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

        //这里获取到  到目前位置找到的7个BeanDefinition, 逐个循环找 Configuration classes
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
            //具体的查找逻辑,选取里面重要的一行判断,看到 metadata.getAnnotationAttributes(Configuration.class.getName()) 是不是就一目了然了. 
            // Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

        //找到 demoApplication 是Configuration classes
		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}


        // 之前大家用的很多的 @Order, 会在这里进行重新排序
        // Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});


        //ConfigurationClassParser 开始解析 demoApplication ,解析的目的是为了获得 demoApplication 导入的其它对象
        do {
			ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

            ....
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			parser.parse(candidates);
			parser.validate();
            ....
            }
		while (!candidates.isEmpty());
  • 继续跟代码,doProcessConfigurationClass 方法比较关键,我们都知道 @ComponentScan 注解很关键,它几乎扫描全项目所有的需要 Spring 托管的对象(所有被 @Component 注解直接或者间接标记的类,继承了@Component的注解包括:@Controller、@RestController、@Repository、@Service、@Configuration等),而 @ComponentScan 就在下述代码中被扫描处理,ComponentScan 的扫描细节就直接忽略。
- parse: ConfigurationClassParser
    - doProcessConfigurationClass: ConfigurationClassParser
        - doScan: ClassPathBeanDefinitionScanner
            - 。。。


    //doProcessConfigurationClass 代码逻辑中扫描 Configuration class 是否有 @ComponentScan,有的话就扫描注册所有的 @Component 对象
    // Process any @ComponentScan annotations
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils。attributesForRepeatable(
                sourceClass。getMetadata(), ComponentScans。class, ComponentScan。class);
        if (!componentScans。isEmpty() &&
                !this。conditionEvaluator。shouldSkip(sourceClass。getMetadata(), ConfigurationPhase。REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this。componentScanParser。parse(componentScan, sourceClass。getMetadata()。getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder。getBeanDefinition()。getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder。getBeanDefinition();
                    }
                    if (ConfigurationClassUtils。checkConfigurationClassCandidate(bdCand, this。metadataReaderFactory)) {
                        parse(bdCand。getBeanClassName(), holder。getBeanName());
                    }
                }
            }
        }

        //扫描 Configuration class 是否有 @Import,有的话就注册@Import要导入的类
        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

        //同理处理 @ImportResource
        // Process any @ImportResource annotations
        AnnotationAttributes importResource =
                AnnotationConfigUtils。attributesFor(sourceClass。getMetadata(), ImportResource。class);
        if (importResource != null) {
            String[] resources = importResource。getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource。getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this。environment。resolveRequiredPlaceholders(resource);
                configClass。addImportedResource(resolvedResource, readerClass);
            }
        }

至此,需要被代理的类被 Spring 全部找到,但是这还远远不够,我们需要引入AOP框架取处理这些类。

如何引入AOP框架?

经过 @ComponentScan 扫描,需要被代理的类 TestAspectJ 和 TestService 被 Spring 找到并注册到 beanDefinitionMap( TestAspectJ 和 TestService 的内容作用机制后面再讲),后面先引入 spring-boot-starter-aop 的组件, 我们用的 Spring boot 版本是 2.4.4 , 接下来就说明下这个组件是如何启用的。

spring-boot-autoconfigure(AopAutoConfiguration)

以前用过 AOP 的都知道, 开启 Spring AOP 需要一个注解 @EnableAspectJAutoProxy,但在启动类 DemoApplication 上并没有发现这个注解,因为 Spring boot 有一个 spring-boot-autoconfigure 模块, autoconfigure 会自动帮我们启用一些组件, 就比如我们现在需要的 aop 模块。

我们先看看 spring-boot-autoconfigure 模块里面的 AopAutoConfiguration 代码。

AopAutoConfiguration 默认是开启的,因为从 AopAutoConfiguration 的注解 @ConditionalOnProperty来看, `spring.aop.auto matchIfMissing` 为true, 说明`spring.aop.auto`不存在显性配置的时候, 条件就是成立的, 而且根据 `spring.aop.proxy-target-class` 的设置,默认会先启用CglibAutoProxyConfiguration,也就是使用cglib来实现aop的逻辑。


@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
				matchIfMissing = false)
		static class JdkDynamicAutoProxyConfiguration {

		}

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		static class CglibAutoProxyConfiguration {

		}

	}
}
@EnableAutoConfiguration(@Import)

那上面的 AopAutoConfiguration 是怎么被 Spring 启动的时候加载的呢? 我们还要接着上章 doProcessConfigurationClass 方法说起,之前它解析了启动类 DemoApplication 的 @ComponentScan 注解,下面还继续解析了 @Import 注解。

  • 这个方法处理了 DemoApplication 里面的 @SpringBootApplication -> @EnableAutoConfiguration -> @Import(AutoConfigurationImportSelector.class) 注解,并把 AutoConfigurationImportSelector 对象放进了 deferredImportSelectors list 中。
class ConfigurationClassParser doProcessConfigurationClass

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
  • 等 DemoApplication 被彻底 parse 完成,在方法返回时,会最后处理 deferredImportSelectors list
class ConfigurationClassParser doProcessConfigurationClass

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				......
			}
		......
		}

		//最后处理 deferredImportSelectors list 中的元素
		this.deferredImportSelectorHandler.process();
	}

//解析deferredImportSelectors list的流程逻辑
 - this.deferredImportSelectorHandler.process();
	- processGroupImports()
		- grouping.getImports()
			- this.group.process
				  //回调AutoConfigurationImportSelector的getAutoConfigurationEntry方法, 找到 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
				- AutoConfigurationImportSelector getAutoConfigurationEntry
					//因为 AopAutoConfiguration 也有注解 @Configuration, 所以按照 @Configuration class的逻辑去处理(执行跟启动类DemoApplication一样的逻辑)
					- processConfigurationClass()
				

这里再稍微细究下AutoConfigurationImportSelector的getAutoConfigurationEntry方法怎么找的。

SpringFactoriesLoader 类专门负责轮询扫描所有的 META-INF/spring.factories 文件,当你传参 org.springframework.boot.autoconfigure.EnableAutoConfiguration时,就会从 spring-boot-autoconfigure 包里的 META-INF/spring.factories 搜索 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的对象列表。

  • getSpringFactoriesLoaderFactoryClass() 为 EnableAutoConfiguration.class
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());

在这里插入图片描述

@Import 导入的类并没有直接被注册到 beanDefinitionMap,等 demoApplication 被 ConfigurationClassParser 解析完毕后,通过 this.reader.loadBeanDefinitions(configClasses) 方法注册到 beanDefinitionMap。

....
    parser.parse(candidates);
    parser.validate();

    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser。getConfigurationClasses());
    configClasses.removeAll(alreadyParsed);

    // Read the model and create bean definitions based on its content
    if (this.reader == null) {
        this.reader = new ConfigurationClassBeanDefinitionReader(
                registry, this.sourceExtractor, this.resourceLoader, this.environment,
                this.importBeanNameGenerator, parser.getImportRegistry());
    }
    //将parser解析结果注册到 beanDefinitionMap 
    this.reader.loadBeanDefinitions(configClasses);
    alreadyParsed.addAll(configClasses);
....

我们总结下这一章, 首先 spring aop 的启用得益于 spring-boot-autoconfigure 模块。 而 spring-boot-autoconfigure 模块是通过启动类的@Import(AutoConfigurationImportSelector.class) 导入的。 所以说别看启动类就一个文件,它在项目启动过程中做了很重要的角色。

AnnotationAwareAspectJAutoProxyCreator

上面我们千辛万苦把 JdkDynamicAutoProxyConfiguration 注册到 beanDefinitionMap(默认应该是启用CglibAutoProxyConfiguration,但是我们配置了 spring.aop.proxy-target-class=false ), JdkDynamicAutoProxyConfiguration本身并不重要,重要的在于方法注解 @EnableAspectJAutoProxy→ @Import(AspectJAutoProxyRegistrar.class),在 this.reader.loadBeanDefinitions(configClasses) 的过程中,会执行 registerBeanDefinitions 回调方法。 并且把 AnnotationAwareAspectJAutoProxyCreator 对象注册进来。

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * Register, escalate, and configure the AspectJ auto proxy creator based on the value
     * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
     * {@code @Configuration} class。
     */
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        // 往beandefinition注册 `AnnotationAwareAspectJAutoProxyCreator`
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

    }

}

AnnotationAwareAspectJAutoProxyCreator 是一个 InstantiationAwareBeanPostProcessor(BeanPostProcessor)对象,这个对象作用非常大, 我们看一下它的注释, 处理所有的@AspectJ修饰过的类,为什么要处理这个类? 因为所有的 AOP 逻辑(代理谁,代理的逻辑)都写在@AspectJ修饰过的类里, Spring需要一个专门的组件来收集处理我们在@AspectJ类定义的AOP信息, 而这就是AnnotationAwareAspectJAutoProxyCreator需要处理的事。

处理所有的@AspectJ修饰过的类

AspectJAwareAdvisorAutoProxyCreator subclass that processes all AspectJ annotation aspects in the current application context。

小结

这一篇我们并没有说 AOP 在 Spring 是怎么大显神威的。 而是先介绍了一系列我们接下来必不可少的组件。 这些组件对于理解 Spring 支持 AOP 特别重要。 我们再简单复习下:

  • ConfigurationClassPostProcessor。 Spring 启动首批注册的组件, 专门用来处理 @Configuration class, 正因为有它,才能引出其它待引入的组件
  • AutoConfigurationImportSelector。 写在启动类@Import里, ConfigurationClassPostProcessor 解析启动类的时候会回调 AutoConfigurationImportSelectorgetAutoConfigurationEntry 方法。从而引出 spring-boot-autoconfigure 模块。
  • AopAutoConfiguration, 隶属于 spring-boot-autoconfigure 模块中的一个类,专门用来提供 CglibAutoProxyConfigurationJdkDynamicAutoProxyConfiguration , 这两者本身无逻辑,但是通过它们引入了核心类
  • AnnotationAwareAspectJAutoProxyCreatorAopAutoConfiguration 顺便也通过 @import 引入了 AspectJAutoProxyRegistrarAspectJAutoProxyRegistrar 又注册了 AnnotationAwareAspectJAutoProxyCreatorAnnotationAwareAspectJAutoProxyCreator 负责找出所有 @AspectJ修饰过的对象,为实现 AOP 做准备。

上菜:AOP是如何代理的

先把我本地的demo应用代码放出来。前面说到, 我们已经准备好了各种食材,这一篇真正开始看看 AOP 是怎么代理某个对象的.

@Aspect
@Component
public class TestAspectJ {

    @Pointcut("execution(* com.example.demo.service.TestService.test())")
    public void testPointcut() {
    }

    @Before("testPointcut()")
    public void before() {
        System.out.println("i am coming");
    }

    @After("testPointcut()")
    public void after() {
        System.out.println("i will opt out");
    }
}


public interface TestInterface {

    void test();
}


@Service
public class TestService implements TestInterface {

    @Override
    public void test() {
        System.out.println("i am here~");
    }
}

创建代理对象切入点

postProcessBeforeInstantiation

postProcessBeforeInstantiation 为 InstantiationAwareBeanPostProcessor 定义的方法。触发时机在 对象实例化前调用,AnnotationAwareAspectJAutoProxyCreator 利用这个方法,做了两件事情:

  • 在这里查找所有的 @Aspect 修饰的类.
  • 通过TargetSourceCreator提前生成代理对象
查找所有的 @Aspect 修饰的类

上面的总结也提到了, AnnotationAwareAspectJAutoProxyCreator 利用 postProcessBeforeInstantiation 方法,查找所有的 @Aspect 修饰的类.下面就是具体查找的代码.

当创建整个 Spring应用 第一个对象时, 会走 createBean 方法, 然后经过AnnotationAwareAspectJAutoProxyCreatorpostProcessBeforeInstantiation 方法的时候,开始找所有的 @Aspect 修饰的对象,下面是调用链.

- org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
    - org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation
        - org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#shouldSkip
            - org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors


//当走到 `findCandidateAdvisors` 时,首先找到所有的 `beanNames` ,然后开始循环遍历. `TestAspectJ` 也是其中之一
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                            this.beanFactory, Object.class, true, false);


//判断这个类是不是有 `@Aspect` 修饰,这时候判断的 beanType 如果是 `TestAspectJ` ,那么就会返回 true
this.advisorFactory.isAspect(beanType)


//确定是 `@Aspect` 对象后,会加入缓存,因为 `findCandidateAdvisors` 后续还会用到,所以第一次已经找到后,后续就直接从缓存中获取,提升效率. 
aspectNames.add(beanName);
this.aspectBeanNames = aspectNames;


//将 `@Aspect` 对象转换为 `Advisor` 对象(Base interface holding AOP advice (action to take at a joinpoint)),因为后续 AOP 逻辑依靠的是 `Advisor`.
MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);

//同样的,Advisor对象也会被缓存起来, 以备后用.
this.advisorsCache.put(beanName, classAdvisors);

看到这里,其实在 Spring IOC 阶段, 每个对象执行 postProcessBeforeInstantiation 的时候,再准确的说是 findCandidateAdvisors 方法, 会从 beanFactory 找所有符合的 AOP advice. 这时可能会问,spring 管理的对象那么多,每个都执行一遍是不是太慢了?所以Spring的设计是只有第一次会完整执行逻辑, 随后获取的都是缓存。

提前生成代理对象
  1. 需要提供AbstractBeanFactoryBasedTargetSourceCreator的实现类
  2. 需要提供TargetSource接口的实现。
  3. 需要调用 AbstractAutoProxyCreator#setCustomTargetSourceCreators来设置TargetSourceCreator。
wrapIfNecessary

真正开始代理这个对象有三个入口,上面已经说了一个,还有两个。这两个入口都调用wrapIfNecessary方法。先介绍wrapIfNecessary的大致流程。


    - org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
        - org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean
            - org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
                - org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findCandidateAdvisors

上面的调用链有没有发现熟悉的方法? 没错, findCandidateAdvisors方法又出现了, 具体来说我们先从 findEligibleAdvisors 方法看起.

    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        //直接从缓存 找到所有的 `AOP advice`
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        //再看看这个对象能不能跟 candidateAdvisors 匹配上
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = sortAdvisors(eligibleAdvisors);
        }
        //返回能跟这个对象匹配上的 `AOP advice`
        return eligibleAdvisors;
    }

findEligibleAdvisors 作用就是 筛选出能与当前对象匹配的 AOP advice . 从下图可知,能与TestService 匹配的 advice 正好就是 TestAspectJ 类所写的内容.

在这里插入图片描述

到这一步,AOP 已经到了创建代理对象的步骤:

  1. 新建 ProxyFactory 对象,对象包含了 Advisors待代理的对象 等必要的信息.
  2. createAopProxy 方法确定代理的模式,jdk 动态代理 或者 cglib.
    • 之前设置了 spring.aop.proxy-target-class=false ,所以这里会做判断选择 JdkDynamicAopProxy.
  3. JdkDynamicAopProxy的getProxy方法会调用 Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);.到这里是不是逻辑就很清楚了,Proxy.newProxyInstance做了什么直接看我另一篇博客 从代理模式再出发!Proxy.newProxyInstance的秘密

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!NativeDetector.inNativeImage() &&
                (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);
        }
    }
getEarlyBeanReference

在 getBean 最开始,Spring 会先从缓存尝试获取对象,如果这个对象存在循环依赖,那么调用链路会有变化,会通过ObjectFactory调用getEarlyBeanReference 提前暴露对象,对于代理类来说,提前暴露也就意味着代理的过程也要提前,所以这个入口纯粹是为了循环依赖的情况服务的。

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		this.earlyBeanReferences.put(cacheKey, bean);
		return wrapIfNecessary(bean, beanName, cacheKey);
	}
postProcessAfterInitialization

postProcessAfterInitialization 触发时机在目标对象初始化之后,且属性已经设置。并且为了避免跟前面getEarlyBeanReference入口重复执行,特意多加了一个if判断。

	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyBeanReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

修订记录

2024-12-19 重新修订文章,两篇并成一篇,并调整文章结构。

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

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

相关文章

基于 SSM 框架 Vue 电脑测评系统:赋能电脑品质鉴定

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为一个一般的用户都开始注重与自己的信息展示平台&#xff0c;实现基于SSM框架的电脑测评系统在技术上已成熟。本文介绍了基于SSM框架的电脑测评系统的开发全过程。通过分析用户对于基于SSM框架的电脑测评系统的…

[react] 优雅解决typescript动态获取redux仓库的类型问题

store.getState()是可以获取总仓库的 先拿到函数的类型 再用ReturnType<T> 它是 TypeScript 中的一个内置条件类型&#xff0c;用于获取某个函数类型 T 的返回值类型 代码 // 先拿总仓库的函数类型type StatefuncType typeof store.getState;//再拿函数类型T的返回值类…

mysql中与并发相关的问题?

今天我们来聊聊 MySQL 中与并发相关的一些问题。作为一名资深 Python 开发工程师&#xff0c;我觉得这些问题不仅关乎数据库的稳定性和数据的一致性&#xff0c;更与我们的代码实现和业务逻辑密切相关。 尤其是在高并发环境下&#xff0c;如何保证数据的一致性&#xff0c;如何…

(补)算法刷题Day19:BM55 没有重复项数字的全排列

题目链接 给出一组数字&#xff0c;返回该组数字的所有排列 例如&#xff1a; [1,2,3]的所有排列如下 [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1]. &#xff08;以数字在数组中的位置靠前为优先级&#xff0c;按字典序排列输出。&#xff09; 思路&#xff1a; 使用回…

ARM嵌入式学习--第八天(PWM)

PWM -PWM介绍 PWM&#xff08;pulse Width Modulation&#xff09;简称脉宽调制&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术&#xff0c;广泛应用在测量&#xff0c;通信&#xff0c;工控等方面 PWM的频率 是指在1秒钟内&#xff0c;信号从…

neo4j 图表数据导入到 TuGraph

neo4j 图表数据导入到 TuGraph 代码文件说明后文 前言:近期在引入阿里的 TuGraph 图数据库&#xff0c;需要将 原 neo4j 数据导入到新的 tugraph 数据库中。预期走csv文件导入导出&#xff0c;但因为格式和数据库设计问题&#xff0c;操作起来比较麻烦&#xff08;可能是个人没…

Docker介绍、安装、namespace、cgroup、镜像-Dya 01

0. 容器简介 从生活上来说&#xff0c;容器是一种工具&#xff0c;可以装东西的工具&#xff0c;如衣柜、背包、行李箱等等。 从IT技术方面来说&#xff0c;容器是一种全新的虚拟化技术&#xff0c;它提高了硬件资源利用率&#xff0c;结合k8s还可以让企业业务快速横向扩容、业…

鱼跃医疗获评2024年国家级“绿色工厂”,以绿色制造树立行业标杆

近日&#xff0c;工业和信息化部公布了2024年度绿色制造名单&#xff0c;鱼跃医疗凭借在绿色制造和可持续发展方面的卓越表现&#xff0c;成功入选并获评国家级“绿色工厂”。 “绿色工厂”是工信部为贯彻落实国家《工业绿色发展规划》&#xff0c;加快推动绿色制造体系建设&a…

建投数据与腾讯云数据库TDSQL完成产品兼容性互认证

近日&#xff0c;经与腾讯云联合测试&#xff0c;建投数据自主研发的人力资源信息管理系统V3.0、招聘管理系统V3.0、绩效管理系统V2.0、培训管理系统V3.0通过腾讯云数据库TDSQL的技术认证&#xff0c;符合腾讯企业标准的要求&#xff0c;产品兼容性良好&#xff0c;性能卓越。 …

Java-30 深入浅出 Spring - IoC 基础 启动IoC 纯XML启动 Bean、DI注入

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

基础入门-Web应用蜜罐系统堡垒机运维API内外接口第三方拓展架构部署影响

知识点&#xff1a; 1、基础入门-Web应用-蜜罐系统 2、基础入门-Web应用-堡垒机运维 3、基础入门-Web应用-内外API接口 4、基础入门-Web应用-第三方拓展架构 一、演示案例-Web-拓展应用-蜜罐-钓鱼诱使 蜜罐&#xff1a;https://hfish.net/ 测试系统&#xff1a;Ubuntu 20.04 …

【长城杯】Web题 hello_web 解题思路

查看源代码发现路径提示 访问…/tips.php显示无用页面&#xff0c;怀疑…/被过滤&#xff0c;采用…/./形式&#xff0c;看到phpinfo()页面 注意到disable_functions&#xff0c;禁用了很多函数 访问hackme.php,看到页面源码 发现eval函数&#xff0c;包含base64 解密获得php代…

【测试】Pytest

建议关注、收藏&#xff01; 目录 功能pytest 自动化测试工具。 功能 单元测试&#xff1a;用于验证代码的最小功能单元&#xff08;如函数、方法&#xff09;的正确性。 简单的语法&#xff1a;不需要继承特定类或使用复杂的结构。断言语句简化。 自动发现测试&#xff1a;P…

Python语法之字典

免责说明&#xff1a;此内容不包含嵌套字典 关注我更新更多初学实例 字典 一.字典的应用场景 思考:数据顺序发生变化&#xff0c;每个数据的下标也会随之变化&#xff0c;如何保证数据顺序变化前后能使用同一的 标准查找数据呢&#xff1f; 答&#xff1a;字典&#xff0c…

mysql客户端命令

目录 结束符 ; \g \G 中断输入 ctrl c 查看命令列表 help ? (\?) connect (\r) status (\s) delimiter (\d) exit (\q) quit (\q) tee (\T) ​编辑 notee (\t) prompt (\R) source (\.) system (\!) ​编辑 use (\u) help contents 结束符 ; \g \G 当我…

更频繁的 Android SDK 发布:更快的创新、更高的质量和更完善

Android 一直致力于让创新更快地进入用户手中。除了每年的平台发布之外&#xff0c;我们还投资了Project Treble、Mainline、Google Play 服务、每月安全更新和季度发布&#xff0c;为 Pixel Drops 提供支持。 未来&#xff0c;Android 将更频繁地发布 SDK&#xff0c;计划于 …

蓝卓总裁谭彰:AI+工业互联网推动制造业数字化转型

近日&#xff0c;新一代工业操作系统supOS6.0在2024中国5G工业互联网大会上重磅发布。 大会期间&#xff0c;工信部新闻宣传中心《人民邮电报》对蓝卓总裁谭彰就“工业互联网人工智能技术融合的思考”“supOS6.0的探索与实践”“未来工业互联网平台的发展方向”展开专题访谈&am…

【WRF-Urban】输入空间分布人为热排放数据的WRF运行全过程总结

目录 数据准备检查新增变量配置(如果有)WPS预处理修改namelist.wpsStep1: geogridStep2: ungribStep3: metgridWRF运行修改namelist.input调试namelist.input运行./real.exe运行./wrf.exe参考WRF模型的基本流程如下: 数据准备 空间分布热排放数据下载及制备可参见另一博客…

如何利用Python爬虫获得1688按关键字搜索商品

在当今的数字化时代&#xff0c;数据已成为企业竞争的核心资源。对于电商行业来说&#xff0c;了解市场动态、分析竞争对手、获取商品信息是至关重要的。Python作为一种强大的编程语言&#xff0c;其丰富的库和框架使得数据爬取变得简单易行。本文将介绍如何使用Python爬虫技术…

Python从0到100(七十三):Python OpenCV-OpenCV实现手势虚拟拖拽

前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能…