spring(六)--------spring的扫描机制

news/2024/4/26 6:00:45/文章来源:https://blog.csdn.net/qq_35599414/article/details/129152865

spring的扫描一般可以通过两种方式:

测试类:

@Component
public class Scan_A {@PostConstructpublic void init(){System.out.println("-----------Scan_A");}}

1)、@ComponentSscan注解

public class ComponentScanTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ContextConfig.class);}
}

配置类:(可配@Compnent,也可不配,都会生效)

@ComponentScan("com.spring.demo.scan")
public class ContextConfig {
}

启动此时控制台:

----------Scan_A

则表示扫描到了。

2)、使用api调用扫描

public class ApplicationContextTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.scan("com.spring.demo.scan");context.refresh();}
}

此时运行,照样能打印----------Scan_A

两种方法实例化了各自实例化了自己的扫描器,但最终都是调用doscan方法完成解析的。

(关于两种方式的区别,后面说明,不过我们最常用的应该就是第一种方式)

==========================我们先以第一种情况作为分析========================

上一篇文章中,invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors,registry)代码行中(情况2),ConfigurationClassPostProcessor执行了其子接口实现类。我们说到这个实现类完成对注解类的扫描。我们就以此为入口。

调用链:ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(子接口实现方法)

--------》processConfigBeanDefinitions(registry)

/*** ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口的实现类* 功能:完成对注解的扫描解析,并放入BeanDefinitionMap(此时未进行实例化为bean,应为采用的是ASM字节码技术,所以不会在这里实例化)* registry------容器对象* *//*** Build and validate a configuration model based on the registry of* {@link Configuration} classes.*/public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();//获取内置的+我们提供的配置类的BeanDefinition名称String[] candidateNames = registry.getBeanDefinitionNames();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);}}//判断配置类是全配置类(除了用@CompnentScan注解外还用了@Compnent)或者半配置类(只用了@CompnentScan)//是则加入集合(我们这里一般会有6个配置类,5个内置的+1个我们提供的(当然我们可以提供多个,不过一般是一个)// 此时只有我们提供配置类有用到@CompnentScan,所以这里只会加入一个else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {return;}//排序,由于一般情况下是一个,所以没什么影响// Sort by previously determined @Order value, if applicableconfigCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});//获取Bean名称的生成策略// Detect any custom bean name generation strategy supplied through the enclosing application contextSingletonBeanRegistry 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();}//实例化一个配置类的解析器//此时构造方法传入一个componentScanBeanNameGenerator----类型AnnotationBeanNameGenerator(即一个Bean的名称生成策略)// Parse each @Configuration classConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);//去重Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());//开始循环解析配置类do {//调用parse方法进行解析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 contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);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());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}

进入parser.parse(candidates)---》parse()-----》processConfigurationClass()

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}//查看缓存是否存在(每解析完一个配置类会放进缓存)ConfigurationClass existingClass = this.configurationClasses.get(configClass);if (existingClass != null) {if (configClass.isImported()) {if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}// Otherwise ignore new imported config class; existing non-imported class overrides it.return;}else {// Explicit bean definition found, probably replacing an import.// Let's remove the old one and go with the new one.this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// Recursively process the configuration class and its superclass hierarchy.SourceClass sourceClass = asSourceClass(configClass, filter);do {//解析配置类sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);}while (sourceClass != null);this.configurationClasses.put(configClass, configClass);}

进入doProcessConfigurationClass()

