SpringBoot自动配置原理详解

news2024/11/18 10:46:12

1 前言

之前也写过一篇类似的文章,但是当时理解的并不是很深入,所以一直想重新写,但是一直没有时间,就拖到了现在。这篇文章可能会很长,因为在讲解自动配置的过程中还会衍生出其他一些重要的知识点,我也会进行介绍。

2 热身案例

要谈 SpringBoot 的自动装配,肯定离不开下面这段代码。
在这里插入图片描述

其实我学习 SpringBoot 时,根本没有思考过这段代码,只知道这是 SpringBoot 程序的启动类,要保证他能够覆盖所有要扫描的包,然后就去写代码了。

我们先来看看这段代码:

ConfigurableApplicationContext run = SpringApplication.run(AppRunApplication.class, args);

这段代码的作用是启动 SpringBoot 应用程序,并返回一个 ConfigurableApplicationContext 对象的实例。

SpringApplication.run() 方法返回的是 ConfigurableApplicationContext 类型的对象,表示 Spring 应用程序的上下文。这个上下文保存了Spring 容器中所有 bean 的引用,对于应用程序中的其他组件来说,这个上下文就是一个全局共享的容器。

SpringApplication.run(AppRunApplication.class, args) 语句的意义是基于 AppRunApplication 这个类启动 SpringBoot 应用程序,并将命令行参数传递给应用程序。运行这个语句后,Spring会自动进行应用程序的初始化工作,包括创建ApplicationContext、注册所有的bean定义、启动嵌入式Web容器、加载应用程序的配置文件等。

此外,通过这个方法还可以获取到 ConfigurableApplicationContext 的实例,使用这个对象可以进一步控制应用程序的运行,如手动关闭应用程序、获取应用程序的环境变量、添加自定义的bean等。

只看文字实在时太枯燥了,我们还是看看代码吧。我们在启动类,创建了一个 Test 类,然后生命了一个 Bean 方法,它的作用就是返回一个 Test 类,然后我们进行断点调试。
在这里插入图片描述

太神奇了,我们通过 run 拿到了 Test 的 Bean 实例
在这里插入图片描述

如果我们试图获取一个没有使用 Bean 方法注册的类,就会抛出异常。
在这里插入图片描述

这个 run 其实 可以简单地理解为 Spring 的 IOC 容器,SpringBoot 启动时会自动帮我们配置程序运行需要的使用的 Bean 对象放到 IOC 容器中,我们在其他类需要使用时只需要使用 @Autowire 或者 @Resource 注解进行依赖注入即可。

Spring Boot 自动配置是 Spring Boot 框架的一项核心特性,它可以基于应用程序的依赖关系和配置信息,
自动配置应用程序所需的 Spring BeanSpring Boot 自动配置是通过条件化配置实现的,
这意味着只有在特定条件下才会应用这些配置。这些条件可以是应用程序的依赖关系、配置值、环境变量等等。

Spring Boot 提供了许多 Starter 包,这些 Starter 包为应用程序添加了一组默认的依赖关系和配置信息,
以便应用程序能够正常运行。例如,Spring Boot Starter Web 包为应用程序添加了 
Spring MVC、TomcatWeb 相关的依赖关系和配置信息。当应用程序添加了 Spring Boot Starter Web 包时,
Spring Boot 会自动配置应用程序的 Web 相关配置。

自动装配是指Spring Boot的依赖注入机制,它会根据需要自动为应用程序中的Bean注入依赖关系。
例如,当一个类需要使用JdbcTemplate来访问数据库时,Spring Boot会自动将JdbcTemplate对象注入到这个类中,
而不需要程序员手动编写任何配置代码。

如下图,我们在项目中要使用 Redis ,只需要在 pom 文件中引入相关依赖,然后在 application.yml 文件中配置相关连接参数,之后的累活就全部交给 SpringBoot 自动进行配置,我们使用时只需要使用 @Autowire 或者 @Resource 注解进行依赖注入即可
在这里插入图片描述

通过上面的讲解,我们对 SpringBoot 自动配置有了一个大概的认识——我们需要什么,就在 pom 文件中引入相关依赖,然后在 application.yml 文件中配置相关配置信息,然后 SpringBoot 会帮我们把需要的 Bean 都自动配置到 Spring IOC 容器中,之后我们使用时只需要通过依赖注入机制将需要的 Bean 对象注入即可。

