Spring实例化源码解析之ConfigurationClassPostProcessor(二)

news2024/10/5 16:21:27

ConfigurationClassPostProcessor源码 解析

书接上回,在第一次调用invokeBeanDefinitionRegistryPostProcessors方法的时候参数currentRegistryProcessors为ConfigurationClassPostProcessor,本章主要深入这个类的postProcessBeanDefinitionRegistry方法。

postProcessBeanDefinitionRegistry

首先看到的这句注释就能完整的概括这章源码的全部内容:从注册表中的配置类派生更多的bean definitions。刚看这句话可能理解不出来里面的意思,那就各位看官里面请。

	/**
	 * Derive further bean definitions from the configuration classes in the registry.
	 */
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);

		processConfigBeanDefinitions(registry);
	}

首先为BeanDefinitionRegistry生成了一个registryId,这个就是为了检验重复加载的问题,所以前面的都只是校验,如果只看主流程这些你都可以不看,直接关注最后一行代码,processConfigBeanDefinitions(registry)。

在这里插入图片描述

上面这张图可以知道registry中的具体属性内容,需要我们关注的是BeanDefinitionNames和beanPostProcessors这两个。aopConfig是我启动的时候的配置类,也就是我手动注册的类(register(componentClasses)),其他的都是spring自带的,本章的ConfigurationClassPostProcessor就是。

processConfigBeanDefinitions

开始进入正题,接下来就慢慢的拆解processConfigBeanDefinitions方法。

		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// 当前BeanFacoty中存在的BeanDefinitonNames
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			// 根据名称获取BeanDefinition
			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);
				}
			}
			// 检测配置候选人的条件,此处为true才会加入配置候选人
			// private static final Set<String> candidateIndicators = new HashSet<>(8);
			//
			//	static {
			//		candidateIndicators.add(Component.class.getName());
			//		candidateIndicators.add(ComponentScan.class.getName());
			//		candidateIndicators.add(Import.class.getName());
			//		candidateIndicators.add(ImportResource.class.getName());
			//	}
			// 也就是说只要包含这些也都是配置候选人
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

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

首先定义了一个configCandidates的集合,我们把他叫做后续按配置类集合,存放的是BeanDefinitionHolder,BeanDefinitionHolder和BeanDefinition是Spring框架中两个相关的概念,它们之间存在一种包含关系。

BeanDefinition是一个接口,用于描述一个bean的定义信息,包括bean的类名、作用域、依赖关系、属性值等信息。它是Spring框架中定义bean的元数据的核心表示形式。

BeanDefinitionHolder是一个包装类,用于持有BeanDefinition对象以及与之关联的bean名称。它包含两个主要成员变量:bean名称和BeanDefinition对象。

在Spring框架的内部,BeanDefinitionHolder常用于在注册表中存储和管理bean的定义。它提供了一个包装机制,使得可以将bean名称与其对应的BeanDefinition关联起来,并一起存储在注册表中。

通过BeanDefinitionHolder,可以轻松地访问和操作与特定bean相关联的BeanDefinition。它提供了一种方便的方式来获取bean的名称、获取和设置BeanDefinition的属性值等操作。

candidateNames是一个string数组,从上面的截图可以看出,当前会有5个BeanDefinitionNames,然后进入for循环,for循环要做的事情就是把这些BeanDefinition拿出来判断是否是候选配置类。

else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}

ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)方法就是用来判断是否是候选类,可以看出configCandidates.add方法就只在这里用到了。

public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

		String className = beanDef.getBeanClassName();
		if (className == null || beanDef.getFactoryMethodName() != null) {
			return false;
		}

		AnnotationMetadata metadata;
		if (beanDef instanceof AnnotatedBeanDefinition &&
				className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
			// Can reuse the pre-parsed metadata from the given BeanDefinition...
			metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
		}
		else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
			// Check already loaded Class if present...
			// since we possibly can't even load the class file for this Class.
			Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
			if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
					BeanPostProcessor.class.isAssignableFrom(beanClass) ||
					AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
					EventListenerFactory.class.isAssignableFrom(beanClass)) {
				return false;
			}
			metadata = AnnotationMetadata.introspect(beanClass);
		}
		else {
			try {
				MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
				metadata = metadataReader.getAnnotationMetadata();
			}
			catch (IOException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Could not find class file for introspecting configuration annotations: " +
							className, ex);
				}
				return false;
			}
		}

		Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
		else {
			return false;
		}

		// It's a full or lite configuration candidate... Let's determine the order value, if any.
		Integer order = getOrder(metadata);
		if (order != null) {
			beanDef.setAttribute(ORDER_ATTRIBUTE, order);
		}

		return true;
	}

