Spring Boot Starter 剖析与实践 | 京东云技术团队

引言

对于 Java 开发人员来说,Spring 框架几乎是必不可少的。它是一个广泛用于开发企业应用程序的开源轻量级框架。近几年,Spring Boot 在传统 Spring 框架的基础上应运而生,不仅提供了 Spring 的全部功能,还使开发人员更加便捷地使用。在使用 Spring Boot 时,我们经常会接触到各种 Spring Boot Starter,例如 spring-boot-starter-web。只需将该依赖加入项目中,我们就可以开始开发应用;在引入 spring-boot-starter-data-jdbc 后,只需在配置文件中填写数据库连接信息,即可连接数据库。此外,您还可以随意切换数据源组件依赖,而无需修改业务代码。Spring Boot Starter 是如何适配的呢?我们能否自己实现一个 Spring Boot Starter 呢?本文将剖析 Spring Boot Starter 的原理,并自定义实现一个 Spring Boot Starter 组件。

一、Spring Boot Starter 是什么?

Spring Boot Starter 是 Spring Boot 中比较重要的概念, 是一种依赖描述符,它可以帮助您简化配置。当需要构建一个 Web 应用程序时,不必再遍历所有的依赖包,一个一个地添加到项目的依赖管理中,而是只需要一个配置spring-boot-starter-web,如以下示例:

从上面示例来看,我们使用了相当少的代码创建了一个 REST 应用程序。Spring 官方提供了许多 Starter,同时第三方也可以自定义 Starter,官方为了加以区分,Starter 从名称上进行了如下规范:spring-boot-starter-xxx;第三方提供的 starter 名称为:xxx-spring-boot-starter

二、Spring Boot Starter 剖析

前面介绍了 Starter 的概念以及如何快速创建 REST 应用程序。只需添加一个依赖和几行代码,就能完成 REST 接口开发。那么,在没有 Spring Boot 和 Starter 的情况下,我们该如何进行开发呢?Spring Boot Starter 的工作原理又是什么?接下来,我们将通过开发 Web 服务和 Dubbo 服务作为例子,分别剖析纯 Spring 和 Spring Boot Starter。

Spring

环境依赖

  • JDK 1.8

  • Maven 3

  • Tomcat 8(需要依靠 Web 容器服务器才能启动)

  • spring-webmvc 4.3.30.RELEASE

  • dubbo 2.7.23

开发流程

  1. 首先介绍一下,这是一个标准的 Maven 目录结构与demo-service依赖内容

    <dependencies><!-- SpringMVC --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.30.RELEASE</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version></dependency><!-- 此处需要导入databind包即可, jackson-annotations、jackson-core都不需要显示自己的导入了--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.8</version></dependency><!-- Dubbo --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo</artifactId><version>2.7.23</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-x-discovery</artifactId><version>5.1.0</version></dependency><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.8.0</version></dependency><!-- Demo API --><dependency><groupId>com.demo</groupId><artifactId>demo-api</artifactId><version>1.0-SNAPSHOT</version></dependency>
    </dependencies>
  2. 由于在 Spring XML 下还需要依靠 Java Web 和 Web 容器运行,还需要web/WEB-INF/web.xmlWeb 配置文件,内容配置了 SpringMVC 入口

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- Spring监听器 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:dubbo.xml</param-value></context-param><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:mvc.xml</param-value></init-param></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
    </web-app>
  3. SpringMVC 配置文件mvc.xml与 Dubbo 配置文件dubbo.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"><context:component-scan base-package="com.demo.controller"/><!-- 开启 MVC 注解驱动 --><mvc:annotation-driven/><!-- 访问静态资源 --><mvc:default-servlet-handler/>
    </beans>
    <?xml version="1.0" encoding="utf-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"><!-- Dubbo --><dubbo:application name="demo-service"/><dubbo:registry address="zookeeper://127.0.0.1:2181"/><dubbo:protocol name="dubbo" port="20880"/><bean id="demoServiceImpl" class="com.demo.provider.DemoServiceImpl"/><dubbo:service interface="com.demo.api.DemoService" ref="demoServiceImpl"/>
    </beans>
  4. 编写 Controller 接口与 Dubbo RPC 接口

    package com.demo.controller;import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;@RestController
    public class HelloController {@GetMapping(value = "/say/hello")public HelloEntity sayHello() {return new HelloEntity("Hello World");}}
    package com.demo.provider;import com.demo.api.DemoService;
    import com.demo.dto.HelloEntity;public class DemoServiceImpl implements DemoService {@Overridepublic HelloEntity sayHello() {return new HelloEntity("Hello World");}
    }
  5. 以上还无法单独运行,需要将以上打包成war包放入到 Tomcat 才可运行。

