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
开发流程
-
首先介绍一下,这是一个标准的 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>
-
由于在 Spring XML 下还需要依靠 Java Web 和 Web 容器运行,还需要
web/WEB-INF/web.xml
Web 配置文件,内容配置了 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>
-
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>
-
编写 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");} }
-
以上还无法单独运行,需要将以上打包成
war
包放入到 Tomcat 才可运行。
剖析
从上面的开发流程中,我们可以看到入口都在 web.xml
中。其中有一个监听器和一个 Servlet,以及初始化参数 dubbo.xml
和 mvc.xml
。在 Spring Boot 出现之前,Spring 通常使用 XML 配置方式描述 Bean,或者在 XML 中配置注解驱动和上下文扫描方式解析 Bean。因此,我们可以看出这里有两个 XML 文件。经过分析源代码,我们整理出了以下 XML 标签解析到 Bean 解析的流程。如下:
-
由 Tomcat 启动加载
web.xml
并通过监听器和 Servlet 让 Spring 加载 XML 并解析。 -
直到
BeanDefinitionParserDelegate#parseCustomElement
开始解析自定义标签,找到mvc:xxx
或dubbo:xxx
标签找到了 XML 命名空间。 -
DefaultNamespaceHandlerResolver
处理逻辑:以懒加载方式加载所有 jar 中META-INF/spring.handlers
(路径必须得是这个)并缓存到handlerMappings
,通过命名空间 URI 找到与之对应的处理类,SpringMVC 与 Dubbo 命名空间处理类分别为MvcNamespaceHandler
和DubboNamespaceHandler
-
MvcNamespaceHandler
和DubboNamespaceHandler
都分别实现了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
开发流程
-
目录结构与 Maven
demo-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>
-
应用程序入口
DemoSpringBootApplication
@SpringBootApplication public class DemoSpringBootApplication {public static void main(String[] args) {SpringApplication.run(DemoSpringBootApplication.class, args);}}
-
application.yml
文件内容只有 Dubbo 的配置dubbo:application:name: demo-providerprotocol:port: 20880name: dubboregistry:address: zookeeper://127.0.0.1:2181
-
编写 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");} }
-
由于
spring-boot-starter-web
已经内嵌 tomcat ,只需要直接运行DemoSpringBootApplication#main
方法即可运行应用
剖析
从开发流程上没办法第一时间找到解析入口,唯一入口就是在DemoSpringBootApplication
,经过源代码分析得出以下流程:
-
应用
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
。
-
-
Spring Boot 自动装配由
@EnableAutoConfiguration
导入的AutoConfigurationImportSelector
类,会调用SpringFactoriesLoader#loadFactoryNames
从 ClassPath 下扫描所有 jar 包的META-INF/spring.factories
内容,由于传入的EnableAutoConfiguration.class
,只会返回org.springframework.boot.autoconfigure.EnableAutoConfiguration
key 的值,得到一个全限定类名字符串数组configurations
。 -
configurations
经过去重与声明式排除后,会进行以下进行过滤自动装配:configurations = getConfigurationClassFilter().filter(configurations)
分成两部分:获取过滤器和执行过滤。
-
getConfigurationClassFilter()
,也是通过SpringFactoriesLoader#loadFactoryNames
在META-INF/spring.factories
找到 Key 为org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
的值,目前只有:OnBeanCondition
、OnClassCondition
、OnClassCondition
三个过滤器。 -
执行过滤,会根据配置类上含有
@ConditionOnBean
、@ConditionalOnClass
、@ConditionalOnWebApplication
等等条件注解来过滤掉部分配置类。比如WebMvcAutoConfiguration
指定需要在@ConditionOnWebApplication
下才生效。
-
-
在引入各类 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
等数据库操作时,通常会引入一个数据库数据源连接池,比如:HikariCP
、DBCP
等,同时可随意切换依赖而不需要去更改任何业务代码,开发人员也无需关注底层实现,在此我们自定义一个 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 不存在DistributedLock
Bean 当前配置类才生效,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 来完成开发。这样,我们可以兼容多种分布式锁实现,并且在不同环境下使用不同的分布式锁实现,而无需修改业务代码。
作者:京东零售 陈炎清
来源:京东云开发者社区
相关文章:

