Sping源码(七)—context: component-scan标签如何扫描、加载Bean

序言

简单回顾一下。上一篇文章介绍了从xml文件context component-scan标签的加载流程到ConfigurationClassPostProcessor的创建流程。
本篇会深入了解context component-scan标签底层做了些什么。

component-scan

早期使用Spring进行开发时,很多时候都是注解 + 标签的形式来进行类的配置。而在之前文章中有介绍过xml在加载解析时,会对 各种标签进行解析。那注解修饰的类是什么时候被Spring识别的呢?

就是在component-scan标签解析时,获取对应base-package所对应的包下所有符合条件的类,从而进行处理

流程图

在这里插入图片描述

parse - 主流程

接下来从parse() 主流程开始,看看每一步都有哪些细节。

@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		//获取base-package属性值
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		//解析占位符
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		//解析base-package属性值进行拆分,返回数组
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		// Actually scan for bean definitions and register them.
		// 创建和配置ClassPathBeanDefinitionScanner对象
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		//执行扫描,返回bean定义集合并注册到BeanFactory
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		//注册组件(包括注册一些内部的注解后置处理器(ConfigurationClassPostProcessor.class)等 触发注册事件
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}

configureScanner解析标签属性

方法主要是看context:component-scan标签中是否包含scope-resolver、resource-pattern、use-default-filters等属性值,并创建scanner对象进行封装。
在这里插入图片描述

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {

		//去除部分无用代码....

		boolean useDefaultFilters = true;
		//解析use-default-filters属性值,默认为true
		if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
			useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
		}

		// Delegate bean definition registration to scanner class.
		//创建ClassPathBeanDefinitionScanner对象,将bean定义注册委托给scanner
		ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
		scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
		scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
		//解析name-generator属性值
		if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
			scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
		}

			//如果包含name-generator属性值,则按照Spring规则生成beanName.
			parseBeanNameGenerator(element, scanner);

			//解析scope-resolver属性值
			parseScope(element, scanner);

		}
		//解析include-filter和exclude-filter子标签属性。
		parseTypeFilters(element, scanner, parserContext);

		return scanner;
	}

parseTypeFilters解析子标签

值得注意的和扩展的是parseTypeFilters方法。方法中会对 context:exclude-filter 子标签和 context:exclude-filter 子标签进行处理。

type类型:
assignable-指定扫描某个接口派生出来的类

annotation-指定扫描使用某个注解的类

aspectj-指定扫描AspectJ表达式相匹配的类

custom-指定扫描自定义的实现了

org.springframework.core.type.filter.TypeFilter接口的类 regex-指定扫描符合正则表达式的类

context:exclude-filter
标签的作用是:在base-package扫描时,让指定的类不被Spring管理。
例子中代表标记了Controller注解的类不被Spring识别管理

<!--举个栗子 -->
<context:component-scan base-package="com.example">
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

context:include-filter
标签的作用是:在base-package扫描时,额外扫描指定的类被Spring管理
例子中User类虽然没有注解修饰,但也会被加载。

<!--举个栗子 -->
<context:component-scan base-package="com.example">
      <context:include-filter type="assignable" expression="org.springframework.User"/>
</context:component-scan>

额外加载User类。

package org.springframework;

public class User {
	
}

方法会遍历context:componet-scan标签下的include和exclude子标签,并加到scanner的属性中。

protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
		// Parse exclude and include filter elements.
		ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				String localName = parserContext.getDelegate().getLocalName(node);
					if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
						TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
						scanner.addIncludeFilter(typeFilter);
					}
					else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
						TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
						scanner.addExcludeFilter(typeFilter);
					}		
			}
		}
	}

其中,Scanner对象在创建时,构造方法中会对IncludeFilter进行初始化赋值操作。
划重点!!!!!!!!!!!!后面includeFilters有用到。在这也可以看出,为什么只会识别@Component注解和@Configuration

	protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
		return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters,
				readerContext.getEnvironment(), readerContext.getResourceLoader());
	}

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
			Environment environment, @Nullable ResourceLoader resourceLoader) {
		
		//删除无用代码
		if (useDefaultFilters) {
			registerDefaultFilters();
		}
	}

protected void registerDefaultFilters() {
		//删掉无用代码
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
	}

@Configuration
Configaration注解上也被@Component修饰,所以也可以被识别到。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
}

doScan-扫描package下所有class文件