剖析

从上面的开发流程中,我们可以看到入口都在 web.xml 中。其中有一个监听器和一个 Servlet,以及初始化参数 dubbo.xml 和 mvc.xml。在 Spring Boot 出现之前,Spring 通常使用 XML 配置方式描述 Bean,或者在 XML 中配置注解驱动和上下文扫描方式解析 Bean。因此,我们可以看出这里有两个 XML 文件。经过分析源代码,我们整理出了以下 XML 标签解析到 Bean 解析的流程。如下:

  1. 由 Tomcat 启动加载web.xml并通过监听器和 Servlet 让 Spring 加载 XML 并解析。

  2. 直到BeanDefinitionParserDelegate#parseCustomElement开始解析自定义标签,找到mvc:xxxdubbo:xxx标签找到了 XML 命名空间。

  3. DefaultNamespaceHandlerResolver处理逻辑:以懒加载方式加载所有 jar 中META-INF/spring.handlers(路径必须得是这个)并缓存到handlerMappings,通过命名空间 URI 找到与之对应的处理类,SpringMVC 与 Dubbo 命名空间处理类分别为MvcNamespaceHandlerDubboNamespaceHandler

  4. MvcNamespaceHandlerDubboNamespaceHandler都分别实现了NamespaceHandler#init方法,内容如下:


    init方法将 SpringMVC 和 Dubbo 标签对应的 BeanDefinitionParser 注册到了 NamespaceHandlerSupport#parsers 中。在上一步中,DefaultNamespaceHandlerResolver 根据标签获取到了该标签的 BeanDefinitionParser,从而将对应的 Bean 注册到了 Spring IOC 容器中。注册逻辑不是本文的重点,这里就不再赘述。至此,SpringMVC 和 Dubbo 的加载流程已经完成。

从以上加载流程中,我们可以看出,在没有 Spring Boot 之前,Spring 主要依靠 XML 配置来启动。它会加载 XML 中的自定义标签,找到对应的命名空间,然后扫描 classpath 下的 META-INF/spring.handlers,找到命名空间处理类来解析当前标签。

Spring Boot

环境依赖

  • JDK 1.8

  • Maven 3

  • spring-boot 2.6.9

  • dubbo 2.7.23

开发流程

  1. 目录结构与 Mavendemo-spring-boot依赖内容

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- dubbo --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>2.7.23</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-x-discovery</artifactId><version>5.1.0</version></dependency><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.8.0</version></dependency><dependency><groupId>com.demo</groupId><artifactId>demo-api</artifactId><version>1.0-SNAPSHOT</version></dependency>
    </dependencies>
  2. 应用程序入口DemoSpringBootApplication

    @SpringBootApplication
    public class DemoSpringBootApplication {public static void main(String[] args) {SpringApplication.run(DemoSpringBootApplication.class, args);}}
  3. application.yml文件内容只有 Dubbo 的配置

    dubbo:application:name: demo-providerprotocol:port: 20880name: dubboregistry:address: zookeeper://127.0.0.1:2181
  4. 编写 Controller 接口与 Dubbo RPC 接口

    package com.demo.controller;import com.demo.dto.HelloEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;@RestController
    public class HelloController {@GetMapping(value = "/say/hello")public HelloEntity sayHello() {return new HelloEntity("Hello World");}}
    package com.demo.provider;import com.demo.api.DemoService;
    import com.demo.dto.HelloEntity;@DubboService 
    public class DemoServiceImpl implements DemoService {@Overridepublic HelloEntity sayHello() {return new HelloEntity("Hello World");}
    }
  5. 由于spring-boot-starter-web已经内嵌 tomcat ,只需要直接运行DemoSpringBootApplication#main方法即可运行应用

