6-spring源码阅读之invokeBeanFactoryPostProcessors方法


前言

上一节 已经说了postProcessBeanFactory空方法的扩展点使用,这一节开始正式进入比较重要的环节,那一节我们来聊一聊invokeBeanFactoryPostProcessors这个方法。

一、invokeBeanFactoryPostProcessors方法全流程

老规矩先画图看看整体干了什么
在这里插入图片描述
这个方法办的所有事情都在这了,虽然有些细节没有画出来,但是重要的点都在这,下面我们边看源码结合图来详细说明。

二、invokeBeanFactoryPostProcessors源代码详细讲解

①点入该方法可以看到如下代码,这个干的两件事情就是流程图中的①和②,执行后置处理器以及为织入做准备。

	/**
	 * 实例化并调用所有已注册的 BeanFactoryPostProcessor Bean,
	 * 若指定了显式顺序则遵循该顺序。
	 * 必须在单例实例化之前调用。
	 */
	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		//tip 委托给 PostProcessorRegistrationDelegate 执行所有 BeanFactoryPostProcessor
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(
				beanFactory,
				getBeanFactoryPostProcessors()  // 获取应用上下文中手动注册的 BeanFactoryPostProcessor
		);

		// 检测是否存在 LoadTimeWeaver,并为织入(weaving)做准备
		// (例如通过 ConfigurationClassPostProcessor 注册的 @Bean 方法)
		if (!NativeDetector.inNativeImage() &&
				beanFactory.getTempClassLoader() == null &&
				beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			// 添加 LoadTimeWeaverAwareProcessor 处理器
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			// 设置临时类加载器以支持上下文类型匹配
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}
	}

②接着进入PostProcessorRegistrationDelegate类的方法,可以看到这个方法传入的是beanFactory以及手动注册后置处理器。

	public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors)

③我们可以分为两步部分看,第一部分的代码如下

// 用于存储已处理的bean名称,以避免重复处理。
		Set<String> processedBeans = new HashSet<>();

		// 如果beanFactory是BeanDefinitionRegistry的实例,则进行特殊处理。
		if (beanFactory instanceof BeanDefinitionRegistry registry) {
			List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
			List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

			// 遍历传入的BeanFactoryPostProcessor列表,将它们分为BeanDefinitionRegistryPostProcessor和普通BeanFactoryPostProcessor。
			for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
				if (postProcessor instanceof BeanDefinitionRegistryPostProcessor registryProcessor) {
					//直接执行使用编程方式addBeanFactoryPostProcessor()方法注入的处理器
					registryProcessor.postProcessBeanDefinitionRegistry(registry);
					registryProcessors.add(registryProcessor);
				} else {
					regularPostProcessors.add(postProcessor);
				}
			}

			// 不要在这里初始化FactoryBean:我们需要让所有常规bean保持未初始化状态,以便让bean工厂后处理器应用到它们!

			// 分离出实现PriorityOrdered、Ordered和其余的BeanDefinitionRegistryPostProcessor。
			List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

			// 首先,调用实现PriorityOrdered接口的BeanDefinitionRegistryPostProcessor。
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			//PriorityOrdered.getOrder() 返回值,数值越小优先级越高
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			//tip currentRegistryProcessors,依次调用每个处理器的 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
			// 动态注册新的 Bean 定义(例如 Spring 内置的 【ConfigurationClassPostProcessor】 解析 @Configuration 类中的 @Bean 方法)。
			// 修改已存在的 Bean 定义(如调整作用域、属性值)。
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
			currentRegistryProcessors.clear();

			// 接下来,调用实现Ordered接口的BeanDefinitionRegistryPostProcessor。
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			// tip 动态注册新的 Bean 定义或修改现有定义(例如解析 @ComponentScan 生成的 Bean)
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
			currentRegistryProcessors.clear();

			// 最后,调用所有其他BeanDefinitionRegistryPostProcessor,直到没有新的出现。
			boolean reiterate = true;
			while (reiterate) {
				reiterate = false;
				postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
				for (String ppName : postProcessorNames) {
					if (!processedBeans.contains(ppName)) {
						currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
						processedBeans.add(ppName);
						reiterate = true;
					}
				}
				sortPostProcessors(currentRegistryProcessors, beanFactory);
				registryProcessors.addAll(currentRegistryProcessors);
				invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
				currentRegistryProcessors.clear();
			}

			// 现在,调用到目前为止处理过的所有处理器的postProcessBeanFactory回调。
			invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
			invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
		} else {
			// 如果beanFactory不是BeanDefinitionRegistry的实例,则只调用上下文实例中注册的工厂处理器。
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}