方法主要是获取到base-package包下所有类,并遍历看是否符合条件让Spring进行管理。其中findCandidateComponents会对类进行筛选。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		//遍历basePackage
		for (String basePackage : basePackages) {
			//获取basePackage下所有符合要求的Bean
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				//解析@Scope注解
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				//用生成器生成beanName
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					// 处理定义在目标类上的通用注解,包括@Lazy,@Primary,@DependsOn,@Role,@Description
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				//再次检查beanName是否注册过,如果注册过,检查是否兼容
				if (checkCandidate(beanName, candidate)) {
					//将beanName和beanDefinition封装到BeanDefinitionHolder
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					//注册BeanDefinition
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

findCandidateComponents - 筛选符合条件的类

代码会走scanCandidateComponents方法逻辑。

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

scanCandidateComponents
获取package下所有class文件,并转换成Resource -> MetadataReader读取数据进行判断。
如果满足isCandidateComponent方法的逻辑,则创建ScannedGenericBeanDefinition对象封装Bean信息。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		//删除无用代码。。。。
		
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			//packageSearchPath: "classpath*:com/example/*/**.class"
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			for (Resource resource : resources) {
				if (resource.isReadable()) {
					try {
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
						//判断该类是否允许被Spring识别
						if (isCandidateComponent(metadataReader)) {
							//创建BeanDefinition封装Bean信息
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setSource(resource);
							if (isCandidateComponent(sbd)) {
								candidates.add(sbd);
							}
						}
					}
				}
			}
		}
		return candidates;
	}

isCandidateComponent
根据配置 context:include-filter 和 context:exclude-filter 规则进行过滤,如果都没有配置,则此时 includeFilters 属性中有默认值 @Component ,所以此处只会保留包含 @Component注解的类

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

isConditionMatch
创建ConditionEvaluator对象,并在shouldSkip()方法中判断类是否含有 @Conditional注解,是否符合@Conditional中的类加载条件

private boolean isConditionMatch(MetadataReader metadataReader) {
		if (this.conditionEvaluator == null) {
			this.conditionEvaluator =
					new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
		}
		return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
	}

如果类中没有@Conditional注解,则直接返回,否则获取Conditional注解中value属性值并进行加载。递归调用,看是否符合家在条件。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
		//如果metadata为空或者没有@Conditional注解,直接返回false
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}
		//第一次进来时phase为null,所以一定会走下面方法
		//判断是否是@Configuration注解,如果是,则进入shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION)
		if (phase == null) {
			if (metadata instanceof AnnotationMetadata &&
					//判断是否是抽象类 return false
					//判断是否被Component、ComponentScan、Import、ImportResource注解修饰 return true
					//判断是否被Bean修饰 return true
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
				//递归调用shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION)
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}

		List<Condition> conditions = new ArrayList<>();
		//获取@Conditional注解的value属性
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				//创建value属性所对应的Condition类
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}
		//对conditions进行排序
		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
			//此逻辑为:1.requiredPhase不是ConfigurationCondition的实例
			//2.phase==requiredPhase,从上述的递归可知:phase可为ConfigurationPhase.PARSE_CONFIGURATION或者ConfigurationPhase.REGISTER_BEAN
			//3.condition.matches(this.context, metadata)返回false
			//如果1、2或者1、3成立,则在此函数的上层将阻断bean注入Spring容器
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

registerComponents 注册组件 (ConfigurationClassPostProcessor.class等)

将doScan过滤出来的beanDefinition添加到compositeDef的nestedComponents属性中。
获取annotation-config属性值(默认为true),并调用registerAnnotationConfigProcessors方法进行注册。

protected void registerComponents(
			XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

		Object source = readerContext.extractSource(element);
		//根据tagName(此处为context:component-scan)和source创建CompositeComponentDefinition对象
		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
		// 将扫描到的所有beanDefinition添加到compositeDef的nestedComponents属性中
		for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
			compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
		}

		// Register annotation config processors, if necessary.
		boolean annotationConfig = true;
		if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
			//获取component-scan标签的annotation-config属性值
			annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
		}
		//annotationConfig默认为true.
		if (annotationConfig) {
			//注册注解配置处理器
			Set<BeanDefinitionHolder> processorDefinitions =
					AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
			for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
				// 将注册的注解后置处理器的BeanDefinition添加到compositeDef的nestedComponents属性中
				compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
			}
		}

		readerContext.fireComponentRegistered(compositeDef);
	}

registerAnnotationConfigProcessors