剖析

从开发流程上没办法第一时间找到解析入口,唯一入口就是在DemoSpringBootApplication,经过源代码分析得出以下流程:

  1. 应用DemoSpringBootApplication类上有@SpringBootApplication注解,而该注解由以下三个注解组成:

    • @SpringBootConfiguration,标注当前类为一个配置类,与[@Configuration](https://my.oschina.net/pointdance)注解功能一致 ,被[@Configuration](https://my.oschina.net/pointdance)注解的类对应 Spring 的 XML 版的容器。

    • @EnableAutoConfiguration,开启启动自动装配的关键,由@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)组成

    • @ComponentScan,按照当前类路径扫描含有@Service@Controller等等注解的类,等同于 Spring XML 中的context:component-scan

  2. Spring Boot 自动装配由@EnableAutoConfiguration导入的AutoConfigurationImportSelector类,会调用SpringFactoriesLoader#loadFactoryNames从 ClassPath 下扫描所有 jar 包的META-INF/spring.factories内容,由于传入的EnableAutoConfiguration.class,只会返回org.springframework.boot.autoconfigure.EnableAutoConfigurationkey 的值,得到一个全限定类名字符串数组configurations

  3. configurations经过去重与声明式排除后,会进行以下进行过滤自动装配:

    configurations = getConfigurationClassFilter().filter(configurations)

    分成两部分:获取过滤器和执行过滤。

    • getConfigurationClassFilter(),也是通过SpringFactoriesLoader#loadFactoryNamesMETA-INF/spring.factories找到 Key 为org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的值,目前只有:OnBeanConditionOnClassConditionOnClassCondition三个过滤器。

    • 执行过滤,会根据配置类上含有@ConditionOnBean@ConditionalOnClass@ConditionalOnWebApplication等等条件注解来过滤掉部分配置类。比如WebMvcAutoConfiguration指定需要在@ConditionOnWebApplication下才生效。

  4. 在引入各类 Configuration 的配置类后,配置类结合@Bean来完成 Spring Bean 解析和注入,同时 Spring Boot 还提供了许多@ConditionalXXX给开发者完成灵活注入。

以上就是 Spring Boot 的自动装配过程。Spring Boot 利用被 @Configuration 注解的配置类来代替 Spring XML 完成 Bean 的注入。然后,SpringFactoriesLoader 会最终加载 META-INF/spring.factories 中的自动配置类,实现自动装配过程。依靠“约定大于配置”的思想,如果开发的 Starter 想要生效,就需要按照 Spring Boot 的约定。

小结

通过对比 Spring 与 Spring Boot 的开发流程,我们可以发现 Spring Boot 在完成 Web 与 Dubbo 独立应用开发时,使用了相对较少的代码和配置。这得益于 Spring Boot Starter 的自动装配能力,它是 Spring Boot 的主要功能。通过消除定义一些属于自动配置类部分的需求,自动配置可以帮助简化开发流程并加速开发速度。

SPI

我们从上面剖析发现,两者都使用了一项机制去加载引入的 jar 包中的配置文件从而加载对应类,那就是SPI(Service Provider Interface)

SPI (Service Provider Interface), 是 Java 内置的一种服务提供发现机制,提高框架的扩展性。

Java SPI

Java 内置的 SPI 通过java.util.ServiceLoader类解析 Classpath 和 jar 包的META-INF/services目录下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用。

但是 Java SPI 会有一定不足:

  • 不能做到按需加载,需要遍历所有的实现并实例化,然后在循环中找到所需要的实现。

  • 多个并发多线程使用ServiceLoader类的实例不安全

  • 加载不到实现类时抛出并不是真正原因的异常,错误难定位。

Spring SPI