Spring Boot Starter 剖析与实践 | 京东云技术团队
引言 对于 Java 开发人员来说,Spring 框架几乎是必不可少的。它是一个广泛用于开发企业应用程序的开源轻量级框架。近几年,Spring Boot 在传统 Spring 框架的基础上应运而生,不仅提供了 Spring 的全部功能,还使开发人员更加便捷地…...
技术能力提升-《系统架构设计师教程》
在最近的月度读书会上,国林哥分享了下对《系统架构设计教程》的一点见解,在技术管理摸爬滚打了多年,觉得这个认证还是有一定价值,希望对有兴趣了解这门认证考试的朋友有所帮助,起到抛砖引玉的作用。 国林哥从以下四个方…...
【LeetCode 热题 100】矩阵 专题(大多原地算法,需要一定思维)
解题思路 在 代码注释中! 文章目录 73. 矩阵置零54. 螺旋矩阵48. 旋转图像240. 搜索二维矩阵 II 73. 矩阵置零 class Solution { public:void setZeroes(vector<vector<int>>& matrix) {// 难点:原地算法// 直接复用 matrix 第一行 和 …...
Java 中为什么要把一个数模(10^9+7)
在计算机科学和编程中,经常会遇到需要对结果进行取模操作的情况。模运算是指将一个数除以另一个数,并取得余数的运算。 在 Java 中,常见的一个数取模的值是 (10^97),即 1000000007。这个特定的数值经常在算法和数学计算中被使用&…...

RPC与REST有什么区别?
原文:RPC与REST有什么区别? 背景 好多开发的同学在工作中,经常分不清RPC和REST的区别,导致经常沟通不在一个层次上。甚至有些同学把这两个当成同一个东西。 RPC与REST的区别? 对比名称 rpc rest 备注 架构风格 RP…...

时间复杂度介绍及其计算
时间复杂度 1.算法效率 如何衡量一个算法的好坏呢?看这段代码: long long Fib(int N) {if(N < 3)return 1;return Fib(N-1) Fib(N-2); }这是斐波那契数列的递归代码,非常简洁,那么这就一定说明它好吗?答案显而易…...

etcd实现大规模服务治理应用实战
导读:服务治理目前越来越被企业建设所重视,特别现在云原生,微服务等各种技术被更多的企业所应用,本文内容是百度小程序团队基于大模型服务治理实战经验的一些总结,同时结合当前较火的分布式开源kv产品etcd,…...

目标检测中 anchor base和anchor free
目标检测中两种不同anchor的生成 趋势:anchor free越来越受到实时性检测的青睐,,,...

TypeC拓展设计方案|TypeC转HDMI设计方案|CS5261/CS5265芯片设计参数对比
集睿智远CS5261/CS5265都可以用于设计TypeC转HDMI方案,低成本TypeC扩展坞设计方案,而两者也有些差异:1.CS5261支持DP1.4输入,一个HDMI1.4输出,即HDMI输出为4K30HZ ;CS5265DP1.4到HDMI2.0转换芯片,即HDMI输出…...

SQL Developer中的Active Data Guard
这篇文章 Display Data Guard configuration in SQL Developer 中,用SQL Developer展示了多种ADG的拓扑。 今天自己也试了一下,还蛮简单的,其实最麻烦的部分在于搭建一个ADG环境。 假设我已有一个ADG环境,即最典型的环境&#x…...
谈谈FFT到底有何用
谈谈FFT到底有何用 FFT快速傅里叶变换是数字信号处理的经典算法,学过DSP或者芯片设计的人大多知道这个算法;但是,大家是否想过,为什么数字信号处理会有那么多FFT呢有人会说,为了分析信号的频谱;那么下边的问题就是,分析频谱对我们的日常需求,比如手机打电话,雷达测量速度和方向…...

MATLAB | 如何绘制这样的描边散点图?
part.-1 前前言 最近略忙可能更新的内容会比较简单,见谅哇,今日更新内容: part.0 前言 看到gzhBYtools科研笔记(推荐大家可以去瞅瞅,有很多有意思的图形的R语言复现!!)做了这样一张图: 感觉很…...

偶数科技与白鲸开源完成兼容性认证
近日,偶数科技自主研发的云原生分布式数据库 OushuDB v5.0 完成了与白鲸开源集成调度工具 WhaleStudio v2.0 的兼容性相互认证测试。 测试结果显示,双方产品相互良好兼容,稳定运行、安全,同时可以满足性能需求,为企业级…...

【机器学习】Feature scaling and Learning Rate (Multi-variable)
Feature scaling and Learning Rate 1、数据集2、学习率2.1 α \alpha α 9.9e-72.2 α \alpha α 9e-72.3 α \alpha α 1e-7 3、特征缩放3.1 特征缩放的原因3.2 Z-score 归一化3.3 预测3.4 损失等值线 导入所需的库 import numpy as np np.set_printoptions(precision…...

windows编译ncnn
官方代码https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-windows-x64-using-visual-studio-community-2017 编译工具 visual studio 2017 一、编译protobuf 1、下载protobuf protobuf-3.11.2:https://github.com/google/protobuf/archive/v3.11…...

C++和Lua交互总结
C和Lua交互总结 Chapter1. C和Lua交互总结一、Lua与C的交互机制——Lua堆栈二、堆栈的操作三、C 调用 Lua1)C获取Lua值2)C调用Lua函数示例: 四、Lua 调用 C包装C函数 最后总结一下 Chapter1. C和Lua交互总结 原文链接:https://bl…...

nvm安装和切换node版本
1、nvm list查看已安装的node版本 2、查看当前使用的npm和node版本 3、安装某版本的node 4、 切换node版本...

每日一题8.2 2536
2536. 子矩阵元素加 1 给你一个正整数 n ,表示最初有一个 n x n 、下标从 0 开始的整数矩阵 mat ,矩阵中填满了 0 。 另给你一个二维整数数组 query 。针对每个查询 query[i] [row1i, col1i, row2i, col2i] ,请你执行下述操作:…...

适配器模式(Adapter)
适配器模式用于将一个接口转换成用户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。 Adapter is a structural design pattern that…...

Spring学习笔记——1
Spring学习笔记——1 一、Spring入门1.1、学习路线1.2、传统Javaweb开发困惑及解决方法1.3、三种思想的提出和框架概念1.3.1、IoC、DI和AOP思想提出1.3.2、框架的基本特点 1.4、Spring概述1.5、BeanFactory快速入门1.6、ApplicationContext快速入门1.7、BeanFactory与Applicati…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...