到现在,我们应该知道了 SpringBoot 自动配置是什么,但是这样我们只是知其所以然,所以我们还需要继续了解它的底层实现。

3 源码解读

我们把目光来到今天的主角,@SpringBootApplication 注解。
在这里插入图片描述
我们进入这个注解,好嘛,它的头上怎么顶着这么多注解,不过真正重要的只有三个注解,我们接下来会一一介绍。
在这里插入图片描述

3.1 @SpringBootConfiguration

点进@SpringBootConfiguration 注解,可以发现其核心注解为@Configuration注解:

在这里插入图片描述
@Configuration注解是Spring框架的注解之一,用于标记配置类。

在Spring Boot中,使用@Configuration注解可以将该类作为配置类,从而使该类中的Bean可以被Spring IoC容器管理和使用。

在配置类中,我们可以使用另外两个注解@Bean和@Scope来定义Bean,其中@Bean注解用于定义Bean对象,而@Scope注解用来指定Bean对象的作用域。

除此之外,在配置类中,我们还可以定义一些常量,并使用@Value注解来注入应用程序的属性。

举例来说,一个简单的配置类可以被定义如下:

@Configuration
public class MyConfiguration {
    
    @Value("${myapp.something}")
    private String something;

    @Bean
    public MyBean myBean() {
        return new MyBean(something);
    }
}

在上述代码中,我们使用@Configuration注解来标记MyConfiguration类为配置类。使用@Value注解来注入myapp.something属性到该类中的something变量中。

同时,我们使用@Bean注解来定义一个名为myBean的Bean对象,并在Bean方法中返回一个新创建的MyBean对象,将something参数作为其构造函数的参数进行传递。

@Configuration 注解还可以与 @Import 注解一起使用,@Import 注解用于导入其他的配置类,从而组合多个配置类,形成一个完整的应用程序配置。这样,应用程序可以分而治之,将配置信息分散到不同的配置类中,从而使得配置更加灵活和可维护。

总的来说,@Configuration注解能够将一个类定义为Spring Boot应用程序中的配置类,从而使该类中的Bean对象能够被Spring IoC容器进行自动管理和装配。这让应用开发者能够更加专注于应用逻辑的实现,而不必花费精力在繁琐的配置上。