虽然上述的代码看起来很长,而且杂七杂八的逻辑判断让我们不知道如何下手,这个时候可以打端点调试一下。

在这里插入图片描述

唉哟,beanDef是RootBeanDefinition类型,所以会进入else if的逻辑里面,也就到了我们这段代码的第一个核心逻辑。因为这里有个return,所以在看的时候需要关注一下。

Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
			if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
					BeanPostProcessor.class.isAssignableFrom(beanClass) ||
					AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
					EventListenerFactory.class.isAssignableFrom(beanClass)) {
				return false;
			}

这段代码用于过滤掉特定类型的bean,不对它们应用后续的处理。

具体来说,改代码中的条件判断用于检查给定的beanClass是否属于以下四个类型之一:

1、BeanFactoryPostProcessor:实现了BeanFactoryPostProcessor接口的类,用于在容器实例化任何其他bean之前对bean工厂进行自定义修改。

2、BeanPostProcessor:实现了BeanPostProcessor接口的类,用于在容器实例化bean时对bean进行自定义处理,例如初始化前后的操作。

3、AopInfrastructureBean:实现了AopInfrastructureBean接口的类,用于定义AOP基础设施bean,如AOP代理工厂等。

4、EventListenerFactory:实现了EventListenerFactory接口的类,用于创建事件监听的工厂。

如果给定的beanClass是上述四个类型之一,那么该代码会返回false,表示不对该类型的bean应用后续的处理。

打端点之后其实可以知道Spring自己注入的那些都会被跳过,唯独就是我们手动registry(AopConfig.class)

在这里插入图片描述

else {
			try {
				MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
				metadata = metadataReader.getAnnotationMetadata();
			}
			catch (IOException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Could not find class file for introspecting configuration annotations: " +
							className, ex);
				}
				return false;
			}
		}

		Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
		else {
			return false;
		}

		// It's a full or lite configuration candidate... Let's determine the order value, if any.
		Integer order = getOrder(metadata);
		if (order != null) {
			beanDef.setAttribute(ORDER_ATTRIBUTE, order);
		}

然后我们可以知道metadataReader就是元数据的读取,然后看是否含有@Configuration注解,我们的AopConfig其实是没有@Configutation注解的。

// 容器启动
AnnotationConfigApplicationContext annotationConfigApplicationContext =
       new AnnotationConfigApplicationContext(AopConfig.class);

// AopCofig类,类似于SpringBoot的Application启动类
@ComponentScan(value = {"com.qhyu.cloud.**"})
public class AopConfig {
	// 啥也没有
}

所以第二个核心逻辑就是isConfigurationCandidate(matedata),ConfigurationClassUtils类中。

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
		// Do not consider an interface or an annotation...
		if (metadata.isInterface()) {
			return false;
		}

		// Any of the typical annotations found?
		for (String indicator : candidateIndicators) {
			if (metadata.isAnnotated(indicator)) {
				return true;
			}
		}

		// Finally, let's look for @Bean methods...
		return hasBeanMethods(metadata);
	}

如果是个接口直接返回false,说明得是类才可以。candidateIndicators是一个静态的全局变量,内容是static静态代码块里加载的。

private static final Set<String> candidateIndicators = new HashSet<>(8);

	static {
		candidateIndicators.add(Component.class.getName());
		candidateIndicators.add(ComponentScan.class.getName());
		candidateIndicators.add(Import.class.getName());
		candidateIndicators.add(ImportResource.class.getName());
	}

很明朗了对吧。就是包含这些注解的,都会被加入到configCandidates配置后端名单中。

跳出方法查看beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);这行代码将一个名为 CONFIGURATION_CLASS_ATTRIBUTE 的属性设置为值 CONFIGURATION_CLASS_LITE。

在Spring中,CONFIGURATION_CLASS_ATTRIBUTE是一个常量,用于指示配置类的类型。它通常用于标识配置类是以何种方式加载和处理的。

CONFIGURATION_CLASS_LITE 和 CONFIGURATION_CLASS_FULL 是两种可能的取值,表示不同类型的配置类:

1、CONFIGURATION_CLASS_LITE:表示轻量级配置类。这种类型的配置类通常是通过@Configuration注解进行标记的,但不包含任何特殊的逻辑处理。它们可能指示简单地定义一些bean的声明,而没有涉及复杂的依赖注入、条件化配置或其他高级功能。轻量级配置类在处理过程中会更加简单和高效。

