当前位置: 首页 > news >正文

【Spring专题】Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)

目录

  • 前言
    • 阅读准备
    • 阅读指引
    • 阅读建议
  • 课程内容
    • 一、生成BeanDefinition
      • 1.1 简单回顾
      • *1.2 概念回顾
      • 1.3 核心方法讲解
    • 二、方法讲解
      • 2.1 ClassPathBeanDefinitionScanner#scan
      • 2.2 ClassPathBeanDefinitionScanner#doScan
      • 2.3 ClassPathScanningCandidateComponentProvider#findCandidateComponents
      • 2.4 ClassPathScanningCandidateComponentProvider#scanCandidateComponents
      • 2.5 ClassPathScanningCandidateComponentProvider#isCandidateComponent
      • 2.6 ClassPathScanningCandidateComponentProvider#isCandidateComponent
      • 2.7 ClassPathBeanDefinitionScanner#postProcessBeanDefinition
      • 2.8 AnnotationConfigUtils.processCommonDefinitionAnnotations
      • 2.9 ClassPathBeanDefinitionScanner#checkCandidate
      • 2.10 DefaultListableBeanFactory#registerBeanDefinition
    • 三、扫描逻辑流程图
    • 四、合并BeanDefinition
  • 学习总结

前言

阅读准备

由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序):

  • Spring底层核心原理解析——引导篇【学习难度:★★☆☆☆
  • 手写简易Spring容器过程分析——引导篇【学习难度:★★☆☆☆
  • Spring之底层架构核心概念解析【学习难度:★★★☆☆,重要程度:★★★★★
  • Bean的生命周期流程图【学习难度:☆☆☆☆☆,重要程度:★★★★★

(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)
(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)
(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)

阅读指引

Spring最重要的功能就是帮助程序员创建对象(也就是IOC),而启动Spring就是为创建Bean对象做准备,所以我们先明白Spring到底是怎么去创建Bean的,也就是先弄明白Bean的生命周期。
Bean的生命周期就是指:在Spring中,一个Bean是如何生成的,如何销毁的。
本节课的内容,将会以下面这段代码为入口讲解:

public class MyApplicationTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("org.tuling.spring");Object newUser = context.getBean("newUser");System.out.println(newUser);}
}

主要用到的是如下Spring容器的构造方法:

/*** 创建一个新的AnnotationConfigApplicationContext,扫描给定包中的组件,为这些组件注册bean定义,并自动刷新上下文。* 参数:* baseppackages——要扫描组件类的包*/
public AnnotationConfigApplicationContext(String... basePackages) {this();scan(basePackages);refresh();
}

阅读建议

  1. 看源码,切记纠结细枝末节,不然很容易陷进去。正常来说,看主要流程就好了
  2. 遇到不懂的,多看看类注释或者方法注释。Spring这种优秀源码,注释真的非常到位
  3. 如果你是idea用户,多用F11的书签功能。
    • Ctrl + F11 选中文件 / 文件夹,使用助记符设定 / 取消书签 (必备)
    • Shift + F11 弹出书签显示层 (必备)
    • Ctrl +1,2,3…9 定位到对应数值的书签位置 (必备)

课程内容

一、生成BeanDefinition

1.1 简单回顾

如果大家看过我前面的文章,应该会知道,BeanDefiniton的生成,是在生产Bean之前的【扫描】阶段。如下图所示:
在这里插入图片描述
在这里插入图片描述

不过咱也说过,这只是简单实现而已,事实上,Spring实现这个扫描过程,涉及到了3个核心类,10个核心方法!

*1.2 概念回顾

在这个【扫描】过程中,涉及到了一些Spring底层设计的概念,我在上一个笔记里面有大概介绍过,不记得的同学记得回去翻一翻。
主要涉及的概念有:

  • BeanDefinition(设计图纸):BeanDefinition表示Bean定义,BeanDefinition中存在很多属性用来描述一个Bean的特征
  • ClassPathBeanDefinitionScanner(图纸注册器):用于注册图纸
  • BeanFacotory(Bean工厂):生产Bean。不过实际上,这里用到DefaultListableBeanFactory这个类的BeanDefinitionRegistry接口的注册能力