@Nullableprotected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {//解析配置类是否加了@Compnent注解if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes first//如果加了,则处理内部类processMemberClasses(configClass, sourceClass, filter);}//处理配置类@propertySource注解(读取文件)// Process any @PropertySource annotationsfor (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}//解析@ComponentScan注解(@ComponentScans即可扫描多个包)// Process any @ComponentScan annotationsSet<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {//配置类存在@ComponentScan或@ComponentScans注解for (AnnotationAttributes componentScan : componentScans) {//解析这个@ComponentScan注解,完成对@Compnent注解的扫描,并返回Set<BeanDefinitionHolder>// The config class is annotated with @ComponentScan -> perform the scan immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (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());}}}}//解析@Import// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), filter, true);//解析@ImportResource // Process any @ImportResource annotationsAnnotationAttributes 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);}}//解析@Bean// Process individual @Bean methodsSet<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfacesprocessInterfaces(configClass, sourceClass);// Process superclass, if anyif (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;}

进入this.componentScanParser.parse(componentScan,sourceClass.getMetadata().getClassName())

开始解析@CompnentScan注解:

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {//实例化一个扫描器//registry----容器对象//useDefaultFilters----是否使用默认的过滤器//存在两种过滤器:icludeFilter引入过滤器、excludeFilter排除过滤器ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);//看看@CompnentScan注解中的nameGenerator属性(bean名称的生成策略,可以自己配置)是否有配置,有则获取Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);//设置bean名字的生成策略scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :BeanUtils.instantiateClass(generatorClass));//下面的都是对注解的属性的解析(scopedProxy、resourcePattern、includeFilters等等)ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");if (scopedProxyMode != ScopedProxyMode.DEFAULT) {scanner.setScopedProxyMode(scopedProxyMode);}else {Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));}scanner.setResourcePattern(componentScan.getString("resourcePattern"));//加入Include过滤器for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addIncludeFilter(typeFilter);}}for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addExcludeFilter(typeFilter);}}boolean lazyInit = componentScan.getBoolean("lazyInit");if (lazyInit) {scanner.getBeanDefinitionDefaults().setLazyInit(true);}Set<String> basePackages = new LinkedHashSet<>();String[] basePackagesArray = componentScan.getStringArray("basePackages");for (String pkg : basePackagesArray) {String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);Collections.addAll(basePackages, tokenized);}for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}if (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(declaringClass));}//加入Exclude过滤器scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {@Overrideprotected boolean matchClassName(String className) {return declaringClass.equals(className);}});//return scanner.doScan(StringUtils.toStringArray(basePackages));}

进入scanner.doScan()

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) {//使用ASM字节码技术读取对应的目录,解析符合的类(加了@Compnent注解)并返回BeanDefinition集合Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());//根据bean名字生成策略生成bean名称String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//查看是否已经存在BeanDefinition,不存在则进行注册if (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;}

我们可以继续往findCandidateComponents方法看看,大概是怎么扫描的,再次进入

scanCandidateComponents方法。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {//防止重复Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//获取包全路径String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;//读取目录转为Resource[]对象Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();//遍历获取到单个Resource对象(即单个文件)for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}try {//ASM技术读取文件内容转为MetadataReader对象MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//判断该对象是否被@CompnentScan的excludeFilters排除被排除则false,// 被includeFilters或者存在@Compnent则实例化成ScannedGenericBeanDefinition对象放进集合//注意:这里判断是否有@Compnent是因为在实例化扫描器ClassPathBeanDefinitionScanner的时候会执行registerDefaultFilters()方法//     在该方法中默认就注入了Component到includeFilter中(this.includeFilters.add(new AnnotationTypeFilter(Component.class));)//     所以如果有@Compnent注解则在判断是否在includeFilter是返回true。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;}

然后进入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;}

可以看到只获取includeFilters中存在的,我们知道我们自己在配置类的@CompnentScan注解中手动加入了一个Scan_C类到includeFilters,所以Scan_C是可以扫到的,那么加了@Compnent注解的Scan_A又是怎么被扫描的?

其实在实例化一个扫描器时,就会默认加入Compnent.Class到IncludeFilters中,我们可以看看代码:进入扫描器类ClassPathBeanDefinitionScanner

	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");this.registry = registry;//加入默认的类到includeFiltersif (useDefaultFilters) {registerDefaultFilters();}setEnvironment(environment);setResourceLoader(resourceLoader);}

进去registerDefaultFilters方法看看加了哪些类:

protected void registerDefaultFilters() {//加入Compnent类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.}}

至此则解答了如何扫描出有@Compnent注解的类的疑问。

 

 总体下来扫描的大概流程:

spring内置的ConfigurationClassPostProcessor执行子接口实现方法(postProcessBeanDefinitionRegistry)
-----》通过容器registry获取spring内置类+传入的配置类
-----》依次判断是否为合格的配置类
-----》如是则获取配置类的部分属性,实例化一个扫描器(扫描器实例化的时候会将Compnent类加入到includeFilters中,为了后判断哪些类用了@Compnent注解)
-----》解析配置类是否加了@Compnent、@Improt、@CompnentScan等等注解,有则进行解析
-----》解析@CompnentScan注解,解析注解中的各种属性(includeFilters、excludeFilters、beanNameGenerator等)
----》扫描包路径,使用ASM技术读取文件内容为MetadataReader对象,并判断是否能对应includeFilters中的类(并排除excludeFilters的类)
----》有则转化为ScannedGenericBeanDefifinition对象,并判断ScannedGenericBeanDefifinition是否接口是否抽象、是否加了LockUp注解等等
-----》然后加入Set<BeanDefinition>并返回,最后对其进行注册(put到BeanDefinitionMap,但还不会实例化为bean)。

=============================第二种情况做分析==============================

第二种情况非常简单,而且和第一种都是扫描器调用了doscan方法进行扫描解析的。

AnnotationConfigApplicationContext会实例化一个扫描器,然后该扫描器直接调用doScan(basePackages)方法。

=====================第一种情况下的excludeFilter、includeFilter===================

我们先看看怎么使用这两个@CompnentScan注解的属性。

新增两个测试类,一个加@Compnent,一个不加。

@Component
public class Scan_B {@PostConstructpublic void init(){System.out.println("-----------Scan_B");}
}
public class Scan_C {@PostConstructpublic void init(){System.out.println("-----------Scan_C");}
}

修改下配置类:

/*** 排除Scan_B,加入Scan_C* */
@ComponentScan(value = "com.spring.demo.scan",excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = Scan_B.class)},includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = Scan_C.class)})
public class ContextConfig {
}