2、CONFIGURATION_CLASS_FULL:表示完全配置类。这种类型的配置类包含了更多的复杂逻辑和功能,如条件配置、依赖注入、AOP、事件处理等。完全配置类可能使用了更多的Spring功能和特性,需要进行更全面的解析和处理。

重回processConfigBeanDefinitions

去过了最远的地方,还得回到最原来的地方。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		//registry 也就是BeanFactory(ConfigurableListableBeanFactory)
		// configCandidates Candidates候选人
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// 当前BeanFacoty中存在的BeanDefinitonNames
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			// 根据名称获取BeanDefinition
			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);
				}
			}
			// 检测配置候选人的条件,此处为true才会加入配置候选人
			// private static final Set<String> candidateIndicators = new HashSet<>(8);
			//
			//	static {
			//		candidateIndicators.add(Component.class.getName());
			//		candidateIndicators.add(ComponentScan.class.getName());
			//		candidateIndicators.add(Import.class.getName());
			//		candidateIndicators.add(ImportResource.class.getName());
			//	}
			// 也就是说只要包含这些也都是配置候选人
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

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

当ConfigurationClassUtils类的isConfigurationCandidate返回true之后就说明ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)返回true。所以会把当前beanDefinitionHolder加入到配置候选集合中。当然如果配置候选集合为空就直接return了。

接下来这个就是单纯的排序,这边直接跳过,不耽误时间,有兴趣自己深入,或者后续单独讲一期排序。

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

SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

排序下面这段代码用于获取并设置组件扫描时使用的Bean名称生成器。

首先定义了一个SingletonBeanRegistry类型的变量sbr并初始化为null。然后,通过检查registry对象是否是SingletonBeanRegistry的实例来确定是否可以进行后续的操作。

如果registry是SingletonBeanRegistry的实例,说明它是一个单例Bean注册表,可以用于获取和设置单例Bean。接下来,讲registry对象强制转换为SingletonBeanRegistry类型,并将其赋值给sbr变量。

代码尝试从sbr中获取名为AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR的单例Bean。这个常量是用于指定配置类的Bean名称生成器的键。如果能够获取到该单例Bean,说明已经为配置类设置了特定的Bean名称生成器。如果generator不为null,代码将获取到的generator赋值给两个成员变量:componentScanBeanNameGenerator和importBeanNameGenerator。这两个成员变量分别用于组件扫描和导入Bean时使用的Bean名称生成器。

通过这段代码,可以实现以下功能:

  • 检查是否有合适的单例Bean注册表可用。
  • 获取配置类的Bean名称生成器。
  • 设置组件扫描和导入Bean时使用的Bean名称生成器。

这样做的目的是为了在Spring的组件扫描和Bean加载过程中使用适当的Bean名称生成器,以确保生成的Bean名称符合预期并与其他组件协调一致。

// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		// 这是候选人Set,去重
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		// 以及解析了的配置类
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			// 解析我们的配置类,我们使用AnnotationConfigApplicationContent启动的时候,使用的构造函数进行创建
			// 也就是加入了@ComponentScan注解,需要根据里面的路径扫描包路径,从而找到所有需要被Spring管理的Bean的beanDefiniton信息
			// ConfigurationClassParser去解析我们的AopConfig类.这个类只会在启动的时候呗调用一次
			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());
			}
			// ImportBeanDefinitionRegistrar扫描实现了这个接口的方法
			// ConfigurationClassParser解析的在这里又读出来,这里需要打断点再观察一下。
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);
			processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						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());

这段代码是最最最核心的内容,还会引入一个新的类来解析和处理候选配置类,本章我们主要梳理这段代码的逻辑,深入的源码将在下一章节来分析。

1、创建一个ConfigurationClassParser对象并进行初始化。该对象用于解析候选配置类,并生成相应的配置类对象(ConfigurationClass)和相关的Bean定义。

2、创建一个Set<BeanDefinitionHolder>类型的变量candidates,用于存储候选的Bean定义持有者。这些候选人是从configCandidates中获取的,它是一个包含待解析的候选配置类的集合。

3、创建一个Set<ConfigurationClass>类型的变量alreadyParsed,用于存储已经解析过的配置类。