文章链接:
《【Spring专题】Spring之底层架构核心概念解析》

1.3 核心方法讲解

我们在前面提到过,整个扫描流程会涉及到【3个核心类,10个核心方法】。下面,我们将会按照调用次序,依次讲解各个方法。

二、方法讲解

整个扫描流程的入口,是下面代码中的scan()方法,而最终调用的是ClassPathBeanDefinitionScanner.scan()

/*** 创建一个新的AnnotationConfigApplicationContext,扫描给定包中的组件,为这些组件注册bean定义,并自动刷新上下文。* 参数:* baseppackages——要扫描组件类的包*/
public AnnotationConfigApplicationContext(String... basePackages) {this();scan(basePackages);refresh();
}public void scan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan").tag("packages", () -> Arrays.toString(basePackages));this.scanner.scan(basePackages);scanPackages.end();
}

2.1 ClassPathBeanDefinitionScanner#scan

全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
方法注释:在指定的基本包中执行扫描

源码如下:

	public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();doScan(basePackages);// Register annotation config processors, if necessary.if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);}

但其实真正干活的还不是这里,而是里面的doScan()

2.2 ClassPathBeanDefinitionScanner#doScan

方法调用链:由 2.1中的scan()调用进来
全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
方法注释:在指定的基包中执行扫描,返回已注册的bean定义。此方法不注册注释配置处理程序,而是将此工作留给调用者。

源码如下:

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();for (String basePackage : basePackages) {// 寻找候选的BeanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);// 判断是否要设置BeanDefinition属性if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}// 判断是否要设置通用的注解BeanDefiniton属性if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}// 判断是否注册BeanDefinitionif (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

方法解读:这个代码看似很多,其实整体来说比较简单,分为4个步骤

  1. 先扫描classpath下所有的class,寻找候选的BeanDefinition。至于什么是候选的,后面会讲解
  2. 遍历找到的候选BeanDefinition,判断是否需要设置BeanDefinition的一些属性。比如默认属性等
  3. 判断是否要设置通用的注解BeanDefiniton属性
  4. 判断是否要注册BeanDefinition,毕竟可能出现重复的BeanDefinition

2.3 ClassPathScanningCandidateComponentProvider#findCandidateComponents

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
方法注释:扫描类路径上的候选Component。

源码如下:

	public Set<BeanDefinition> findCandidateComponents(String basePackage) {if (this.componentsIndex != null && indexSupportsIncludeFilters()) {return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);}else {return scanCandidateComponents(basePackage);}}

方法解读:这里有一个if-else,但通常都是进入elseif里面的情况我们在业务开发中基本不会用到,这是一个为了加快扫描包的策略,通过使用索引思想。怎么实现呢?就是在resource下新建一个spring.components配置文件(K-V格式)。如下:

org.tuling.spring.bean.User = org.springframework.stereotype.Component

通过直接告诉Spring哪些是候选的Component,免除了扫描所有class文件筛选的过程,从而提升了效率。

2.4 ClassPathScanningCandidateComponentProvider#scanCandidateComponents

方法调用链:由 2.3中的findCandidateComponents()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
方法注释:扫描类路径上的候选Component。