此时启动ComponentScanTest,查看控制台打印:

 由此我们可以知道includeFilters 属性的作用是增加型过滤器,而excludeFilters是排除型过滤器。 

》》》》》》》》》》》》》》》》》》》问题解答

1、为什么spring扫描要使用ASM获取字节码技术?

ASM是读取字节码文件,并不会提前加载类或执行类的方法,这样不会对类的生命周期等流程产生影响。且效率较高(比反射等方式效率高)

我们以第一种情况为例,看看完成扫描的时候Scan_A有没有被实例化(如被实例化会执行init方法)前一篇文章我们可以知道情况2执行invokeBeanDefinitionRegistryPostProcessors后会完成扫描,此时我们在这行打上断点。

 此时发现Scan_A还没被扫描到,控制台也没打印任何东西,让后我们执行这行代码后:

 

此时Scan_A被扫描到了,但ini方法并没有被实例化,所以spring采用ASM扫描并不会对对象实例化产生影响。(如果此时使用反射等方式,此时就会对对象进行实例化,从而打乱了原先对象实例化的顺序(提前进行实例化了)) 

2、两个扫描器的区别?

两种方式的扫描器类型都是ClassPathBeanDefinitionScanner,且都调用了doscan方法进行扫描解析。不同的是@CompnentScan注解提供了更多属性的配置,例如nameGenerator、includeFilters等。

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

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

相关文章

前端JS调用grpc服务(cpp)

参考文献&#xff1a;https://blog.csdn.net/qq_45634989/article/details/128151766依赖文件&#xff1a;protoc-v3-20.1.exe grpc_cpp_plugin.exe // 生成cpp中间文件protoc-gen-grpc-web.exe // 生成js中间文件grpcwebproxy-v0.13.0-win64 1.4.0.exe // 负责代理的端口映射新…

运营级手机直播平台源码 短视频直播带货APP源码

短视频直播带货APP源码 全开源原生直播APP源码 前端&#xff1a;原生APP 安卓端&#xff1a;Java 苹果端&#xff1a;OC 后台&#xff1a;PHP 数据库&#xff1a;Mysql 技术框架&#xff1a;Thinkphp5.1 系统特色功能包括&#xff1a;礼物系统&#xff1b;提现方式&#…

WAF:ModSecurity on Nginx(15)

预备知识 Nginx概述 Nginx ("engine x") 是一个高性能的HTTP和 反向代理 服务器&#xff0c;也是一个 IMAP/POP3/SMTP服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的&#xff0c;第一个公开版本0.1.0发布于2004年10月4日。其将源代…

指针的进阶【上篇】

文章目录&#x1f4c0;1.字符指针&#x1f4c0;2.指针数组&#x1f4c0;3.数组指针&#x1f4bf;3.1.数组指针的定义&#x1f4bf;3.2. &数组名VS数组名&#x1f4bf;3.3.数组指针的使用&#x1f4c0;1.字符指针 int main() {char ch w;char* pc &ch;// pc就是字符指…

智慧物联网系统源码:一个用于数据的收集、处理、可视化、设备管理、设备预警、报警的平台

项目简介&#xff1a; 一个用于数据的收集、处理、可视化、设备管理、设备预警、报警的平台&#xff0c;通过平台将所有设备连接起来&#xff0c;为上层应用提供设备的管理、数据收集、远程控制等核心物联网功能。 支持支持远程对设备进行实时监控、故障排查、远程控制&#…

PPP点到点协议认证之PAP认证

PPP点到点协议认证之PAP认证 需求 如图配置接口的IP地址将R1配置为认证端&#xff0c;用户名和密码是 huawei/hcie &#xff0c;使用的认证方式是pap确保R1和R2之间可以互相ping通 拓扑图 配置思路 确保接口使用协议是PPP确保接口的IP地址配置正确在R1 的端口上&#xff0c…

Pycharm远程服务器常见问题

2023年02月23日 问题描述&#xff1a;Pycharm远程服务器跑代码时&#xff0c;不小心把Pycharm关掉了&#xff0c;但服务器代码还在运行&#xff1f; 解决办法&#xff1a;kill进程 先用watch -n 0.5 nvidia_smi查看进程&#xff0c;然后kill -9 <进程> 1、nvidia-smi…