所以@SpringBootConfiguration 注解本质上就是一个@Configuration注解,用来标注某个类为 JavaConfig 配置类,有了这个注解就可以在 SpringBoot 启动类中使用```@Bean``标签配置类了,如下图所示。

在这里插入图片描述

3.2 @ComponentScan

@ComponentScan 是 Spring Framework 中的一个注解,它用于指定 Spring 容器需要扫描和管理的组件。组件是 Spring 中的一个抽象概念,它包括了 Spring Bean、Controller、Service、Repository 等等。通过 @ComponentScan 注解,可以让 Spring 容器自动扫描和管理这些组件,从而简化应用程序的配置和管理。

@ComponentScan 注解有多个参数,可以用于指定要扫描的组件的位置、排除不需要扫描的组件、指定要排除扫描的组件等等。

默认情况下,Spring Boot会自动扫描主应用程序下的所有组件(@Configuration, @Controller, @Service, @Repository等),但是如果你将组件放在其他包下,那么就需要显式地配置扫描目录。

举个例子,假设我们有以下目录结构:

com
|-- myapp
|   |-- Application.java
|   +-- config
|       +-- MyConfiguration.java
+-- other
    +-- MyComponent.java

可以在主应用程序中添加@ComponentScan注解,来指定Spring应该扫描的包位置:

@SpringBootApplication
@ComponentScan(basePackages = { "com.myapp", "com.other" })
public class Application {
    // ...
}

在上述代码中,我们使用@ComponentScan注解,并指定两个基本包路径com.myapp和com.other以进行扫描。这两个路径下的组件都会被自动扫描到并加载入Spring IoC容器中。

除了basePackages参数以外,@ComponentScan注解还有一些其他可选参数:

除了basePackages参数以外,@ComponentScan注解还有一些其他可选参数:

  1. basePackageClasses:可以使用一个或多个类作为基础包来指定要扫描的根目录。比如:@ComponentScan(basePackageClasses = {MyComponent.class, MyService.class})。

  2. excludeFilters:可以指定过滤器来排除带有某些注解或实现某些接口的组件。

  3. includeFilters:可以指定过滤器来仅包含带有某些注解或实现某些接口的组件,可能的值有@Component, @Repository, @Service, @Controller等。

使用这些参数,可以更加精细的控制扫描范围。

3.3 @EnableAutoConfiguration 注解

这是今天的主角中的主角,自动配置实现的核心注解。

点进这个注解可以发现,如下图所示。

在这里插入图片描述

我们重点来看 @Import(AutoConfigurationImportSelector.class)这个注解。

@Import 注解是 它用于将一个或多个类导入到 Spring 容器中,以便于在应用程序中使用。通过 @Import 注解,我们可以将一些非 Spring 管理的类实例化并注册到 Spring 容器中,或者将一些 Spring 管理的配置类导入到当前配置类中,以便于在应用程序中进行统一的配置和管理。

@Import是Spring Framework 中的一个注解,用于在配置类中导入其他配置类或者普通的Java类。

通过@Impor注解,它用于将一个或多个类导入到 Spring 容器中,以便于在应用程序中使用。通过 @Import 注解,我们可以将一些非 Spring 管理的类实例化并注册到 Spring 容器中,或者将一些 Spring 管理的配置类导入到当前配置类中,以便于在应用程序中进行统一的配置和管理。

说白了在这里@Import注解的作用就是将 AutoConfigurationImportSelector 这个类导入当前类,这个类就是实现自动配置的核心。

我们继续进入到 AutoConfigurationImportSelector 类:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
最后,我们发现, AutoConfigurationImportSelector 实际上是实现了 ImportSelector 接口,这个接口只有两个方法,其中我们需要重点关注 selectImports() 方法。

ImportSelector 接口是 Spring Framework 中的一个接口,它可以用于在 Spring 容器启动时动态地导入一些类到 Spring 容器中。通过实现 ImportSelector 接口,并重写其中的 selectImports 方法,我们可以自定义逻辑来确定需要导入的类,从而实现更加灵活的配置和管理。

selectImports 方法是 ImportSelector 接口中的一个方法,用于返回需要导入的类的全限定类名数组。在 Spring 容器启动时,Spring 会扫描所有实现了 ImportSelector 接口的类,并调用其中的 selectImports 方法来确定需要导入的类。在 selectImports 方法中,我们可以自定义逻辑来确定需要导入的类,例如根据某些条件来动态地确定需要导入的类。

好嘛,搞了半天,关键点在这里,通过 selectImports 方法,我们就可以得到需要自动配置的类的全限定类名数组,那我们来看一下这个方法。

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

在这里插入图片描述
既然我们需要自动配置的类的全限定类名数组,那么这个方法必然通过某个方法获取到这个数组,我们看一下这个方法getAutoConfigurationEntry(annotationMetadata),单看这个名字它的嫌疑就非常大。

getAutoConfigurationEntry 方法可以用于获取自动配置类的元数据,以便于分析和调试自动配置机制。
它接受一个 AnnotationMetadata 对象作为参数,该对象表示使用了 @EnableAutoConfiguration 注解的配置类的元数据。
通过调用该方法,我们可以获取到所有已经配置的自动配置类的全限定类名,以及这些自动配置类的条件注解和优先级信息等。

我们继续进入到 getAutoConfigurationEntry() 方法:

在这里插入图片描述
说实话这个方法我现在看还是感觉眼花缭乱,哈哈,不过不影响我们分析,我们先看方法返回值,返回值是一个 AutoConfigurationEntry 对象,再看看 return 语句:

return new AutoConfigurationEntry(configurations, exclusions);

果然是通过构造函数创建一个 AutoConfigurationEntry 对象并返回,我们再看看它的构造参数:

configurations, exclusions

再结合我们之前的分析,这个方法的作用是返回自动配置类的元数据,不难推断出 configurations 就是我们需要的自动配置类的元数据,那exclusions 参数呢,这个从名字上来看,它应该是需要排除的类的元数据。

类似上面 @ComponentScan注解 中的 excludeFilter 参数,可以指定过滤器来排除带有某些注解或实现某些接口的组件。

那我们现在要做的就是分析 configurations 是怎么来的:

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

我们继续进入到 getCandidateConfigurations() 方法:在这里插入图片描述

这个方法的组成还是非常简单的,它只调用了 SpringFactoriesLoader 的静态方法 loadFactoryNames(),还有就是一个断言。

Java 断言是一种调试工具,它用于在程序运行时检查一个条件是否为 true。
可以使用 assert 关键字来编写断言语句,如果条件为 false,则会抛出 AssertionError 异常。
getCandidateConfigurations 方法是 Spring Boot 中的一个方法,它用于获取所有候选的自动配置类。
在 Spring Boot 应用程序中,自动配置是一种约定俗成的机制,它可以根据应用程序的依赖和配置来自动配置 Spring 应用程序上下文。
Spring Boot 会在 classpath 下扫描 META-INF/spring.factories 文件,该文件中定义了一些自动配置类,
这些自动配置类会在应用程序启动时被自动加载和配置。

我们先来了解一下 SpringFactoriesLoader :

SpringFactoriesLoaderSpring 框架中的一个工具类,用于加载 META-INF/spring.factories 文件中定义的类。
在 Spring Boot 应用程序中,META-INF/spring.factories 文件中定义了一些自动配置类,
这些自动配置类会在应用程序启动时被自动加载和配置。
SpringFactoriesLoader 可以用于加载这些自动配置类,从而实现自动配置机制。

接下来我们继续进入 loadFactoryNames() 方法:

在这里插入图片描述

SpringFactoriesLoader 类中的 loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 方法用于加载指定类型的工厂实现类。该方法接受两个参数:

  1. factoryType: 要加载的工厂类型,必须是一个接口或者抽象类。该参数是必须的,因为 SpringFactoriesLoader 会在 META-INF/spring.factories 文件中查找该工厂类型对应的实现类。

  2. classLoader: 类加载器,用于加载 META-INF/spring.factories 文件中定义的类。如果该参数为 null,则使用当前线程的上下文类加载器。
    该方法会返回一个 List 类型的对象,其中包含了所有的候选工厂实现类的全限定类名。在加载工厂实现类时,SpringFactoriesLoader 会使用反射机制创建实例,并调用工厂方法生成对应的工厂对象。

了解了 loadFactoryNames()方法后,我们先把目光回到 getCandidateConfigurations() 方法,它在调用 loadFactoryNames()方法时传递了两个参数:

SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())

在这里插入图片描述

这两个参数是两个方法:

在这里插入图片描述

在这里插入图片描述

结合传递的参数进行分析,这里 loadFactoryNames() 方法的作用是:

加载所有使用了 @EnableAutoConfiguration 注解的自动配置类的全限定类名,并返回一个 List 类型的对象,其中包含了所有的候选自动配置类的全限定类名。

我们把目光回到 loadFactoryNames() 方法,不难看出,实际的加载功能使用最后方法返回处调用的 :

loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList())

完成的。
在这里插入图片描述

我们先来看一下 getOrDefault()方法:

getOrDefault(Object key, V defaultValue)Map 接口中的一个方法,
用于获取指定 key 对应的 value。如果该 key 存在,则返回对应的 value;否则,返回 defaultValue。

也就是说 loadSpringFactories(classLoaderToUse) 方法,返回的是一个 Map 类型的数据,而 getOrDefault()方法的 key 为:

String factoryTypeName = factoryType.getName();

在这里插入图片描述
显然,这个 factoryTypeName 是 EnableAutoConfiguration。

所以 loadSpringFactories(classLoaderToUse) 方法会返回的是一个 Map 类型的数据,并且结合 getOrDefault()传递的参数 key 可知,这个 Map 数据的 key 应该是 EnableAutoConfiguration,而 value 是一个 List<String>集合,所以这个 Map 类型的数据为 Map<String, List<String>>

原来 SpringBoot 通过 loadSpringFactories 方法获得了 Map<String, List<String>>数据结构的数据然后再通过 getOrDefault 方法将其转化成 List<String>数据结构。

分析到这里,我们再来看一下 loadSpringFactories(ClassLoader classLoader) 方法:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

这个方法代码很多,我们一点一点的分析。

在这里插入图片描述
方法开始,先尝试从缓存中获取 Map<String, List<String>>类型的返回值 result,如果缓存命中就直接返回,如果缓存中没有,就继续往下执行。

在这里插入图片描述
到了这里,我们已经来到了这个方法的核心,简单分析一下这段代码的作用:

这段代码的作用是加载指定位置的资源并解析其中的属性,获取工厂类型和对应的实现类名,然后将它们存储在一个 Map 中。
具体来说,这段代码的实现过程如下:


1.获取指定位置 FACTORIES_RESOURCE_LOCATION 的所有资源 URL。

2.遍历所有获取到的 URL,对每一个 URL 进行如下操作:

	a.将 URL 封装成一个 UrlResource 对象,用于访问该 URL 资源。

	b.使用 PropertiesLoaderUtils 工具类加载 UrlResource 对象中的属性,获取工厂类型和对应的实现类名。

	c.遍历工厂类型对应的实现类名数组,将每个实现类名添加到一个 Map 对象中,以工厂类型为键,以实现类名列表为值。
	  这里使用了 computeIfAbsent 方法,如果该工厂类型在 Map 对象中不存在,则会创建一个新的键值对,
	  否则会将实现类名添加到该工厂类型对应的实现类名列表中。


3.返回包含工厂类型和对应的实现类名的 Map 对象。
  
该段代码通常用于 Spring Boot 应用程序中的自动配置,主要目的是在启动时自动加载并配置一些自动配置类,
以减少手动配置的工作量。在 Spring Boot 应用程序中,这段代码通常会在 AutoConfigurationImportSelector 类中被调用,
用于加载并解析 META-INF/spring.factories 文件中定义的自动配置类。

在这个方法中 FACTORIES_RESOURCE_LOCATION :

在这里插入图片描述

搞了这么久,终于破案了,这个 loadSpringFactories 就是根据配置信息的 url 加载配置文件的内容,接下来我们进行断点调试,验证我们的猜想。

在这里插入图片描述

看,我们从 result 中找到了 key 为 EnableAutoConfiguration ,value 为 List<String>
的 Map 类型的数据。
在这里插入图片描述
再看看 spring.factories 文件中的内容,进一步印证了我们的推测。
在这里插入图片描述

最后,我们再次将目光会到 loadSpringFactories() 方法,这个方法先获取 loadSpringFactories() 方法返回的 Map<String, List<String>>数据结构的数据,这里封装了从 spring.factories 文件中获取的类的全限定名信息。

然后再通过 getOrDefault(factoryTypeName, Collections.emptyList()) 方法获取 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
List<String>类型的数据。这个 list 集合里面就是 SpringBoot 自动配置的类的全限定名信息。

在这里插入图片描述

4 SpringFactories 机制

SpringFactories 机制是 Spring 框架提供的一种扩展机制,用于在应用程序启动时自动加载并配置一些扩展类。具体来说,该机制通过在类路径下的 META-INF/spring.factories 文件中定义一些扩展类的全限定类名,然后在应用程序启动时自动扫描该文件,并加载其中的扩展类。
SpringFactories 机制的实现过程如下:

  1. 在类路径下的 META-INF/spring.factories 文件中定义一些扩展类的全限定类名。

  2. 在应用程序启动时,使用 ClassLoader 加载 META-INF/spring.factories 文件,并解析其中定义的扩展类名。

  3. 根据扩展类名使用反射机制动态创建扩展类的实例,并将其注册到相应的容器中。例如,在 Spring Boot 应用程序中,自动配置类会被注册到 Spring 容器中,并在应用程序启动时自动配置。

SpringFactories 机制的优点是可以极大地降低应用程序的配置难度,提高开发效率。在 Spring Boot 应用程序中,该机制被广泛应用于自动配置、自定义 Starter、插件等领域。

总结

没错,我们上面分析的 loadSpringFactories() 方法就是基于 SpringFactories 机制实现的。

那如果面试官问,说说你对 SpringBoot 自动配置的理解,我们该怎么回答?

SpringBoot 自动配置就是基于SpringFactories 机制获取对应依赖META-INF目录下的 spring.factories 文件中的需要自动配置的类的全限定名信息,然后根据这些信息将我们需要的使用的 Bean 对象放到 IOC 容器中,当我们需要使用时,通过依赖注入机制直接注入使用即可。当然如果再追问具体的实现细节,可以根据我们的分析流程讲讲具体的代码实现。

以上文字都是我自己根据自己的理解写的,所以难免有错误的地方,有任何问题,或者文章有任何错误,请在评论区@我。

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

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

相关文章

2023文本定位模型选型调研

背景 时间点&#xff1a;2023年03月 场景&#xff1a;做一个通用型的多种证件解析服务 需求&#xff1a;调研一种又新又快的定位模型。要求&#xff1a;1&#xff09;支持倾斜的文字&#xff0c;可以是4点定位或分割法后获取box&#xff0c;但不能是2点的定位&#xff1b;2&…

2023.4.23第五十次周报

目录 前言 文献阅读&#xff1a;基于ARIMA-WOA-LSTM模型的空气污染物预测 背景 ARIMA-WOA-LSTM模型 思路 主要贡献 积分移动平均自回归 &#xff08;ARIMA&#xff09; 鲸鱼优化算法 搜索超参数 CEEMDAN 结论 LSTM-Kriging 主要目标 理论猜想 问1&#xff1a…

如何申请百度地图开发者AK和基本使用,并解决Uncaught ReferenceError: BMapGL is not defined的错误

文章目录 1. 文章引言2. 申请AK3. 使用AK4. 解决BMapGL is not defined的错误5. 文末总结 1. 文章引言 今天在学习amis框架中的地理位置(LocationPicker)的组件&#xff0c;如下图所示&#xff1a; 关于amis的更多了解&#xff0c;可以参考博文&#xff1a;百度低代码amis框架的…

适合学生的平价蓝牙耳机有哪些?学生平价蓝牙耳机推荐

随着蓝牙耳机的使用越来越频繁&#xff0c;近几年也出现了很多优质的蓝牙耳机&#xff0c;不仅有着超高的性价比&#xff0c;而且使用体验也有了很大的突破。接下来&#xff0c;我来给大家推荐几款适合学生使用的平价蓝牙耳机&#xff0c;可以当个参考。 一、南卡小音舱Lite2蓝…

Java基础--->基础部分(1)

文章目录 Java语言特点JVM、JRE和JDK的关系什么是字节码&#xff1f;采用字节码的好处是什么&#xff1f;面向对象面向对象的三大特征&#xff1a;封装&#xff0c;继承&#xff0c;多态关键字抽象类和接口特点和区别和equals的区别String、StringBuffer、StringBuilder异常 Ja…

中医脉诊仪:结合传统与现代技术的诊断工具

一、引言 随着科技的不断发展&#xff0c;医学领域也取得了举世瞩目的进步。中医作为一种古老的医学体系&#xff0c;始终保持着其独特的魅力。脉诊作为中医诊断的重要方法之一&#xff0c;历经千年的发展和传承&#xff0c;如今在现代科技的助力下&#xff0c;诞生了中医脉诊…

PostgreSQL标准复制方案

集群拓扑 假设我们使用4单元的标准配置&#xff1a;主库&#xff0c;同步从库&#xff0c;延迟备库&#xff0c;远程备库&#xff0c;分别用字母M,S,O,R标识。 M&#xff1a;Master, Main, Primary, Leader, 主库&#xff0c;权威数据源。S: Slave, Secondary, Standby, Sync…

CTFSHOW web入门——web37

过滤了flag&#xff0c;即c中不能有flag字段。 include包含变量c&#xff0c;因此可以利用文件包含漏洞&#xff0c;让变量c变成php代码&#xff0c;然后通过include函数执行。可以使用data协议获取flag.php文件中的内容 data://协议 通常可以用来执行PHP代码 data://text/pl…

FFmpeg PCM 编码 AAC

1. 概要说明与流程图 1.1 概要: 1) FFmpeg 已经废弃了 AV_SAMPLE_FMT_S16 格式 PCM 编码 AAC,也就是说如果使用 FFmpeg 自带的 AAC 编码器,必须做音频的重采样(重采样为:AV_SAMPLE_FMT_FLTP),否则AAC编码是失败的。 2) 传输 PCM 数据时,采取截取缓存机制,解决接收数据包…

8 年开发告诉你,API 是什么?如何看懂 API 文档

API 指的是应用程序编程接口&#xff0c;它是应用程序之间通信的一种方式&#xff0c;允许应用程序之间相互交互和传输数据。 API 文档是编写 API 的开发人员所提供的用户使用说明&#xff0c;通常包括 API 的用途、参数、请求示例、返回格式等信息&#xff0c;以便开发人员使用…

使用ltp进行三元组提取的实战代码

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Bugzilla详解

Bugzilla详解 引言 Bugzilla是一种开源的缺陷管理系统&#xff0c;用于跟踪软件开发过程中的缺陷、错误和问题。它提供了一个集中化的平台&#xff0c;允许开发团队、测试团队和用户报告和跟踪软件中的缺陷&#xff0c;以便及时发现、修复和验证这些问题。 Bugzilla的作用和用…

前端学习--Ajax(2) form表单

一、form表单 组成&#xff1a;表单标签、表单域&#xff08;采集信息&#xff09;、表单按钮&#xff08;提交&#xff09; 1.1 <form>的属性 action -- 向何处发送表单数据 如果不写默认是当前页面url target -- 在何处打开action的url _blank 在新窗口打开 _self …

设计模式之访问者模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、访问者模式是什么&#xff1f; 访问者模式是一种行为型的软件设计模式&#xff0c;表示一个作用于某对象结构中的各元素的操作…

函函函函函函函函函函函数——one

&#x1f929;本文作者&#xff1a;大家好&#xff0c;我是paperjie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 &#x1f970;内容专栏&#xff1a;这里是《C知识系统分享》专栏&#xff0c;笔者用重金(时间和精力)打造&#xff0c;基础知识一网打尽&#xff0c;…

java: Compilation failed: internal java compiler error

问题描述&#xff1a; 今天学习一个新的框架 Jbolt-v3.0&#xff0c;然后将它通过 IDEA 导入&#xff0c;运行报错&#xff0c;如下显示&#xff1a; 接着我尝试了百度上的解决方案&#xff0c;依然没有解决&#xff0c;遂即记录一下。 原因分析&#xff1a; 出现这种报错的原…

聚观早报|飞猪:五一出游需求爆发;​特斯拉一季度盈利同比跌20%

今日要闻&#xff1a;飞猪&#xff1a;五一出游需求爆发&#xff1b;特斯拉一季度盈利同比暴跌20%&#xff1b;郑渊洁永远不再发表作品&#xff1b;KargoBot推出无人化自动驾驶卡车&#xff1b;中国6G通信技术研发取得重要突破 飞猪&#xff1a;五一出游需求爆发 4 月 19 日&a…

requests实现系统模拟登录

文章目录 requests模拟登录QWebEngine登录存储数据PySide2使用QWebEngineView报错extension_system_qt.cpp(122) failed to parse extension manifest requests模拟登录 使用requests发送post请求&#xff1b;获取响应头中的Set-Cookie的值&#xff1b;将该Cookie值存入浏览器…

图像ROI与mask掩码与图像几何变换

图像ROI与mask掩码与图像几何变换 感兴趣区域ROI: ⚫ ROI—(region of interest)—感兴趣区域 ⚫ 形状可有矩形&#xff0c;圆形&#xff0c;椭圆形等 ⚫ 能够确定分析重点&#xff0c;减少处理时间&#xff0c;提高精度 使用Rect起点终点范围 示例代码&#xff1a; import…

条码控件Aspose.BarCode入门教程(8):C#从图像中读取条形码

Aspose.BarCode for .NET 是一个功能强大的API&#xff0c;可以从任意角度生成和识别多种图像类型的一维和二维条形码。开发人员可以轻松添加条形码生成和识别功能&#xff0c;以及在.NET应用程序中将生成的条形码导出为高质量的图像格式。 Aspose API支持流行文件格式处理&am…