Spring SPI 沿用了 Java SPI ,但是在实现上和 Java SPI 存在差异,但是核心机制相同,在不修改 Spring 源码前提下,可以做到对 Spring 框架的扩展开发。

  • 在 Spring XML 中,由DefaultNamespaceHandlerResolver负责解析spring.handlers生成 namespaceUri 和 NamespaceHandler 名称的映射,等有需要时在进行实例化。

  • 在 Spring Boot 中,由SpringFactoriesLoader负责解析spring.factories文件,并将指定接口的所有实现类/全限定类名返回。

Spring Boot 2.7.0

在本文中 Spring Boot 自动装配使用了 SPI 来加载到EnableAutoConfiguration所指定的自动装配的类名,但在 Spring Boot2.7.0之后自动装配 SPI 机制有所改动,META-INF/spring.factories将废弃,同时在 Spring Boot 3 以上会将相关代码移除,改动如下:

  • 新的注解:@AutoConfiguration代替@Configuration

  • 读取自动装配的类文件位置改为:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,并且实现类全限定类名按照一行一个

  • org.springframework.boot.context.annotation.ImportCandidates#load负责解析META-INF/spring/%s.imports,其中%s是接口名的占位符

三、Spring Boot Stater 实践

在使用spring-boot-starter-jdbc或者spring-boot-starter-jpa等数据库操作时,通常会引入一个数据库数据源连接池,比如:HikariCPDBCP等,同时可随意切换依赖而不需要去更改任何业务代码,开发人员也无需关注底层实现,在此我们自定义一个 Starter 同时也实现这种兼容。因为我们以开发一个分布式锁的 Starter 并拥有多个实现:Zookeeper、Redis。 在此使用 Spring Boot 2.6.9 版本。

开发

项目结构与 Maven 依赖

└── src├── main│   ├── java│   │   └── com.demo.distributed.lock│   │      ├── api│   │      │   ├── DistributedLock.java│   │      │   └── LockInfo.java│   │      ├── autoconfigure│   │      │   ├── DistributedLockAutoConfiguration.java│   │      │   └── DistributedLockProperties.java│   │      ├── redis│   │      │   └── RedisDistributedLockImpl.java│   │      └── zookeeper│   │          └── ZookeeperDistributedLockImpl.java│   └── resources│       └── META-INF│           └── spring.factories
<dependencies><!-- Spring Boot 自动装配注解 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><!-- 生成 META-INF/spring-configuration-metadata.json --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!-- Zookeeper --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>5.1.0</version><scope>provided</scope></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.1.0</version><scope>provided</scope></dependency><!-- Redis --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.23.1</version><scope>provided</scope></dependency>
</dependencies>

在依赖里可以看到 Zookeeper 和 Redis 依赖关系被设置为provided,作用为编译与测试阶段使用,不会随着项目一起发布。即打包时不会带上该依赖。该设置在 Spring Boot Starter 作用较大。

分布式锁接口与实现

接口

public interface DistributedLock {/*** 加锁*/LockInfo tryLock(String key, long expire, long waitTime);/*** 释放锁*/boolean release(LockInfo lock);}

Redis 实现