ip公司和soc公司是什么?

IP 公司和 SoC 公司都是半导体行业的重要组成部分&#xff0c;但它们的角色和职责略有不同。IP&#xff08;Intellectual Property&#xff09;公司主要提供可重用的知识产权组件&#xff0c;也称为 IP 核或 IP 模块&#xff0c;这些组件可以在设计芯片的过程中被集成到芯片中。…

Unity 对接 ML-Agents 初探

一、ML-Agents 是什么 The Unity Machine Learning Agents Toolkit (ML-Agents) is an open-source project that enables games and simulations to serve as environments for training intelligent agents. We provide implementations (based on PyTorch) of state-of-the…

回归预测 | MATLAB实现BO-CNN-GRU贝叶斯优化卷积门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现BO-CNN-GRU贝叶斯优化卷积门控循环单元多输入单输出回归预测 目录回归预测 | MATLAB实现BO-CNN-GRU贝叶斯优化卷积门控循环单元多输入单输出回归预测效果一览基本介绍模型描述程序设计参考资料效果一览 基本介绍 基于贝叶斯(bayes)优化卷积神经网络-门控循…

Unity(三)--导入3d模型并实现UGUI界面上嵌入3d模型

Unity支持的常用模型格式及建模软件: 格式建模软件网格动画材质骨骼FBX3DMax,C4D,Blender,Maya等√√√√OBJ3DMax,C4D,Blender,Maya等√目录 导入模型并调整好位置创建2D场景(UGUI)使3d模型显示在图片前面方法一:使用Render Texture注意点导入模型并调整好位置 以FBX为例,…

百万数据excel导出功能如何实现?

最近我做过一个MySQL百万级别数据的excel导出功能&#xff0c;已经正常上线使用了。 这个功能挺有意思的&#xff0c;里面需要注意的细节还真不少&#xff0c;现在拿出来跟大家分享一下&#xff0c;希望对你会有所帮助。 原始需求&#xff1a;用户在UI界面上点击全部导出按钮…

如果不使用时钟同步工具,linux如何解决时钟同步问题?仅需要一行命令即可。

这是一篇日记&#xff0c;记录了上帝下凡出手&#xff0c;解救苍生与水火之中的神奇文章&#xff0c;如果你也有过类似的经历&#xff0c;留言关注&#xff0c;咱们交流一下~ 目录 背景&#xff08;如果不想知道可以跳过&#xff09; 一行神奇的命令 一段一段的研究 总结 背…

go atomic 原子操作

在 go 语言 string 类型思考 中有说到 -race 竞态检测&#xff0c;多个 goroutine 并发读写同一个变量是会触发。竞态竞争导致的问题是&#xff1a;结果不可控&#xff0c;你也无法预料最终的结果是什么。 比较棘手的竞态竞争会发生在一些切片类型上&#xff0c;在遍历读取切片…

221 最大正方形

#221 最大正方形 题目描述 在一个由 0 和 1 组成的二维矩阵内&#xff0c;找到只包含 1 的最大正方形&#xff0c;并返回其面积。 示例 1&#xff1a; 输入&#xff1a;matrix [["1","0","1","0","0"],["1",&…

【LeetCode】2357. 使数组中所有元素都等于零

2357. 使数组中所有元素都等于零 题目描述 给你一个非负整数数组 nums 。在一步操作中&#xff0c;你必须&#xff1a; 选出一个正整数 x &#xff0c;x 需要小于或等于 nums 中 最小 的 非零 元素。nums 中的每个正整数都减去 x。 返回使 nums 中所有元素都等于 0 需要的 …

经典设计模式MVC理解

MVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;将业务逻辑、数据、显示分离的方法来组织代码。今天简单回顾一下。 mvc释义理解 M代表模型(Model)&#xff0c;表示业务规则封装。在MVC的三个部件中&#xff0c;模型拥有最多的处理任务。被模型返回的数据…

图表类可视化开发采坑记录之旅3

如图所示的扇形图样式改造&#xff1a; 开发框架&#xff1a; 基于vue2&#xff0c;echarts5.0.0 基于组件&#xff1a; html代码&#xff1a; <div class"showCanvas"><div id"midError"></div> </div> css代码&#xff1a; …

【华为OD机试模拟题】用 C++ 实现 - 去除多余空格(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

运动蓝牙耳机什么牌子好,运动蓝牙耳机品牌推荐

现在市面上运动耳机的品牌越来越多&#xff0c;还不知道选择哪一些运动耳机品牌&#xff0c;可以看看下面的一些耳机分享&#xff0c;运动耳机需要注意耳机的参数配置以及佩戴舒适度&#xff0c;根据自己最根本的使用需求来选择运动耳机。 1、南卡Runner Pro4骨传导蓝牙运动耳…