正常来讲,我们的容器都是BeanDefinitionRegistry的实例,所以会走if里面的逻辑。首先会遍历传入的后置处理器,也就是通过applicationContext.addBeanFactoryPostProcessor()手动传入的,如果你传入的这个处理类是

  1. 实现了BeanDefinitionRegistryPostProcessor接口,则第一步直接先执行该处理类中postProcessBeanDefinitionRegistry()方法,并将该处理类对象放入待执行列表registryProcessors中,这里是为了后续执行该处理类中postProcessBeanFactory()方法。
  2. 如果没有实现BeanDefinitionRegistryPostProcessor接口,则将该后置处理类对象放入标准待执行列表regularPostProcessors中,也是为了后续执行它的postProcessBeanFactory()方法。

如果是第一种情况,就可以先接着往下走了。
④从beanFactory中获取所有实现了BeanDefinitionRegistryPostProcessor接口的后置处理类。然后这里分离出实现PriorityOrdered、Ordered和其余的BeanDefinitionRegistryPostProcessor,也就是说,按照PriorityOrdered > Ordered > BeanDefinitionRegistryPostProcessord(未实现前两种接口处理器)的顺序依次执行了后置处理类中的postProcessBeanDefinitionRegistry()方法。这里值得一提的是,我们常用的@Configuration注解,就是实现了PriorityOrdered的一个后置处理器,叫做【ConfigurationClassPostProcessor】,这里暂不对该类进行详细讲解,我这里贴一段它的代码注释吧。

	/**
	 * 构建并验证基于 {@link Configuration} 类注册表的配置模型。
	 * 该方法负责解析所有标记为配置类(@Configuration)的 Bean 定义,并处理其内部的:
	 * - @Bean 方法
	 * - @ComponentScan 扫描的组件
	 * - @Import 导入的其他配置类或普通类
	 * - @PropertySource 属性源
	 * 最终将这些配置类及其相关 Bean 定义注册到容器中。
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		// tip 步骤1:收集所有候选的配置类 Bean 定义
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// 获取当前容器中所有已注册的 Bean 定义名称
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			// 检查该 Bean 定义是否已经被标记为“已处理的配置类”(避免重复处理)
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			// 检查当前 Bean 定义是否为潜在的配置类(如标记了 @Configuration 的类)
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// 若没有找到任何配置类,直接返回
		if (configCandidates.isEmpty()) {
			return;
		}

		// tip 步骤2:按 @Order 注解的值对配置类排序(确保处理顺序正确)
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// tip 步骤3:检测并应用自定义的 Bean 名称生成策略(如通过上下文注入的生成器)
		SingletonBeanRegistry singletonRegistry = null;
		if (registry instanceof SingletonBeanRegistry sbr) {
			singletonRegistry = sbr;
			if (!this.localBeanNameGeneratorSet) { // 如果未手动设置生成器
				// 从单例注册表中获取默认的 Bean 名称生成器
				BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					// 设置用于组件扫描和 @Import 类的名称生成器
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		// 确保环境变量已初始化(用于属性解析等)
		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// tip 步骤4:创建配置类解析器,开始解析每个候选配置类
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		// 使用 LinkedHashSet 维护解析顺序,避免重复
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		// 记录已解析的配置类,避免重复处理
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 启动步骤跟踪(性能监控)
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");

			// 解析候选配置类:处理 @ComponentScan、@Import、@Bean 等注解
			parser.parse(candidates);
			// 验证配置类的元数据(如是否存在循环导入)
			parser.validate();

			// 获取解析后的配置类集合
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			// 移除已解析过的类(针对多轮解析的情况)
			configClasses.removeAll(alreadyParsed);

			// tip 步骤5:创建配置类 Bean 定义读取器,将解析结果转换为 Bean 定义并注册到容器
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			// 关键步骤:将配置类中定义的 Bean(@Bean 方法、@Import 等)加载为 Bean 定义
			this.reader.loadBeanDefinitions(configClasses);
			// 记录已解析的类
			alreadyParsed.addAll(configClasses);
			processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

			// tip 步骤6:检查是否有新注册的 Bean 定义,可能包含新的配置类(例如通过 @Import)
			candidates.clear();
			// 比较当前 Bean 定义数量是否增加(说明有新 Bean 被注册)
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				// 获取所有新的 Bean 定义名称
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = Set.of(candidateNames);
				Set<String> alreadyParsedClasses = new HashSet<>();
				// 记录已解析类的全限定名
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				// 筛选出新增的候选配置类
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) { // 新增的 Bean 定义
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						// 检查是否是配置类且未解析过
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames; // 更新候选列表
			}
		}
		while (!candidates.isEmpty()); // 循环直到没有新增配置类

		// tip 步骤7:注册 ImportRegistry 作为单例 Bean,支持 ImportAware 接口
		if (singletonRegistry != null && !singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		// tip 步骤8:保存 PropertySource 描述符(用于 AOT 编译等场景)
		this.propertySourceDescriptors = parser.getPropertySourceDescriptors();

		// tip 步骤9:清除元数据读取缓存(如果使用外部提供的 CachingMetadataReaderFactory)
		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cachingMetadataReaderFactory) {
			cachingMetadataReaderFactory.clearCache();
		}
	}

⑤等依次执行完所有后置处理器中的postProcessBeanDefinitionRegistry方法后,随后执行之前待执行列表registryProcessors中的后置后处理的postProcessBeanFactory方法,放入列表中就是为了这里的回调;等完成后再执行标准待执行列表regularPostProcessors中postProcessBeanFactory方法。流程中有一点未体现,就是最后执行无序的后置处理器前,有一个while循环会一直去扫描BeanDefinitionRegistryPostProcessor中是否有新注册的后置处理器,直到所有后置处理器被扫描完才跳出,这里应该也能理解,毕竟实现了BeanDefinitionRegistryPostProcessor接口的处理器它是由新注册bean定义的能力,所以才这样做的。
⑥到这里为止,第一步做的事情就做完了,接下来继续看第二部分

// 注意:此处不初始化 FactoryBeans!需保持常规 Bean 未初始化状态,以便后处理器能对其生效
		// 获取所有 BeanFactoryPostProcessor 名称(不初始化 FactoryBeans)
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

		// 按优先级分类处理器(PriorityOrdered > Ordered > 无序)
		List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
			if (processedBeans.contains(ppName)) {
				// 跳过已处理的 Bean(如之前处理的 BeanDefinitionRegistryPostProcessor)
			} else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				// 立即实例化 PriorityOrdered 类型(需最早执行)
				priorityOrderedPostProcessors.add(
						beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
			} else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
				// 记录 Ordered 类型名称(延迟实例化)
				orderedPostProcessorNames.add(ppName);
			} else {
				// 记录无序处理器名称(最后处理)
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// tip 阶段一:处理 PriorityOrdered 处理器
		// 1. 按 @Order 值排序(实现 PriorityOrdered 的 getOrder() 方法)
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		// 2. 执行后处理(可能修改 BeanDefinition)
		invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

		// tip 阶段二:处理 Ordered 处理器
		List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>();
		for (String postProcessorName : orderedPostProcessorNames) {
			// 延迟实例化(确保只在当前阶段初始化)
			orderedPostProcessors.add(
					beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);  // 按 Ordered 排序
		invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

		// tip 阶段三:处理无序处理器
		List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
		for (String postProcessorName : nonOrderedPostProcessorNames) {
			// 最后实例化并执行(无排序)
			nonOrderedPostProcessors.add(
					beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

		// 清除元数据缓存(后处理器可能修改了原始数据,如替换属性占位符)
		beanFactory.clearMetadataCache();

其实第二部分跟第一部分有点类似,只不过这次是从容器beanFactory中查找所有实现了BeanFactoryPostProcessor接口的后置处理器,然后也是按照优先级分类处理器(PriorityOrdered > Ordered > 无序)去按顺序执行处理器中的postProcessBeanFactory方法。非要说区别,那就是会先判断该处理器中的方法是否已经被执行过了,执行了就会跳过,然后会立即实例化实现了PriorityOrdered接口的后置处理器,至于实现了Ordered 接口和无实现的,会把beanName存入待执行列表,等需要执行的时候再去实例化,这里和第一部分立即实例化是不太一样的。这里也说下为什么这样做吧。

  1. ‌执行顺序要求‌:
    PriorityOrdered处理器需要最先执行(最高优先级)
    Ordered处理器次之(中等优先级)
    普通处理器最后执行(无明确顺序)
  2. 性能优化考虑‌:
    PriorityOrdered处理器数量通常较少但关键(如配置类处理器)
    立即实例化可以确保高优先级处理器尽早可用
    延迟实例化其他处理器避免不必要的早期对象创建‌
  3. 依赖关系处理‌:
    PriorityOrdered处理器可能影响后续处理器的行为
    先确保高优先级处理器完成初始化,再决定如何初始化其他处理器
  4. 内存效率‌:
    通过名称保存(orderedPostProcessorNames/nonOrderedPostProcessorNames)比保存对象更节省内存实际执行时才按需实例化

⑦最后通过clearMetadataCache方法,清除元数据缓存(后处理器可能修改了原始数据,如替换属性占位符)。

三、测试后置处理类的执行顺序

这里我贴一段代码来测试下执行顺序吧
①首先是测试类

	@Test
	public void beanFactoryPostProcessorTest() {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
		applicationContext.addBeanFactoryPostProcessor(new MyBeanFactoryPostProcessor2());
		applicationContext.register(MyConfig.class);
		applicationContext.refresh();
	}

②Configuration的扫描配置类

@Configuration
@ComponentScan("org.beanfactory")
public class MyConfig{
}

③在扫描包下的BeanFactoryPostProcessorTest 类

@Component
public class BeanFactoryPostProcessorTest implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("====>BeanFactoryPostProcessorTest  这是一个无序的实现了BeanFactoryPostProcessor的类,这应该最后执行。。。。。。");
	}
}

④在扫描包下的MyBeanFactoryPostProcessor2 类(注:该类是手动注册的,并没有@Component注解)

public class MyBeanFactoryPostProcessor2 implements Ordered, BeanDefinitionRegistryPostProcessor {

	@Override
	public int getOrder() {
		return 10;
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		System.out.println("====>MyBeanFactoryPostProcessor2  是用addBeanFactoryPostProcessor()手动注册的,应该最先执行。。。。。。 11111");
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("====>MyBeanFactoryPostProcessor2  是用addBeanFactoryPostProcessor()手动注册的,所以应该是第一个执行的postProcessBeanFactory方法,我的优先级是3333333  ");
	}
}

⑤在扫描包下的MyBeanFactoryPostProcessor3 类

@Component
public class MyBeanFactoryPostProcessor3 implements BeanDefinitionRegistryPostProcessor, Ordered {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("====>MyBeanFactoryPostProcessor3  我实现了 BeanDefinitionRegistryPostProcessor 和 Ordered 接口的postProcessBeanFactory方法。" +
				"\n 哪怕我同样实现了Ordered接口,优先级比上面那个高,还是后执行,因为他是手动注册的。我的优先级是4444444444   ");
	}

	@Override
	public int getOrder() {
		return 0;
	}
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		System.out.println("====>MyBeanFactoryPostProcessor3  我实现了BeanDefinitionRegistryPostProcessor 和 Ordered  接口的postProcessBeanDefinitionRegistry方法 ,我的优先级是22222222222   ");

	}
}

⑥那么执行结果你们知道吗?直接贴图
在这里插入图片描述
这个结果是不是你想的那样呢?如果不是,可能还需要好好理解下

总结

到这里invokeBeanFactoryPostProcessors方法所有的内容都已经说完了,不知道各位看官是否有点收获,其实去掉手动注册的后置处理器,就只有两部分,一是实现了BeanDefinitionRegistryPostProcessor的处理类,二是实现了BeanFactoryPostProcessor的后置处理类。这两个接口是有区别的哈,如果你仔细看了源码,应该发现BeanDefinitionRegistryPostProcessor主要用来注册,另一个主要用来修改bean定义相关的操作。我还是贴一个图作为结尾吧。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值