源码如下:

    private Set<org.springframework.beans.factory.config.BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}try {// 读取类元信息MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// 第一层判断是否为候选组件,判断核心为:是否需要过滤if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);// 第二层判断是否为候选组件,判断核心为:是否独立、抽象类等if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);}else {if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {if (traceEnabled) {logger.trace("Ignored because not matching any filter: " + resource);}}}catch (FileNotFoundException ex) {if (traceEnabled) {logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());}}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}

方法解读:代码看着很长,实际上很简单。主要分为4个步骤

  1. 读取类元信息(从磁盘中读取)
    • 首先,通过ResourcePatternResolver获得指定包路径下的所有.class文件(Spring源码中将此文件包装成了Resource对象)
    • 遍历每个Resource对象
    • 利用MetadataReaderFactory解析Resource对象得到MetadataReader(在Spring源码中MetadataReaderFactory具体的实现类为CachingMetadataReaderFactory,MetadataReader的具体实现类为SimpleMetadataReader)
  2. 判断是否为候选组件,判断核心为:是否需要过滤(第一个isCandidateComponent
    • 利用MetadataReader进行excludeFilters和includeFilters,以及条件注解@Conditional的筛选(条件注解并不能理解:某个类上是否存在@Conditional注解,如果存在则调用注解中所指定的类的match方法进行匹配,匹配成功则通过筛选,匹配失败则pass掉。)
    • 筛选通过后,基于metadataReader生成ScannedGenericBeanDefinition
  3. 判断是否为候选组件,判断核心为:是否独立、抽象类等。(第二个isCandidateComponent)(注意:他跟上面的isCandidateComponent不是同一个东西,属于方法重载)
  4. 如果筛选通过,那么就表示扫描到了一个Bean,将ScannedGenericBeanDefinition加入结果集

MetadataReader表示类的元数据读取器,主要包含了一个AnnotationMetadata,功能有

  • 获取类的名字、
  • 获取父类的名字
  • 获取所实现的所有接口名
  • 获取所有内部类的名字
  • 判断是不是抽象类
  • 判断是不是接口
  • 判断是不是一个注解
  • 获取拥有某个注解的方法集合
  • 获取类上添加的所有注解信息
  • 获取类上添加的所有注解类型集合

2.5 ClassPathScanningCandidateComponentProvider#isCandidateComponent

方法调用链:由 2.4中的scanCandidateComponents()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent
方法注释:确定给定的类是否不匹配任何排除筛选器,而匹配至少一个包含筛选器。

源码如下:

	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return isConditionMatch(metadataReader);}}return false;}

方法解读:这里只是校验,当前类,是否在过滤器排除选项以内。我估计很多朋友想到的都是@ComponentScan注解里面的includeFilterexcludeFilter属性。只能说不完全正确。
因为,在我们启动容器创建ClassPathBeanDefinitionScanner也会添加默认的过滤策略。如下所示:

/*** 注册@Component的默认过滤器。* 这将隐式注册所有带有@Component元注释的注释,包括@Repository、@Service和@Controller构造型注释。* 还支持Java EE 6的javax.annotation.ManagedBean和JSR-330的javax.inject.Named注释(如果可用)。*/
protected void registerDefaultFilters() {this.includeFilters.add(new AnnotationTypeFilter(Component.class));ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.}try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.}
}

2.6 ClassPathScanningCandidateComponentProvider#isCandidateComponent

方法调用链:由 2.4中的scanCandidateComponents()调用进来
全路径:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent
方法注释:确定给定的bean定义是否符合候选条件。默认实现检查类是否不是接口,是否依赖于封闭类。

源码如下:

/***确定给定的bean定义是否符合候选条件。* 默认实现检查类是否不是接口,是否依赖于封闭类。* 可以在子类中重写。*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();return (metadata.isIndependent() && (metadata.isConcrete() ||(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

方法解读:这里有2个比较陌生的,那就是,什么是独立类,以及Lookup注解。

  1. 独立类,及不是普通内部类(静态内部类是独立类)
  2. Lookup注解,自己去百度使用方法

总结方法,判断是候选Component的条件是:

  1. 首先是独立类
  2. 要么是具体的子类,要么就是:有Lookup注解的抽象类

2.7 ClassPathBeanDefinitionScanner#postProcessBeanDefinition

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition
方法注释:扫描类路径上的候选Component。

源码如下:

/*** 除了通过扫描组件类检索到的内容之外,还可以对给定的bean定义应用进一步的设置。*/
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {beanDefinition.applyDefaults(this.beanDefinitionDefaults);if (this.autowireCandidatePatterns != null) {beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));}}