此时,又回到了我们上一篇文章所讲的 ConfigurationClassPostProcessor类的由来。就是在此处进行的加载。

public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME =
			"org.springframework.context.annotation.internalConfigurationAnnotationProcessor";

判断当前BeanFacroty中是否包含internalConfigurationAnnotationProcessor,如果不包含,则创建ConfigurationClassPostProcessor.class

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		//获取beanFactory
		DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
		if (beanFactory != null) {
			if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
				//设置依赖比较器
				beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
			}
			if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
				//设置自动装配解析器
				beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			}
		}

		//创建BeanDefinitionHolder集合
		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
		// 注册内部管理的用于处理@configuration注解的后置处理器的bean
		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));
		}

		// 注册内部管理的用于处理@Autowired,@Value,@Inject以及@Lookup注解的后置处理器bean
		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		// 注册内部管理的用于处理JSR-250注解,例如@Resource,@PostConstruct,@PreDestroy的后置处理器bean
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
		if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition();
			try {
				def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
						AnnotationConfigUtils.class.getClassLoader()));
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
			}
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		// 注册内部管理的用于处理@EventListener注解的后置处理器的bean
		if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
		}
		// 注册内部管理用于生产ApplicationListener对象的EventListenerFactory对象
		if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
		}

		return beanDefs;
	}

下一篇文章详细看看 ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry()方法做了什么。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智能算法 | Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法

智能算法 | Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法 目录 智能算法 | Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法效果一览基本介绍程序设计参考资料效果一览 基本介绍 Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法 融合自适应…

ds18b20温度传感器驱动程序

ds18b20驱动程序 有了之前延时的方法&#xff0c;那么实现一个单总线数据传输的传感器驱动程序就非常简单了。下面我们套用杂项驱动框架来编写ds18b20驱动程序。 实现需要明确的是&#xff1a;**ds18b20驱动的本质是通过2440的gpio&#xff0c;通过给定的时序对ds18b20的读写数…

【介绍下WebStorm开发插件】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

保护你的网站:了解5种常见网络攻击类型及其防御方法

随着互联网的迅猛发展&#xff0c;针对网站的各种类型的网络攻击随之增加&#xff0c;网络攻击事件层出不穷&#xff0c;由此&#xff0c;如何保护网站安全成为每个网站所有者的重要议题。在下面的内容中&#xff0c;我们将探讨5种常见网络攻击类型及其防御方法&#xff0c;以帮…

SNETCracker--超级弱口令检查工具简介

一、简介 SNETCracker 超级弱口令检查工具是一款Windows平台的弱口令审计工具&#xff0c;支持批量多线程检查&#xff0c;可快速发现弱密码、弱口令账号&#xff0c;密码支持和用户名结合进行检查&#xff0c;大大提高成功率&#xff0c;支持自定义服务端口和字典。 二、SNE…

常见内网系统网络结构及nginx代理配置

系统网络结构图及nginx配置 1.系统网络结构图2.Nginx网络配置2.1请求从互联网区访问到内网区2.2 请求从内网访问互联网 1.系统网络结构图 传统公司服务部署网络都会分区&#xff0c;应用都部署在内网区&#xff0c;请求通过dmz区转出内网与互联网发生交互。 结构图详解&#…

springCloud集成activiti5.22.0流程引擎

springCloud集成activiti5.22.0流程引擎 点关注不迷路&#xff0c;欢迎再访&#xff01; 精简博客内容&#xff0c;尽量已行业术语来分享。 努力做到对每一位认可自己的读者负责。 帮助别人的同时更是丰富自己的良机。 小编最近工作需要涉及到流程&#xff0c;由于网络上5.22版…

CHARLS轻松发二区,只用了COX回归模型 | CHARLS CLHLS CFPS 公共数据库周报(4.3)...

零基础CHARLS发论文&#xff0c;不容错过&#xff01; 长期回放更新指导&#xff01;适合零基础&#xff0c;毕业论文&#xff0c;赠送2011-2020年CHARLS清洗后的数据全套代码&#xff01; CHARLS公共数据库 CHARLS数据库简介中国健康与养老追踪调查(China Health and Retireme…

C++初阶学习第三弹——类与对象(上)——初始类与对象

前言&#xff1a; 在前面&#xff0c;我们已经初步学习了C的一些基本语法&#xff0c;比如内敛函数、函数重载、缺省参数、引用等等&#xff0c;接下来我们就将正式步入C的神圣殿堂&#xff0c;首先&#xff0c;先给你找个对象 目录 一、类与对象是什么&#xff1f; 二、类的各…