4、进入循环,直到候选人集合为空。在每次循环迭代中,执行以下操作:
a. 启动一个性能追踪步骤,记录解析过程的性能指标。
b. 调用parser.parse(candidates)方法,解析candidates中的候选配置类。在解析过程中,将根据配置类的内容创建相应的Bean定义。
c. 调用parser.validate()方法,对解析后的配置类进行验证,确保它们符合规范。
d. 获取解析后的配置类集合,并将其与已解析的配置类集合进行比较,筛选出新增的配置类。
e. 如果this.reader为null,则创建一个ConfigurationClassBeanDefinitionReader对象,用于读取模型并基于其内容创建Bean定义。
f. 调用this.reader.loadBeanDefinitions(configClasses)方法,将配置类转换为Bean定义,并注册到Bean定义注册表中。
g. 将已解析的配置类添加到alreadyParsed集合中。
h. 根据新的Bean定义数量更新candidateNames数组,并筛选出新增的候选人Bean定义。
i. 清空candidates集合,准备下一次循环迭代。

整个过程会迭代解析和处理所有的候选配置类,将它们转换为相应的Bean定义,并注册到Bean定义注册表中。这样,这些候选配置类中声明的Bean就可以在应用程序中使用和管理了。

总结

本章就ConfigurationClassPostProcessor的processConfigBeanDefinitions方法做了深入的源码分析,描述了方法内部所完成的spring实例化的过程,具体的在项目启动过程中如何将我们自己定义的需要被spring管理的bean的定义信息放入工厂中,会在下一个章节进行详细的分析。

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

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

相关文章

linux部署页面内容

/bin&#xff1a;该目录包含了常用的二进制可执行文件&#xff0c;如ls、cp、mv、rm等等。 /boot&#xff1a;该目录包含了启动Linux系统所需的文件&#xff0c;如内核文件和引导加载程序。 /dev&#xff1a;该目录包含了所有设备文件&#xff0c;如硬盘、光驱、鼠标、键盘等等…

LeetCode 刷题记录——从零开始记录自己一些不会的(二)

20. 替换后的最长重复字符 题意 给你一个字符串 s 和一个整数 k 。你可以选择字符串中的任一字符&#xff0c;并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。 在执行上述操作后&#xff0c;返回包含相同字母的最长子字符串的长度。 思路 代码 class Solution…

学习MLPERF

测试基准与标准 | BenchCouncil 其中涉及AI的有如下&#xff1a; AI (1) AIBench Training AIBench 培训采用平衡的 AI 基准测试方法&#xff0c;考虑全面性、代表性、可负担性和可移植性。该方法广泛调查人工智能任务和模型&#xff0c;并在最大程度上涵盖了算法级、系统级…

玩转YAML配置文件占位符 ,同事纷纷直呼大佬

配置文件占位符 Spring Boot配置文件支持占位符&#xff0c;一些用法如下&#xff1a; 为server.port设置一个随机端口 server: port: ${random.int} 其他随机占位符 // 随机数占位符${random.value} - 类似uuid的随机数&#xff0c;没有"-"连接${random.int} - 随…

【李沐深度学习笔记】矩阵计算(5)

课程地址和说明 线性代数实现p4 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 本节是第五篇&#xff0c;由于CSDN限制&#xff0c;只能被迫拆分 矩阵计算 多元函数的等高线 此处参考视频&#xff1a;熟肉)多元…

c语言-实用调试技巧

什么是bug&#xff1f; 程序中出现的问题 调试是什么&#xff1f;有多重要&#xff1f; 测试的基本步骤&#xff1a;发现程序错误的存在 以隔离、消除等方式对错误进行定位&#xff0c;确定错误产生的原因&#xff0c;提出纠正错误的解决办法&#xff0c;对程序错误予以改正…

【Servlet】第一个 Servlet 项目

第一个 Servlet 项目 一. Servlet 是什么二. Servlet 主要做的工作三. 第一个 Servlet 程序1. 创建项目2. 引入依赖3. 创建目录4. 编写代码5. 打包程序6. 部署程序7. 验证程序 四. 更方便的部署方式1. 安装 Smart Tomcat 插件2. 配置 Smart Tomcat 插件 一. Servlet 是什么 Se…

2023华为杯数学建模研赛思路分享——最全版本E题技术文档深度解析已出

2023华为杯数学建模研赛E题最新完整版技术文档已出 更多内容加群了解咨询【云顶数模科研交流群】&#xff1a; 正在跳转​qm.qq.com/cgi-bin/qm/qr?_wv1027&kzZ9YYiFSIhMwasovgcr-Fq6wp_ZF2-To&authKeyIDnk4VjKwk4FWcwTKW1ye8qfCOZjKbx%2FeLQVSFeD%2BrrUNJnhWAYwC%2…