方法解读:这里主要还初始化了BeanDefinition一些默认属性beanDefinition.applyDefaults(this.beanDefinitionDefaults);
源码如下:

	public void applyDefaults(BeanDefinitionDefaults defaults) {Boolean lazyInit = defaults.getLazyInit();if (lazyInit != null) {setLazyInit(lazyInit);}setAutowireMode(defaults.getAutowireMode());setDependencyCheck(defaults.getDependencyCheck());setInitMethodName(defaults.getInitMethodName());setEnforceInitMethod(false);setDestroyMethodName(defaults.getDestroyMethodName());setEnforceDestroyMethod(false);}

2.8 AnnotationConfigUtils.processCommonDefinitionAnnotations

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.AnnotationConfigUtils#processCommonDefinitionAnnotations
方法注释:根据注解累类元信息,设置BeanDefinition注解相关属性。

源码如下:

public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {processCommonDefinitionAnnotations(abd, abd.getMetadata());
}static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}else if (abd.getMetadata() != metadata) {lazy = attributesFor(abd.getMetadata(), Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}}if (metadata.isAnnotated(Primary.class.getName())) {abd.setPrimary(true);}AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);if (dependsOn != null) {abd.setDependsOn(dependsOn.getStringArray("value"));}AnnotationAttributes role = attributesFor(metadata, Role.class);if (role != null) {abd.setRole(role.getNumber("value").intValue());}AnnotationAttributes description = attributesFor(metadata, Description.class);if (description != null) {abd.setDescription(description.getString("value"));}}

没啥好解读的,很简单的判断

2.9 ClassPathBeanDefinitionScanner#checkCandidate

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate
方法注释:检查给定的候选bean名称,确定是否需要注册相应的bean定义,或者是否与现有定义冲突。

源码如下:

protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {if (!this.registry.containsBeanDefinition(beanName)) {return true;}BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();if (originatingDef != null) {existingDef = originatingDef;}if (isCompatible(beanDefinition, existingDef)) {return false;}throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");}

方法解读:这里正常来说,要么返回true,要么就是重复定义相同名字的Bean然后抛出异常。直接返回false,通常是存在父子容器的情况下。

2.10 DefaultListableBeanFactory#registerBeanDefinition

方法调用链:由 2.2中的doScan()调用进来
全路径:org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
方法注释:用这个注册中心注册一个新的bean定义。必须支持RootBeanDefinition和ChildBeanDefinition。

源码如下:

@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);if (existingDefinition != null) {if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}else if (existingDefinition.getRole() < beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (logger.isInfoEnabled()) {logger.info("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +existingDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(existingDefinition)) {if (logger.isDebugEnabled()) {logger.debug("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}else {if (logger.isTraceEnabled()) {logger.trace("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}this.beanDefinitionMap.put(beanName, beanDefinition);}else {if (hasBeanCreationStarted()) {// Cannot modify startup-time collection elements anymore (for stable iteration)synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;removeManualSingletonName(beanName);}}else {// Still in startup registration phasethis.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}else if (isConfigurationFrozen()) {clearByTypeCache();}}

方法解读:这个就稍微复杂一点了,我也没细看。而且,这个隐藏的挺深的,虽然是从doScan()里面进调用的,但是本质上是调用AnnotationConfigApplicationContext继承自GenericApplicationContextregisterBeanDefinition方法

三、扫描逻辑流程图

在这里插入图片描述

四、合并BeanDefinition

到了后面的IOC过程,生成Bean的时候,我们会看到很多这样的调用:

RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

这个就是所谓的,获取合并BeanDefinition。什么是合并BeanDefinition?其实就是父子BeanDefinition。跟Java里面的继承是一样一样的。但是,这个通常发生在xml配置bean里面,如下:

<bean id="parent" class="com.zhouyu.service.Parent" scope="prototype"/>
<bean id="child" class="com.zhouyu.service.Child"/>

但是这么定义的情况下,child就是原型Bean了。因为child的父BeanDefinition是parent,所以会继承parent上所定义的scope属性。而在根据child来生成Bean对象之前,需要进行BeanDefinition的合并,得到完整的child的BeanDefinition。

学习总结

  1. 学习了Spring源码流程中的【扫描】底层原理

相关文章:

【Spring专题】Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)

目录 前言阅读准备阅读指引阅读建议 课程内容一、生成BeanDefinition1.1 简单回顾*1.2 概念回顾1.3 核心方法讲解 二、方法讲解2.1 ClassPathBeanDefinitionScanner#scan2.2 ClassPathBeanDefinitionScanner#doScan2.3 ClassPathScanningCandidateComponentProvider#findCandid…...

【C#】判断打印机共享状态

打印机共享状态 /// <summary>/// 打印机共享状态/// </summary>public enum PrinterShareState{/// <summary>/// 无打印机/// </summary>None -1,/// <summary>/// 未共享/// </summary>NotShare 0,/// <summary>/// 已共享/// …...

运维监控学习笔记7

Zabbix的安装&#xff1a; 1、基础环境准备&#xff1a; 安装zabbix的yum源&#xff0c;阿里的yum源提供了zabbix3.0。 rpm -ivh http://mirrors.aliyun.com/zabbix/zabbix/3.0/rhel/7/x86_64/zabbix-release-3.0-1.el7.noarch.rpm 这个文件就是生成了一个zabbix.repo 2、安…...

【业务功能篇64】maven加速 配置settings.xml文件 镜像

maven加速 添加阿里镜像仓 <?xml version"1.0" encoding"UTF-8"?><!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additi…...

Spring Boot(六十四):SpringBoot集成Gzip压缩数据

1 实现思路 2 实现 2.1 创建springboot项目 2.2 编写一个接口,功能很简单就是传入一个Json对象并返回 package com.example.demo.controller;import com.example.demo.entity.Advertising; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframewo…...

Mac安装opencv后无法导入cv2的解决方法

前提条件&#xff1a;以下两个插件安装成功 pip install opencv-python pip install --user opencv-contrib-python 注&#xff1a;直接用pip install opencv-contrib-python如果报错&#xff0c;就加上“–user" 第一步&#xff1a; 设置–添加python解释器 第二步&am…...

【题解】按之字形顺序打印二叉树

按之字形顺序打印二叉树 题目链接&#xff1a;按之字形顺序打印二叉树 解题思路&#xff1a;层次遍历&#xff0c;借助队列 首先解决如何模仿之字形的问题&#xff0c;我们为此设置一个flag&#xff0c;每到一层就修改flag&#xff0c;如果flag为true&#xff08;初始为fals…...

后端人员如何快速上手vue

一、环境搭建 了解更多vue-cli 官网地址:https://cli.vuejs.org/zh/guide/browser-compatibility.html 前提 1.安装node(js代码的运行环境)、npm、cnpm/yarn&#xff1b; nodejs官网&#xff1a;https://nodejs.org/en cnpm安装&#xff1a;https://www.python100.com/htm…...

基于Prometheus监控Kubernetes集群

目录 一、环境准备 1.1、主机初始化配置 1.2、部署docker环境 二、部署kubernetes集群 2.1、组件介绍 2.2、配置阿里云yum源 2.3、安装kubelet kubeadm kubectl 2.4、配置init-config.yaml 2.5、安装master节点 2.6、安装node节点 2.7、安装flannel、cni 2.8、部署测…...

【数据分析】pandas (三)

基本功能 在这里&#xff0c;我们将讨论pandas数据结构中常见的许多基本功能 让我们创建一些示例对象&#xff1a; index pd.date_range(“1/1/2000”, periods8) s pd.Series(np.random.randn(5), index[“a”, “b”, “c”, “d”, “e”]). df pd.DataFrame(np.random.…...

nvm命令

1. 常见命令 1. nvm -v //查看nvm版本 nvm --version &#xff1a;显示 nvm 版本 2. nvm list //显示版本列表 nvm list &#xff1a;显示已安装的版本&#xff08;同 nvm list installednvm list installed&#xff1a;显示已安装的版本nvm list available&#xff1a;显示所有…...

从此已是义无反顾

距离上次发这个专栏的文章已经过去了十多天&#xff0c;现在我已经开始准备面试内容&#xff0c;迟迟还没有投出第一份简历&#xff0c;只是因为我感觉对知识点的理解还不到位&#xff0c;于是开始一边看JavaGuide老师总结的面试题目&#xff0c;一边翻看以前学习的笔记&#x…...

Element组件浅尝辄止2:Card卡片组件

根据官方说法&#xff1a; 将信息聚合在卡片容器中展示。 1.啥时候使用&#xff1f;When? 既然是信息聚合的容器&#xff0c;那场景就好说了 新建页面时可以用来当做页面容器页面的某一部分&#xff0c;可以用来当做子容器 2.怎样使用&#xff1f;How&#xff1f; //Card …...

“深入剖析Java多态:点燃编程世界火花“

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f649; 内容推荐:“继承与组合&#xff1a;代码复用的两种策略“&#x1f649; &#x1f439;今日诗词:马踏祁连山河动,兵起玄黄奈何天&#x1f439; 快去学习 &#x1f338;思维导…...

golang官方限流器rate包实践

日常开发中&#xff0c;对于某些接口有请求频率的限制。比如登录的接口、发送短信的接口、秒杀商品的接口等等。 官方的golang.org/x/time/rate包中实现了令牌桶的算法。 封装限流器可以将ip、手机号这种的作为限流器组的标识。 接下来就是实例化限流器和获取令牌函数的实现…...

[windows]MAT- 下载及安装

1. 下载安装包 1.1MAT下载链接&#xff1a; https://pan.baidu.com/s/1sUWPITSto8MjOrcF0BsJQg?pwd1111 提取码&#xff1a;1111 1.2MAT需要jdk17版本及以上支持&#xff0c;下载链接: https://pan.baidu.com/s/111jz90S4tie_48lQeExcZg?pwd1111 提取码&#xff1a;1…...

数组模拟环形队列详解

数组模拟环形队列 实现逻辑 创建一个固定大小的数组作为队列的存储空间&#xff0c;同时定义队列的头部和尾部指针&#xff08;front和rear&#xff09;。初始时&#xff0c;将头部和尾部指针都设置为0&#xff0c;表示队列为空。入队操作&#xff08;enqueue&#xff09;&am…...

《论文阅读12》RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds

一、论文 研究领域&#xff1a;全监督3D语义分割&#xff08;室内&#xff0c;室外RGB&#xff0c;kitti&#xff09;论文&#xff1a;RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds CVPR 2020 牛津大学、中山大学、国防科技大学 论文链接论文gi…...

elementPlus使用el-icon

安装 # NPM $ npm install element-plus/icons-vue # Yarn $ yarn add element-plus/icons-vue # pnpm $ pnpm install element-plus/icons-vue一、main.ts&#xff08;全局注册&#xff09; import * as ElementIcons from element-plus/icons-vuefor (const key in Element…...

预测知识 | 神经网络、机器学习、深度学习

预测知识 | 预测技术流程及模型评价 目录 预测知识 | 预测技术流程及模型评价神经网络机器学习深度学习参考资料 神经网络 神经网络&#xff08;neural network&#xff09;是机器学习的一个重要分支&#xff0c;也是深度学习的核心算法。神经网络的名字和结构&#xff0c;源自…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...