public class RedisDistributedLockImpl implements DistributedLock {private final RedissonClient client;public RedisDistributedLockImpl(RedissonClient client) {this.client = client;}@Overridepublic LockInfo tryLock(String key, long expire, long waitTime) {//do somethingreturn null;}@Overridepublic boolean release(LockInfo lock) {//do somethingreturn true;}
}

Zookeeper 实现

public class ZookeeperDistributedLockImpl implements DistributedLock {private final CuratorFramework client;public ZookeeperDistributedLockImpl(CuratorFramework client) {this.client = client;}@Overridepublic LockInfo tryLock(String key, long expire, long waitTime) {return null;}@Overridepublic boolean release(LockInfo lock) {return false;}
} 

DistributedLockAutoConfiguration 配置类

@EnableConfigurationProperties(DistributedLockProperties.class)
@Import({DistributedLockAutoConfiguration.Zookeeper.class, DistributedLockAutoConfiguration.Redis.class})
public class DistributedLockAutoConfiguration {@Configuration@ConditionalOnClass(CuratorFramework.class)@ConditionalOnMissingBean(DistributedLock.class)@ConditionalOnProperty(name = "distributed.lock.type", havingValue = "zookeeper",matchIfMissing = true)static class Zookeeper {@BeanCuratorFramework curatorFramework(DistributedLockProperties properties) {//build CuratorFramework clientreturn null;}@BeanZookeeperDistributedLockImpl zookeeperDistributedLock(CuratorFramework client) {return new ZookeeperDistributedLockImpl(client);}}@Configuration@ConditionalOnClass(RedissonClient.class)@ConditionalOnMissingBean(DistributedLock.class)@ConditionalOnProperty(name = "distributed.lock.type", havingValue = "redis",matchIfMissing = true)static class Redis {@BeanRedissonClient redissonClient(DistributedLockProperties properties) {//build RedissonClient clientreturn null;}@BeanRedisDistributedLockImpl redisDistributedLock(RedissonClient client) {return new RedisDistributedLockImpl(client);}}
}
  • @EnableConfigurationProperties(DistributedLockProperties.class)开启配置类 Properties 信息,会将配置文件里的信息注入 Properties 类里。

  • @Configuration配置注解

  • @ConditionalOnClass(CuratorFramework.class)条件注解,要求存在CuratorFramework类当前配置类才生效,Redis 的子配置类同理。

  • @ConditionalOnMissingBean(DistributedLock.class)条件注解,Spring 不存在DistributedLockBean 当前配置类才生效,Redis 的子配置类同理。

  • @ConditionalOnProperty(name = "distributed.lock.type", havingValue = "zookeeper", matchIfMissing = true)条件注解,这里判断配置文件distributed.lock.type等于zookeeper才生效,当如果没配置则默认当做zookeeper,Redis 的子配置类同理。

