【微服务专题】手写模拟SpringBoot
目录
- 前言
- 阅读对象
- 阅读导航
- 前置知识
- 笔记正文
- 一、工程项目准备
- 1.1 新建项目
- 1.1 pom.xml
- 1.2 业务模拟
- 二、模拟SpringBoot启动:好戏开场
- 2.1 启动配置类
- 2.1.1 shen-base-springboot新增
- 2.1.2 shen-example客户端新增启动类
- 三、run方法的实现
- 3.1 步骤一:启动一个Spring容器
- 3.2 步骤二:初始化SpringMVC
- 3.3 完整步骤二:启动Tomcat容器,初始化SpringMVC
- 3.4 试运行
- 四、升级改造
- 4.1 在shen-base-springboot中新增WebServer接口
- 4.2 getWebServer实现
- 4.2.1 实现一个@ShenConditionalOnClass及逻辑ShenOnClassConditional
- 4.2.2 补充getWebServer逻辑
- 五、模拟SpringBoot自动装配
- 5.1 Java SPI
- 5.2 Spring / SpringBoot的SPI实现
- 学习总结
- 感谢
前言
SpringBoot最大的特点是什么大家伙还记得吧?自动配置 + 约定大于配置原则。
阅读对象
阅读导航
在本文中,我自己手写的SpringBoot,我将其称为:ShenSpringBoot
前置知识
笔记正文
本次手写模拟SpringBoot总共会分为上下两部分。这里是第一部分,在本阶段,需要完成的目标如下:
- 简单模拟SpringBoot的启动
- 模拟实现SpringApplication.run方法,启动Spring容器,以及web容器
- 实现一个Web请求
- 模拟SpringBoot自动选择Tomcat/Jetty容器
另外还有一点,关于【手写边界】需要跟大家确认。
5. 这边只是模拟SpringBoot手写一个Web工程,而并非是手写Spring这块
6. 因为是模拟Web请求,所以也会用到SpringMVC的内容,但是SpringMVC的东西显然也不在我们的模拟范围内
一、工程项目准备
1.1 新建项目
新建一个工程,里面有两个Module
shenspringboot
:我的父工程shen-base-springboot
:实现手写SpringBoot的模块。注意这个包名,它不能跟模拟使用示例的报名一样shen-example
:示例模块,用来测试我手写的SpringBoot是否能够工作。注意这个包名,它不能跟手写模拟框架的SpringBoot一样
1.1 pom.xml
因为是依赖Spring跟SpringMVC嘛,所以,我们肯定要在模拟SpringBoot的pom中加入前两者的依赖。另外我们也知道,SpringBoot它是内置容器的,比如Tomcat,所以,我们也需要在手写模块中添加这些依赖。如下:
注意:这里使用的Spring版本为:5.3.20
1)shen-base-springboot模块的pom.xml
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.version}</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version></dependency><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>9.0.60</version></dependency>
2)shen-example的pom.xml
<dependency><groupId>org.example</groupId><artifactId>shen-base-springboot</artifactId><version>1.0-SNAPSHOT</version></dependency>
1.2 业务模拟
我们在shen-example
里面简单新增了一个controler
,用于接收一个web请求。
正常,会返回下面的结果给到客户端
显然,现在运行的化,不会,也不应该有任何效果。毕竟我们还没有完成启动类嘛
二、模拟SpringBoot启动:好戏开场
首先在模拟之前,我们来回忆下,你在项目中是如何启动一个SpringBoot的?应该是两步吧:
- 一个自定义的启动类
- 启动类上有
@SpringBootApplication
注解,然后在main
方法中使用SpringApplication.run
启动SpringBoot应用
大概就跟这个一样:
所以,我们也来照抄一个试试
2.1 启动配置类
当然啦,这个启动配置类在两端都要写点代码,但主要还是在shen-base-springboot
这边。
2.1.1 shen-base-springboot新增
首先新增一个@ShenSpringBootApplication
注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface ShenSpringBootApplication {
}
注意,相比于原生的,我这边缺少了一个很重要的注解,
@EnableAutoConfiguration
,这个后面的【5.3】会提到
至于为什么注解内容是这样子的,当然是因为原来的@SpringBootApplication
就是这样子的呀
注意上图的一个注解:
@EnableAutoConfiguration
,后面【5.3】会提到
然后呢,还要新增一个单例,像这样的:
如下:
有了以上两者,我们就可以在客户端中使用了。
2.1.2 shen-example客户端新增启动类
启动类如下:
@ShenSpringBootApplication
public class MyApplication {public static void main(String[] args) {ShenSpringApplication.run(MyApplication.class);}
}
嘿,是不是有点感觉了。OK,理论上来说,客户端这边已经完成了所有了。按照我们正常使用SpringBoot,当我们点击运行之后,就可以开始我们的web请求了。显然,当我们点击这一步的时候,SpringBoot理应完成了以下两步,才能让整个程序正确的运作:(前面也讲过了)
- Spring容器已经被正确运行
- SpringMVC也被初始化完了
- 内置的Tomcat容器,也应该被启动了
这么一说,那 ShenSpringApplication.run(MyApplication.class);
应该做什么,大家有点思路了吧。好戏开场了,xdm
三、run方法的实现
注意,该标题下代码都是写在
shen-base-springboot
模块下
注意,该标题下代码都是写在shen-base-springboot
模块下
注意,该标题下代码都是写在shen-base-springboot
模块下
OK,前面说过啊,这里要实现目的如下:
- Spring容器已经被正确运行
- SpringMVC也被初始化完了
- 内置的Tomcat容器,也应该被启动了
那行,我们就一步一步来呗
3.1 步骤一:启动一个Spring容器
启动一个Spring容器,这个不在我们本次的研究范围内,怎么启动一个Spring容器大家直接抄就好了。代码如下:
public class ShenSpringApplication {public static void run(Class clazz) {// 步骤一:创建并启动一个Spring容器AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();context.register(clazz);context.refresh();}
}
为了美观,我通常喜欢把这种语义明确的代码写在一个独立的方法里面(偷偷告诉你们,这个是我看了Spring源码之后,学来的大神们的代码风格)。后面也基本会按照这种方式来写代码。美化后如下:
public class ShenSpringApplication {public static void run(Class clazz) {// 步骤一:创建并启动一个Spring容器ApplicationContext context = startSpring(clazz);}/*** 启动一个Spring容器** @param clazz 配置类* @return 一个创建完成的Spring ApplicationContext*/private static ApplicationContext startSpring(Class clazz) {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();context.register(clazz);context.refresh();return context;}
}
走到这里,大家多多少少知道,为什么run()
方法要传入一个Class
对象了吧,是的,充当配置类的。为了照顾Spring不太熟悉的朋友,简单说一下。
首先,我们传入的是客户端启动类MyApplication
嘛,然后,我们的MyApplication
不是被一个我们自定义的@ShenSpringBootApplication
修饰吗?然后这个注解呢,他又是存在@ComponentScan
注解的,所以自然,我们Spring容器想要知道怎么扫描,该扫描哪里,就需要程序员告知了。
所以,到此为止,之所以需要传入一个class
,是为了告知Spring容器需要扫描的包路径是什么。SpringBoot默认扫描的路径大家还知道吧?启动类所在的包以及子包嘛。
3.2 步骤二:初始化SpringMVC
大家还知道SpringMVC是干什么的吗?O,我想处于SpringBoot时代的我们,对SpringMVC确实比较陌生一点。其实我自己也是,不过我呢,也只是很久没用过了,但是大致的内容跟原理还是懂的。这里给大家一篇文章,快速回忆学习下SpringMVC。
言归正传。SpringMVC是Spring框架中的一个模块,用于实现Java Web应用程序的Model-View-Controller(MVC)设计模式。它的主要作用是将请求和响应分开处理,使应用程序的逻辑更加清晰,提高代码的可维护性和可重用性。
它的运行原理如下:
- 在SpringMVC运行原理中,Tomcat会先启动,然后加载SpringMVC的核心控制器
DispatcherServlet
- 当用户向服务器发送请求时,
DispatcherServlet
将请求交给HandlerMapper
做解析,HandlerMapper
将要访问的Controller返回给DispatcherServlet
DispatcherServlet
将用户的请求发送给指定的Controller做业务处理,当业务处理完后,SpringMVC将需要传递的数据和跳转的视图名称封装为一个ModelAndView
对象,将ModelAndView
对象发送给DispatcherServlet
DispatcherServlet
从ModelAndView
对象里取出视图名称,交给视图解析器做解析,视图解析器中配置页面路径中的后缀和前缀,解析之后将要跳转的页面反馈给DispatcherServlet
- 最终
DispatcherServlet
将数据发送给页面通过Response对象响应给用户
SpringMVC,主要把上面出现的几个英文名词(核心组件)搞清楚大概也差不多了
OK,我为什么要贴一段简单的SpringMVC运行原理呢,哈,主要是告诉大家一点:SpringMVC运行之前,Tomcat容器应该先被启动,再然后才会去加载SpringMVC核心组件DispatcherServlet
。
所以,步骤二的代码其实跟Tomcat是融合在一起的,它们一起才是真正的步骤二。
3.3 完整步骤二:启动Tomcat容器,初始化SpringMVC
我们这里使用的是内嵌的Tomcat版本,模拟SpringBoot嘛,而对于启动内嵌的Tomcat,这并不是我们本篇笔记在意的内容,所以直接网上抄一个就好了。
代码如下:
public class ShenSpringApplication {public static void run(Class clazz) {// 步骤一:创建并启动一个Spring容器ApplicationContext context = startSpring(clazz);// 步骤二:启动tomcat容器,并且初始化SpingMVC的核心组件startTomcat(context);}/*** 启动一个Spring容器** @param clazz 配置类* @return 一个创建完成的Spring ApplicationContext*/private static ApplicationContext startSpring(Class clazz) {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();context.register(clazz);context.refresh();return context;}/*** 启动tomcat容器,并且初始化SpingMVC的核心组件** @param applicationContext Spring应用上下文*/private static void startTomcat(ApplicationContext applicationContext){Tomcat tomcat = new Tomcat();Server server = tomcat.getServer();Service service = server.findService("Tomcat");Connector connector = new Connector();connector.setPort(8081);Engine engine = new StandardEngine();engine.setDefaultHost("localhost");Host host = new StandardHost();host.setName("localhost");String contextPath = "";Context context = new StandardContext();context.setPath(contextPath);context.addLifecycleListener(new Tomcat.FixContextListener());host.addChild(context);engine.addChild(host);service.setContainer(engine);service.addConnector(connector);// 初始化SpringMVC组件tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet((AnnotationConfigWebApplicationContext) applicationContext));context.addServletMappingDecoded("/*", "dispatcher");try {tomcat.start();} catch (LifecycleException e) {e.printStackTrace();}}
}
注意哦,我这边tomcat
绑定的接口改为了8081
,安全点嘛。至于其他代码为什么要这么写,啊,因为tomcat跟SpringMVC就是这么写的啊,我们也不关心啊。OK,到了这里,基本上一个简单的【模拟SpringBoot的Web工程】已经能提供简单的服务了。我们运行试试看效果。
3.4 试运行
点击运行后,首先出来的是tomcat的日志,没毛病。接着我们访问一下我们的接口看看,在浏览器输入http://localhost:8081/test试试看效果
输出结果如上,预期是:手写Springboot
。哈,乱码了。不过无所谓,反正手写模拟SpringBoot成功了。
四、升级改造
到了这里,虽然基本的ShenSpringBoot跑起来了,但是,我们原生的SpringBoot的功能远不止如此。就比如,原生SpringBoot支持多种内置容器,有:Jetty、Undertown。然后默认是Tomcat这样子。在原有的SpringBoot中想要切换Jetty容器,只需要在pom.xml中的springboot-starter-web
依赖中排除tomcat
配置,然后添加jetty
的依赖就可以了。如下:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.0</version><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId><version>2.7.0</version></dependency>
那我现在我也想升级改造为支持自动切换的话,那也没啥问题,抄一下咯。整体来说思路是这样的:
- 如果项目中有tomcat的依赖,那就启动tomcat
- 如果项目中没有tomcat的依赖,但是有jetty的依赖,那就启动jetty
如何判断是否有对应的依赖?告诉大家一个方法,也是SpringBoot自动配置的思路。那就是使用类加载器load一下一个jar包比较有代表性的class。比如:
classloader.loadClass("org.example.tomcat")
,通常能load成功即可,出现异常则肯定是不存在依赖的。
- 如果两者都有,报错
- 两者都没有,也报错
注意啊,从这里开始其实会有点绕。
因为,这个逻辑是SpringBoot自动帮我们实现的,对于程序员而言,只要在pom中添加相关的依赖就可以了,想用什么容器都是用户自己的选择。
另外,我想大家通过上面的代码也发现了,所谓容器,不管是tomcat还是jetty,都是Servlet容器而已,所以我们可以定义一个接口去约束、表示他们,多态嘛。在设计模式中也叫做【策略模式】。这里就暂时叫做WebServer
吧。
4.1 在shen-base-springboot中新增WebServer接口
public interface WebServer {/*** 启动一个容器*/void start();
}
然后Tomcat实现:(注意,理论上这里需要实现Tomcat启动内容的,但是这里为了方便不写了,他也不是我们要关注的内容。我们随便看看效果)
public class TomcatWebServer implements WebServer{@Overridepublic void start() {// 这巴拉巴拉的应该是Tomcat容器启动代码,我们前面也演示过了,这里不重复了System.out.println("启动一个Tomcat容器");}
}
Jetty实现:(注意,理论上这里需要实现Jetty启动内容的,但是这里为了方便不写了,他也不是我们要关注的内容。我们随便看看效果)
public class JettyWebServer implements WebServer{@Overridepublic void start() {// 这巴拉巴拉的应该是Jetty容器启动代码System.out.println("启动一个Jetty容器");}
}
当然,既然我们引入了Jetty容器的实现,我们也要在手写模拟的shen-base-springboot
的pom
中引入Jetty
的依赖:(虽然我偷懒给了空实现,但是这一步是必要的)
<dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.46.v20220331</version></dependency>
最后,咱也别忘了修改ShenSpringApplication
的run
方法:
public static void run(Class clazz) {// 步骤一:创建并启动一个Spring容器ApplicationContext context = startSpring(clazz);// // 步骤二:启动tomcat容器,并且初始化SpingMVC的核心组件// startTomcat(context);// 改进步骤二:获取一个容器,并启动,接着初始化SpringMVC的核心组件final WebServer webServer = getWebServer();webServer.start();}private static WebServer getWebServer() {return null;}
OK,这一步到这里就暂时收住了,剩下的代码就是考虑如何实现这个getWebServer
。
4.2 getWebServer实现
getWebServer
该如何实现呢?从实现思路来说,我觉得简单的可以分为2步:
- 从哪里拿
- 怎么拿
其实这些都不难理解,大哥们,咱们都是在Spring容器中开发了,当然是在Spring拿啊。所以嘛,我们能在客户端中,通过Spring容器去获取我们的容器,那肯定是因为,在SpringBoot那边,向Spring容器注册了Bean啊。有点拗口,直接上代码大家就懂了:(代码在shen-base-springboot
)
@Configuration
public class WebServerAutoConfiguration{@Beanpublic TomcatWebServer tomcatWebServer() {return new TomcatWebServer();}@Beanpublic JettyWebServer jettyWebServer() {return new JettyWebServer();}
}
是这样没错吧?哈哈,其实有一点点错误。为什么, 因为正常过来说,这两个容器不会同时存在的,他们的存在都是有条件的,咱前面不是说了吗,需要某个包存在嘛,所以很明显,这里的@Bean
还需要一个类似@ConditionalOnBean
的条件注解。是的,这也正是我们要自定义实现的一个条件注解。怎么实现?抄。
4.2.1 实现一个@ShenConditionalOnClass及逻辑ShenOnClassConditional
注意,不懂得如何实现一个自定义条件注解的朋友可以自行百度先
主要实现类有:@ShenConditionalOnClass
一个注解类,以及核心的条件实现类ShenOnClassConditional
。后者ShenOnClassConditional
才是真正做判断的地方。代码如下:(代码在shen-base-springboot
)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ShenOnClassConditional.class)
public @interface ShenConditionalOnClass {String value() default "";
}
ShenOnClassConditional
实现了Condition
接口的类,它才是真正得条件逻辑处理类哦。
public class ShenOnClassConditional implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ShenConditionalOnClass.class.getName());String className = (String) annotationAttributes.get("value");try {context.getClassLoader().loadClass(className);return true;} catch (ClassNotFoundException e) {// 找不到需要加载的类,会抛异常:ClassNotFoundException// 既然找不到,所以返回falsereturn false;}}
}
具体逻辑为,拿到@ShenConditionalOnClass
中的value属性,然后用类加载器进行加载,如果加载到了所指定的这个类,那就表示符合条件,如果加载不到,类加载会抛出异常ClassNotFoundException
,则表示不符合条件。
那既然条件注解也实现了,接下来就是在WebServerConfig
使用他们就好了,修改后的代码如下:
@Configuration
public class WebServerAutoConfiguration {/*** Tomcat容器典型加载类*/private static final String TOMCAT_PATH = "org.apache.catalina.startup.Tomcat";/*** Jetty容器典型加载累*/private static final String JETTY_PATH = "org.eclipse.jetty.server.Server";@Bean@ShenConditionalOnClass(TOMCAT_PATH)public TomcatWebServer tomcatWebServer() {return new TomcatWebServer();}@Bean@ShenConditionalOnClass(JETTY_PATH)public JettyWebServer jettyWebServer() {return new JettyWebServer();}
}
OK,到这里一个简单的容器自动配置类就完成了。上面这段代码的最终实现语意如下:
- 只有存在
org.apache.catalina.startup.Tomcat
类,那么我们手写模拟的ShenSpringBoot才会向Spring容器中注册TomcatWebServer这个Bean - 只有存在
2.org.eclipse.jetty.server.Server
类,那么我们手写模拟的ShenSpringBoot才会向Spring容器中注册JettyWebServer这个Bean
4.2.2 补充getWebServer逻辑
那最后,就是在getWebServer填上这样的逻辑咯
/*** 从当前Spring容器中获取一个Servlet容器** @param applicationContext Spring应用上下文*/private static WebServer getWebServer(ApplicationContext applicationContext) {// key为beanName, value为Bean对象Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);if (webServers.isEmpty()) {System.err.println("找不到可用的Web容器");throw new NullPointerException();}if (webServers.size() > 1) {System.err.println("找到了多个容器");throw new IllegalStateException();}// 返回唯一的一个return webServers.values().stream().findFirst().get();}
这样整体SpringBoot启动逻辑就是这样的:
- 创建一个AnnotationConfigWebApplicationContext容器
- 解析MyApplication类,然后进行扫描
- 通过getWebServer方法从Spring容器中获取WebServer类型的Bean
- 调用WebServer对象的start方法启动容器
那朋友们,这样就OK了吗?测试一下呗:
诶?空指针?找不到可用的Web容器?点解啊?在shen-base-springboot
模块中不是依赖了Tomcat
跟Jetty
了吗?还做了自动配置。
对啊,你是在shen-base-springboot
做了自动配置,可童鞋们,它的目录是啥:shen.springframework
哦,而你现在的扫描路径是啥?com.shen.example
,都扫描不到了,给你注册个锤锤哦。
是的,这也是在SpringBoot中一样会遇到的问题,他们是怎么解决的呢?当然原理肯定是把这些包也加入到扫描包里面,而SpringBoot则是通过自定义的,一个叫做SPI(Service Provider Interface)
的服务发现
机制,来扫描注册一些基础的服务Bean。
说SPI可能大家比较陌生,其实就是我们比较熟知的
spring.factories
五、模拟SpringBoot自动装配
想要模拟的话,其实最好还是了解一下SPI
机制比较合适,但说实在,想要真的深入去了解一下的话,单单是这个内容都可以写一篇文章出来了。所以我比较建议,大家直接去看这篇文章《SpringBoot(二):springboot自动装配之SPI机制》。
不过对这个知识点不是很感兴趣的朋友,可以看看我这里的简单解释版本。
5.1 Java SPI
我们之所以聊到了SPI这个东西,是因为,我们在前面的推导中遇到了一个问题,那就是:
- 已知我的业务项目中,Spring容器的扫描路径是
com.shen.example
,怎么去扫描位于手写模拟ShenSpringBoot
下shen.springframework
下的bean呢?
为什么要重申这个现象,主要是提醒大家,需要SPI的场景,一个简单的现象是:它们通常都发生在两个独立的jar
包,或者说包路径上。它们彼此之间是第三方关系。理解这点非常非常重要!
那我再问大家一个问题?什么是API?它与SPI有什么区别?(对的,他们之间有联系的哦)
API即程序应用接口,它跟SPI一样,核心内容都包含接口。它和SPI最明显的区别是:API的接口与实现类存在于实现方;SPI是接口与实现类脱离,不在同一方。有点绕,由下图所示:
所以情况就是这么个情况,想要弄明白这个情况的话,还是要好好思考一下这个情况。哈哈哈
由于是别人实现的,所以,Java提供了一个机制去发现别人的实现,这就是SPI。
在Spring里面,也需要解决类似的问题:由于是别人声明的Bean,所以我需要一种机制去让它们的Bean也注册到我们的Spring容器中。
5.2 Spring / SpringBoot的SPI实现
简单来说,它是这么实现的:
- Spring容器启动,注册类配置处理器
- spring刷新上下文,执行配置类处理器
- 扫描类路径下所有jar包里卖弄的spring.factories文件,将得到的BeanDefinition注册到容器
- spring实例化/初始化这些BeanDefinition
它们的实现,其实就是我在【2.1 启动配置类】少写的注解@EnableAutoConfiguration
里面!而 @EnableAutoConfiguration
集成了@Import
注解,这个注解对于springboot非常重要!
具体的实现我就不再继续讲了,确实知识点挺深的。感兴趣的同学自己去继续研究下吧。
学习总结
- 简单学习了一下SpringBoot的启动流程
- 简单学习了一下SPI机制,及其在Java和Spring中的使用
感谢
感谢百度大佬【作者:一页一】的文章《SpringBoot(二):springboot自动装配之SPI机制》
相关文章:

【微服务专题】手写模拟SpringBoot
目录 前言阅读对象阅读导航前置知识笔记正文一、工程项目准备1.1 新建项目1.1 pom.xml1.2 业务模拟 二、模拟SpringBoot启动:好戏开场2.1 启动配置类2.1.1 shen-base-springboot新增2.1.2 shen-example客户端新增启动类 三、run方法的实现3.1 步骤一:启动…...

七个优秀微服务跟踪工具
随着微服务架构复杂性的增加,在问题出现时确定问题的根本原因变得更具挑战性。日志和指标为我们提供了有用的信息,但并不能提供系统的完整概况。这就是跟踪的用武之地。通过跟踪,开发人员可以监控微服务之间的请求进度,从而使他们…...
redis 问题解决 1
1.1 常见考点 1、Redis 为何这么快? Redis 是一款基于内存的数据结构存储系统,它之所以能够提供非常快的读写性能,主要是因为以下几个方面的原因: 基于内存存储:Redis 所有的数据都存储在内存中,而内存的访问速度比磁盘要快得多。因此,Redis 可以提供非常快的读写性能…...
odoo16前端框架源码阅读——启动、菜单、动作
odoo16前端框架源码阅读——启动、菜单、动作 目录:addons/web/static/src 1、main.js odoo实际上是一个单页应用,从名字看,这是前端的入口文件,文件内容也很简单。 /** odoo-module **/import { startWebClient } from "…...

C/C++(a/b)*c的值 2021年6月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析
目录 C/C(a/b)*c的值 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C(a/b)*c的值 2021年6月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给定整数a、b、c,计算(a / b)*c的值&…...

CIFAR-100数据集的加载和预处理教程
一、CIFAR-100数据集介绍 CIFAR-100(Canadian Institute for Advanced Research - 100 classes)是一个经典的图像分类数据集,用于计算机视觉领域的研究和算法测试。它是CIFAR-10数据集的扩展版本,包含了更多的类别,用…...

C#,数值计算——函数计算,Eulsum的计算方法与源程序
1 文本格式 using System; namespace Legalsoft.Truffer { public class Eulsum { private double[] wksp { get; set; } private int n { get; set; } private int ncv { get; set; } public bool cnvgd { get; set; } pri…...

ChatGLM3 langchain_demo 代码解析
ChatGLM3 langchain_demo 代码解析 0. 背景1. 项目代码结构2. 代码解析2-1. utils.py2-2. ChatGLM3.py2-3. Tool/Calculator.py2-4. Tool/Weather.py2-5. main.py 0. 背景 学习 ChatGLM3 的项目内容,过程中使用 AI 代码工具,对代码进行解释,…...

asp.net学院网上报销系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
一、源码特点 asp.net学院网上报销系统是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语言 开发 asp.net学院网上报销系统 应用技术…...

ElasticSearch知识点
什么是ElasticSearch ElasticSearch: 智能搜索,分布式的搜索引擎,是ELK的一个非常完善的产品,ELK代表的是: E就是ElasticSearch,L就是Logstach,K就是kibana Elasticsearch是一个建立在全文搜索引擎 Apache Lucene基础…...

STM32 GPIO
STM32 GPIO GPIO简介 GPIO(General Purpose Input Output)通用输入输出口,也就是我们俗称的IO口 根据使用场景,可配置为8种输入输出模式 引脚电平:0V~3.3V,部分引脚可容忍5V 数据0就是低电平,…...
Electron 开发页面应用
简介 Electron集成了包括chromium(理解为具备chrom浏览器的工具),nodejs,native apis chromium:支持最新特性的浏览器。 nodejs:js运行时,可实现文件读写等。 native apis :提供…...
CSDN写博文的128天
起因 为什么要写博文? 写博文是因为当我还是编程小白时,我那会啥也不懂,不懂函数调用,不懂指针,更不懂结构体,别更说Linux,平时不会也没有可以问的人,也幸好有CSDN,遇到…...

Linux学习教程(第二章 Linux系统安装)1
第二章 Linux系统安装 学习 Linux,首先要学会搭建 Linux 系统环境,也就是学会在你的电脑上安装 Linux 系统。 很多初学者对 Linux 望而生畏,多数是因为对 Linux 系统安装的恐惧,害怕破坏电脑本身的系统,害怕硬盘数据…...
vue2手机项目如何使用蓝牙功能
要在Vue2手机项目中使用蓝牙功能,你需要先了解基本的蓝牙知识和API。以下是一些基本的步骤: 确认你的手机设备支持蓝牙功能。在Vue2项目中安装蓝牙插件或库,例如vue-bluetooth或vue-bluetooth-manager。你可以通过npm安装它们。在Vue2项目中…...
魔兽服务器学习-笔记1
文章目录 一、环境准备1)依赖安装2)源码下载和编译 二、生成数据信息1)地图数据信息(客户端信息)2)数据库信息 三、启动服务器四、日志模块五、数据库模块六、场景模块1)地图管理2)A…...
代码随想录day60|84.柱状图中最大的矩形
84.柱状图中最大的矩形(找到右边第一个更小的元素) 1、对于每一个柱子:找到左边第一个比他矮的,再找到右边第一个比他矮的。 2、首尾加0: 为什么要在末尾加0:否则如果原数组就是单调递增的话,就…...

常见面试题-分布式锁
Redisson 分布式锁?在项目中哪里使用?多久会进行释放?如何加强一个分布式锁? 答: 什么时候需要使用分布式锁呢? 在分布式的场景下,使用 Java 的单机锁并不可以保证多个应用的同时操作共享资源…...
vue开发 安装一些工具
下载 node.js环境 nodeJs 官网 命令行输入 node -v 和 npm -v 出现版本号 代表nodejs 安装成功选择安装pnpm npm install -g pnpmpnpm -v 出现版本号即成功安装安装 scss vue3 组件库 Element Plus Element 官网 安装 pnpm install Element-Plus --save第一次使用开发v…...
Vue.js 组件 - 自定义事件
Vue.js 组件 - 自定义事件 父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件! 我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即: …...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...