OceanBase再获OSCAR两项大奖,坚定开源开放

2023 年 9 月 21 日&#xff0c;由中国信息通信研究院&#xff08;简称“中国信通院”&#xff09;、中国通信标准化协会联合主办的“OSCAR 开源产业大会”在京召开。本次发布了 2023 年可信开源评估结果&#xff0c;OceanBase 通过 可信开源 社区评估&#xff0c;荣获 “OSCAR…

【打开新世界大门】看测试老鸟如何把API 测试玩弄在鼓掌之间

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 一、API 测试的基本步骤 我介绍过当今互联网产品的测试策略往往会采用菱形结构&#xff0c;即重量级 AP…

蜣螂优化(DBO)算法的5种最新变体(含MATLAB代码)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

spark Structured报错解决

报错&#xff0c;不想看原因的直接去解决方案试试 Exception in thread "main" java.lang.IllegalArgumentException: Pathname /C:/Users/Administrator/AppData/Local/Temp/1/temporary-611514af-8dc5-4b20-9237-e5f2d21fdf88/metadata from hdfs://master:8020/C…

C/C++连接数据库,包含完整代码。

C/C连接数据库 本篇文章意在简洁明了的在linux环境下使用C/C连接远程数据库&#xff0c;并对数据库进行增删查改等操作。我所使用的环境是centos7&#xff0c;不要环境除环境配置外&#xff0c;代码是大同小异的。完整代码在最底部&#xff01;&#xff01;&#xff01; 1.前…

[极客大挑战 2019]RCE ME 取反绕过正则匹配 绕过disable_function设置

目录 取反 1.蚁剑插件绕过 2.baypass disable_function open_dir/disable_function putenv()/LD_PRELOAD 来绕过限制 利用条件 利用思路 有意思。。。。 <?php error_reporting(0); if(isset($_GET[code])){$code$_GET[code];if(strlen($code)>40){die("Th…

redis的安装、基础命令及常用数据结构

文章目录 前言一、Redis安装1.Ubuntu下安装&#xff08;1&#xff09;切换到root用户下&#xff08;2&#xff09;使用apt安装redis5&#xff08;3&#xff09;为了使redis支持远程连接&#xff0c;修改以下地方&#xff08;4&#xff09;验证安装是否成功 2.Centos7下安装&…

【C++】STL简介 | string类的常用接口

目录 STL简介 学string类前的铺垫 概念 为什么要学string类 string类的底层&#xff08;了解&#xff09; 编码表的故事 string类的常用接口与应用 3个必掌握的构造 赋值 访问字符operator[] 初识迭代器&#xff08;iterator&#xff09; 反向迭代器 用范围for遍历…

uniapp获取一周日期和星期

UniApp可以使用JavaScript中的Date对象来获取当前日期和星期几。以下是一个示例代码&#xff0c;可以获取当前日期和星期几&#xff0c;并输出在一周内的每天早上和晚上&#xff1a; // 获取当前日期和星期 let date new Date(); let weekdays ["Sunday", "M…

剔除数据中的异常值(python实现)

目录 一、3σ原则 二、箱线图发现异常值 三、boxcox数据变换 一、3σ原则 该准则仅局限于对正态或近似正态分布的样本数据处理,此外,当测量次数少的情形用准则剔除粗大误差是不够可靠的。 异常值是指样本中的个别值,其数值明显偏离其余的观测值。异常值也称离群点,异常…

xpath定位不包含某种属性的元素

今天定位一个页面中的input文本框&#xff0c;发现竟然有两个几乎一模一样的html代码的input文本框。 唯一不同的是&#xff0c;一图中的input有一个comps"[object Object],[object Object]"的属性和属性值&#xff0c;二图则没有。我要定位的是二图中的input&#x…

AUTOSAR实战篇:手把手带你搞定Watchdog协议栈

AUTOSAR实战篇:手把手带你搞定Watchdog协议栈 前言 小T出品,必是精品! 手把手搞定Watchdog协议栈,你值得拥有! 正文 在进行Watchdog协议栈实战之前,建议先阅读小T之前有关Watchdog协议栈的两篇文章《Watchdog协议栈上》与《Watchdog协议栈下》先了解下在AUTOSAR框架下的W…