  • @Bean将方法返回的 Bean 注入到 Spring IOC 容器里,方法入参中含依赖的 Bean

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.demo.distributed.lock.autoconfigure.DistributedLockAutoConfiguration

我们只需要将该文件放到resource/META-INF/spring.factories下,就会被 Spring Boot 加载,这也是 Spring Boot 的约定大于配置的思想。

使用

Maven 依赖关系

<dependencies><dependency><groupId>com.demo</groupId><artifactId>distributed-lock-spring-boot-starter</artifactId><version>1.0.0-SNAPSHOT</version></dependency>
</dependencies><profiles><profile><id>dev</id><activation><activeByDefault>true</activeByDefault></activation><dependencies><!-- Redis --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.23.1</version></dependency></dependencies></profile><profile><id>test</id><dependencies><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>5.1.0</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.1.0</version></dependency></dependencies></profile>
</profiles>

此处结合 Maven profile 功能按照不同环境依赖不同分布式锁底层实现,同时 Spring Boot 也提供了 Spring Boot Profile 加载不同配置,可以从开发、测试、生产环境使用不同底层了,同时 Maven profile 可以根据-P指定加载不同的依赖进行打包,解决了不同环境使用不同分布式锁实现。

代码使用

private final DistributedLock distributedLock;public DemoServiceImpl(DistributedLock distributedLock) {this.distributedLock = distributedLock;
}public void test() {LockInfo lock = null;try {lock = distributedLock.tryLock("demo", 1000, 1000);//do something} finally {if (lock != null) {distributedLock.release(lock);}}
}

业务代码中由于依赖的是接口,结合 Spring Boot Starter 条件注解 + Maven Profile 不管依赖哪个分布式锁实现,都无需去修改代码。

四、总结

本文介绍了在没有 Spring Boot 和 Starter 之前,开发人员在使用传统的 Spring XML 开发 Web 应用时需要引用许多依赖,并且需要大量编写 XML 代码来描述 Bean 以及它们之间的依赖关系。也了解了如何利用 SPI 加载自定义标签来加载 Bean 并进行注入。而 Spring Boot Starter 则提供了一种更加现代化的配置方式,它通过 SPI 机制加载自动装配的 @Configuration 配置类来代替传统的 Spring XML 完成 Bean 的注入,从而消除了大量的 XML 配置。最后,我们通过自定义开发了一个分布式锁 Spring Boot Starter 组件,利用一系列的 @ConditionalXXX 注解和 Maven Profile 来完成开发。这样,我们可以兼容多种分布式锁实现,并且在不同环境下使用不同的分布式锁实现,而无需修改业务代码。

作者:京东零售 陈炎清

来源:京东云开发者社区

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

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

相关文章

Redis 简介

文章目录 Redis 简介 Redis 简介 Redis&#xff08;Remote Dictionary Server&#xff09;&#xff0c;远程词典服务器&#xff0c;基于 C/S 架构&#xff0c;是一个基于内存的键值型 NoSQL 数据库&#xff0c;开源&#xff0c;遵守 BSD 协议&#xff0c;Redis 由 C语言 实现。…

P4145 上帝造题的七分钟 2 / 花神游历各国

上帝造题的七分钟 2 / 花神游历各国 题目背景 XLk 觉得《上帝造题的七分钟》不太过瘾&#xff0c;于是有了第二部。 题目描述 "第一分钟&#xff0c;X 说&#xff0c;要有数列&#xff0c;于是便给定了一个正整数数列。 第二分钟&#xff0c;L 说&#xff0c;要能修改…

《向量数据库指南》——腾讯云向量数据库Tencent Cloud VectorDB产品特性,架构和应用场景

腾讯云向量数据库(Tencent Cloud VectorDB)是一款全托管的自研企业级分布式数据库服务,专用于存储、检索、分析多维向量数据。该数据库支持多种索引类型和相似度计算方法,单索引支持 10 亿级向量规模,可支持百万级 QPS 及毫秒级查询延迟。腾讯云向量数据库不仅能为大模型提…

JAVA开发工具-maven的安装与配置(最新最详细教程)

引言 Maven项目对象模型(POM)&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的项目管理工具 软件。 Maven 除了以程序构建能力为特色之外&#xff0c;还提供高级项目管理工具。由于 Maven 的缺省构建规则有较 高的可重用性&#xff0c;所以常常用两…

模电专题-MOS管的放大电路分析

在实际应用中&#xff0c;我们经常会使用到功率MOS&#xff0c;这时通常不会将它当成一个开关使用&#xff0c;而是当成一个放大器来使用&#xff0c;那这就需要让其工作在放大状态。 参考下图中的mos管的特性曲线&#xff0c;右图中的输出特性曲线中有一根红色的分界线&#x…

竞速榜实时离线对数方案演进介绍 | 京东云技术团队

一、背景 竞速榜是大促期间各采销群提供的基于京东实时销售数据的排行榜&#xff0c;同样应对大促流量洪峰场景&#xff0c;通过榜单撬动品牌在京东增加资源投入。竞速榜基于用户配置规则进行实时数据计算&#xff0c;榜单排名在大促期间实时变化&#xff0c;相关排名数据在微…

【雕爷学编程】Arduino动手做(181)---Maixduino AI开发板2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

roop 视频换脸

roop: one click face swap. 只用一张人脸图片&#xff0c;就能完成视频换脸。 项目地址&#xff1a; https://github.com/s0md3v/roopColab 部署&#xff1a; https://github.com/dream80/roop_colab 本文是本地部署的实践记录。 环境基础 OS: Ubuntu 22.04.2 LTSKernel: 5…

操作系统专栏2-文件系统from小林coding

文件系统 文件系统构成虚拟文件系统文件的使用文件的存储连续存储非连续空间存放方式链表方式索引方式 Linux文件的实现方式 空闲分区的管理文件系统结构目录的存储软链接和硬链接 文件系统构成 Linux的设计哲学有一点很重要:一切皆文件,不仅仅是普通的文件和目录,就连块设备,…

程序员面试金典17.*

文章目录 17.01 不用加号的加法17.04 消失的数字17.05字母与数字17.06 2出现的次数17.07 婴儿名字17.08 马戏团人塔17.09 第k个数17.10 主要元素17.11 单词距离17.12 BiNode17.13 恢复空格&#xff08;未做&#xff0c;字典树dp&#xff09;17.14 最小K个数17.15 最长单词17.16…

webpack基础知识一:说说你对webpack的理解?解决了什么问题?

一、背景 Webpack 最初的目标是实现前端项目的模块化&#xff0c;旨在更高效地管理和维护项目中的每一个资源 模块化 最早的时候&#xff0c;我们会通过文件划分的形式实现模块化&#xff0c;也就是将每个功能及其相关状态数据各自单独放到不同的JS 文件中 约定每个文件是一…

数字孪生的「三张皮」问题:数据隐私、安全与伦理挑战

引言 随着数字化时代的来临&#xff0c;数据成为了当今社会的宝贵资源。然而&#xff0c;数据的广泛使用也带来了一系列隐私、安全与伦理挑战。数字孪生作为一种虚拟的数字化实体&#xff0c;通过收集和分析大量数据&#xff0c;模拟和预测现实世界中的各种情境&#xff0c;为…

windows美化任务栏,不使用软件

1.任务栏透明: 效果图: (1).winr打开命令行 输入regedit回车打开注册表 regedit (2).在注册表中打开 \HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced 这个路径 \HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explore…

(自控原理)自动控制的分类与基本要求

一、分类 1、线性连续控制系统 2、非线性控制系统 判断是时变时不变看的是系数&#xff0c;判断线性还是非线性看的是变量 二、基本要求 三、自动控制的分析方法

C++库函数——String类的模拟实现

目录 ①String类的主体 ②String类的具体实现 1.构造函数、拷贝构造函数、赋值运算符、析构函数 ⑴构造函数 ⑵拷贝构造函数 ⑶赋值运算符 ⑷析构函数 2.迭代器&#xff08;范围for的实现原理&#xff09; 3.修改:push_back, apppend, , clear, swap, c_str ⑴push_b…

如何设计一个自动化测试框架?

一个成熟的测试框架主要由 4 部分组成&#xff1a;基础模块、管理模块、运行模块和统计模块 基础模块 底层核心库 一般指用于操作被测试应用程序的第三方库&#xff0c;例如在 Web 端的 Selenium/WebDriver。如API端的Requests 对象库 PO模式中的页面对象 可重用组件 如一些…

Spring系列二:基于注解配置bean【建议收藏】

文章目录 &#x1f497;通过注解配置bean&#x1f35d;基本介绍&#x1f35d;快速入门&#x1f35d;注意事项和细节 &#x1f497;自己实现Spring注解配置Bean机制&#x1f35d;需求说明&#x1f35d;思路分析&#x1f35d;注意事项和细节 &#x1f497;自动装配 Autowired&…

特性快闪:使用 Databend 玩转 Iceberg

作者&#xff1a;尚卓燃&#xff08;PsiACE&#xff09;澳门科技大学在读硕士&#xff0c;Databend 研发工程师实习生 Apache OpenDAL(Incubating) Committer https://github.com/PsiACE 几周前&#xff0c;Databricks 和 Snowflake 召开了各自的年度大会&#xff0c;除了今年一…

【WebRTC---序篇】(七)RTC多人连麦方案

服务端可以选择mediasoup&#xff0c;作为SFU服务器&#xff0c;只负责转发数据 下图举例三个Client (browser或者客户端)同时加入一个房间&#xff0c;每个app同时发布一路视频和一路音频&#xff0c;并且接受来自其他app的音视频流&#xff0c;mediasoup内部的结构如下&…

python爬虫基础

文章目录 前言爬虫简介urllib库的使用如何获取网页的源码一个类型六个方法一个类型六个方法1、read()方法2、readline()方法3、readlines()方法4、getcode()5、geturl()6、getheaders() urllib下载下载网页下载图片下载视频 请求对象的定制 未完待续 前言 爬虫爬的好牢饭吃的早…