ArtNeRF、Attention Control、Pixel is a Barrier、FilterPrompt

本文首发于公众号&#xff1a;机器感知 ArtNeRF、Attention Control、Pixel is a Barrier、FilterPrompt ArtNeRF: A Stylized Neural Field for 3D-Aware Cartoonized Face Synthesis Recent advances in generative visual models and neural radiance fields have greatly …

【笔试训练】day11

1.游游的水果大礼包 思路&#xff1a; 枚举。假设最后的答案是x个a礼包&#xff0c;y个b礼包&#xff0c;得到一个式子&#xff1a;ansa*xb*y 我们可以枚举x的数量&#xff0c;这样就能变相的把y的求出来。呃这就是鸡兔同笼问题嘛 x最大的范围是多少呢&#xff1f;也就是a礼…

【CouchDB 与 PouchDB】

CouchDB是什么 CouchDB&#xff0c;全名为Apache CouchDB&#xff0c;是一个开源的NoSQL数据库&#xff0c;由Apache软件基金会管理。CouchDB的主要特点是使用JSON作为存储格式&#xff0c;使用JavaScript作为查询语言&#xff08;通过MapReduce函数&#xff09;&#xff0c;并…

面试二十二、跳表SkipLists

跳表全称为跳跃列表&#xff0c;它允许快速查询&#xff0c;插入和删除一个有序连续元素的数据链表。跳跃列表的平均查找和插入时间复杂度都是O(logn)。快速查询是通过维护一个多层次的链表&#xff0c;且每一层链表中的元素是前一层链表元素的子集&#xff08;见右边的示意图&…

day07 51单片机-18B20温度检测

18B20温度检测 1.1 需求描述 本案例讲解如何从18B20传感器获取温度信息并显示在LCD上。 1.2 硬件设计 1.2.1 硬件原理图 1.2.3 18B20工作原理 可以看到18B20有两根引脚负责供电&#xff0c;一根引脚负责数据交换。18B20就是通过数据线和单片机进行数据交换的。 1&#xf…

前端项目中使用插件prettier/jscodeshift/json-stringify-pretty-compact格式化代码或json数据

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、json代码格式化-选型二、json-stringify-pretty-compact简单试用三、prettier在前端使用四、查看prettier支持的语言和插件五、使用prettier格式化vue代码最终效果如图&#xff1a; ![在这里插入图片描述](https://im…

【ruoyi-vue】登录解析(后端)

调试登录接口 进入实现类可以有 验证码校验 登录前置校验 用户验证 验证码校验 通过uuid获取redis 中存储的验证码信息&#xff0c;获取后对用户填写的验证码数据进行校验比对 用户验证 1.进入控制器的 /login 方法 2.进入security账号鉴权功能&#xff0c;经过jar内的流…

python逆向基础流程(纯小白教程)

一&#xff0c;例题链接 NSSCTF | 在线CTF平台 二&#xff0c;文件特征 使用工具查看文件信息&#xff0c;发现是pyinsatller打包的exe文件&#xff0c;如果硬用ida分析成汇编或c语言根本摸清楚程序的逻辑&#xff0c;所以思路是反编译成py文件直接分析python代码 三&#xf…

【论文推导】基于有功阻尼的转速环PI参数整定分析

前言 在学习电机控制的路上&#xff0c;PMSM的PI电流控制是不可避免的算法之一&#xff0c;其核心在于内环电流环、外环转速环的设置&#xff0c;来保证转速可调且稳定&#xff0c;并且保证较好的动态性能。整个算法仿真在《现代永磁同步电机控制原理及matlab仿真》中已详细给出…

VUE项目使用.env配置多种环境以及如何加载环境

第一步&#xff0c;创建多个环境配置文件 Vue CLI 项目默认使用 .env 文件来定义环境变量。你可以通过创建不同的 .env 文件来为不同环境设置不同的环境变量&#xff0c;例如&#xff1a; .env —— 所有模式共用.env.local —— 所有模式共用&#xff0c;但不会被 git 提交&…

Clickhouse离线安装教程

https://blog.51cto.com/u_15060531/4174350 1. 前置 1.1 检查服务器架构 服务器&#xff1a;Centos7.X 需要确保是否x86_64处理器构架、Linux并且支持SSE 4.2指令集 grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 supported" || echo "SSE 4.2 …
最新文章