当前位置: 首页 > news >正文

【微服务专题】手写模拟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总共会分为上下两部分。这里是第一部分,在本阶段,需要完成的目标如下:

  1. 简单模拟SpringBoot的启动
  2. 模拟实现SpringApplication.run方法,启动Spring容器,以及web容器
  3. 实现一个Web请求
  4. 模拟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的?应该是两步吧:

  1. 一个自定义的启动类
  2. 启动类上有@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理应完成了以下两步,才能让整个程序正确的运作:(前面也讲过了)

  1. Spring容器已经被正确运行
  2. SpringMVC也被初始化完了
  3. 内置的Tomcat容器,也应该被启动了

这么一说,那 ShenSpringApplication.run(MyApplication.class);应该做什么,大家有点思路了吧。好戏开场了,xdm
在这里插入图片描述

三、run方法的实现

注意,该标题下代码都是写在shen-base-springboot模块下
注意,该标题下代码都是写在shen-base-springboot模块下
注意,该标题下代码都是写在shen-base-springboot模块下

OK,前面说过啊,这里要实现目的如下:

  1. Spring容器已经被正确运行
  2. SpringMVC也被初始化完了
  3. 内置的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)设计模式。它的主要作用是将请求和响应分开处理,使应用程序的逻辑更加清晰,提高代码的可维护性和可重用性。
它的运行原理如下:

  1. 在SpringMVC运行原理中,Tomcat会先启动,然后加载SpringMVC的核心控制器DispatcherServlet
  2. 当用户向服务器发送请求时,DispatcherServlet将请求交给HandlerMapper做解析,HandlerMapper将要访问的Controller返回给DispatcherServlet
  3. DispatcherServlet将用户的请求发送给指定的Controller做业务处理,当业务处理完后,SpringMVC将需要传递的数据和跳转的视图名称封装为一个ModelAndView对象,将ModelAndView对象发送给DispatcherServlet
  4. DispatcherServletModelAndView对象里取出视图名称,交给视图解析器做解析,视图解析器中配置页面路径中的后缀和前缀,解析之后将要跳转的页面反馈给DispatcherServlet
  5. 最终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>

那我现在我也想升级改造为支持自动切换的话,那也没啥问题,抄一下咯。整体来说思路是这样的:

  1. 如果项目中有tomcat的依赖,那就启动tomcat
  2. 如果项目中没有tomcat的依赖,但是有jetty的依赖,那就启动jetty

如何判断是否有对应的依赖?告诉大家一个方法,也是SpringBoot自动配置的思路。那就是使用类加载器load一下一个jar包比较有代表性的class。比如:classloader.loadClass("org.example.tomcat"),通常能load成功即可,出现异常则肯定是不存在依赖的。

  1. 如果两者都有,报错
  2. 两者都没有,也报错

注意啊,从这里开始其实会有点绕。
因为,这个逻辑是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-springbootpom中引入Jetty的依赖:(虽然我偷懒给了空实现,但是这一步是必要的)

    <dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.46.v20220331</version></dependency>

最后,咱也别忘了修改ShenSpringApplicationrun方法:

    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步:

  1. 从哪里拿
  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,到这里一个简单的容器自动配置类就完成了。上面这段代码的最终实现语意如下:

  1. 只有存在org.apache.catalina.startup.Tomcat类,那么我们手写模拟的ShenSpringBoot才会向Spring容器中注册TomcatWebServer这个Bean
  2. 只有存在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启动逻辑就是这样的:

  1. 创建一个AnnotationConfigWebApplicationContext容器
  2. 解析MyApplication类,然后进行扫描
  3. 通过getWebServer方法从Spring容器中获取WebServer类型的Bean
  4. 调用WebServer对象的start方法启动容器

那朋友们,这样就OK了吗?测试一下呗:
在这里插入图片描述
诶?空指针?找不到可用的Web容器?点解啊?在shen-base-springboot模块中不是依赖了TomcatJetty了吗?还做了自动配置。
对啊,你是在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,怎么去扫描位于手写模拟ShenSpringBootshen.springframework下的bean呢?

为什么要重申这个现象,主要是提醒大家,需要SPI的场景,一个简单的现象是:它们通常都发生在两个独立的jar包,或者说包路径上。它们彼此之间是第三方关系。理解这点非常非常重要!

那我再问大家一个问题?什么是API?它与SPI有什么区别?(对的,他们之间有联系的哦)
API即程序应用接口,它跟SPI一样,核心内容都包含接口。它和SPI最明显的区别是:API的接口与实现类存在于实现方;SPI是接口与实现类脱离,不在同一方。有点绕,由下图所示:
在这里插入图片描述所以情况就是这么个情况,想要弄明白这个情况的话,还是要好好思考一下这个情况。哈哈哈
由于是别人实现的,所以,Java提供了一个机制去发现别人的实现,这就是SPI。
在Spring里面,也需要解决类似的问题:由于是别人声明的Bean,所以我需要一种机制去让它们的Bean也注册到我们的Spring容器中。

5.2 Spring / SpringBoot的SPI实现

简单来说,它是这么实现的:

  1. Spring容器启动,注册类配置处理器
  2. spring刷新上下文,执行配置类处理器
  3. 扫描类路径下所有jar包里卖弄的spring.factories文件,将得到的BeanDefinition注册到容器
  4. spring实例化/初始化这些BeanDefinition

它们的实现,其实就是我在【2.1 启动配置类】少写的注解@EnableAutoConfiguration里面!而 @EnableAutoConfiguration集成了@Import注解,这个注解对于springboot非常重要!
在这里插入图片描述
具体的实现我就不再继续讲了,确实知识点挺深的。感兴趣的同学自己去继续研究下吧。

学习总结

  1. 简单学习了一下SpringBoot的启动流程
  2. 简单学习了一下SPI机制,及其在Java和Spring中的使用

感谢

感谢百度大佬【作者:一页一】的文章《SpringBoot(二):springboot自动装配之SPI机制》

相关文章:

【微服务专题】手写模拟SpringBoot

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

七个优秀微服务跟踪工具

随着微服务架构复杂性的增加&#xff0c;在问题出现时确定问题的根本原因变得更具挑战性。日志和指标为我们提供了有用的信息&#xff0c;但并不能提供系统的完整概况。这就是跟踪的用武之地。通过跟踪&#xff0c;开发人员可以监控微服务之间的请求进度&#xff0c;从而使他们…...

redis 问题解决 1

1.1 常见考点 1、Redis 为何这么快? Redis 是一款基于内存的数据结构存储系统,它之所以能够提供非常快的读写性能,主要是因为以下几个方面的原因: 基于内存存储:Redis 所有的数据都存储在内存中,而内存的访问速度比磁盘要快得多。因此,Redis 可以提供非常快的读写性能…...

odoo16前端框架源码阅读——启动、菜单、动作

odoo16前端框架源码阅读——启动、菜单、动作 目录&#xff1a;addons/web/static/src 1、main.js odoo实际上是一个单页应用&#xff0c;从名字看&#xff0c;这是前端的入口文件&#xff0c;文件内容也很简单。 /** 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&#xff0c;计算(a / b)*c的值&…...

CIFAR-100数据集的加载和预处理教程

一、CIFAR-100数据集介绍 CIFAR-100&#xff08;Canadian Institute for Advanced Research - 100 classes&#xff09;是一个经典的图像分类数据集&#xff0c;用于计算机视觉领域的研究和算法测试。它是CIFAR-10数据集的扩展版本&#xff0c;包含了更多的类别&#xff0c;用…...

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 的项目内容&#xff0c;过程中使用 AI 代码工具&#xff0c;对代码进行解释&#xff0c;…...

asp.net学院网上报销系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

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

ElasticSearch知识点

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

STM32 GPIO

STM32 GPIO GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口&#xff0c;也就是我们俗称的IO口 根据使用场景&#xff0c;可配置为8种输入输出模式 引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 数据0就是低电平&#xff0c…...

Electron 开发页面应用

简介 Electron集成了包括chromium&#xff08;理解为具备chrom浏览器的工具&#xff09;&#xff0c;nodejs&#xff0c;native apis chromium&#xff1a;支持最新特性的浏览器。 nodejs&#xff1a;js运行时&#xff0c;可实现文件读写等。 native apis &#xff1a;提供…...

CSDN写博文的128天

起因 为什么要写博文&#xff1f; 写博文是因为当我还是编程小白时&#xff0c;我那会啥也不懂&#xff0c;不懂函数调用&#xff0c;不懂指针&#xff0c;更不懂结构体&#xff0c;别更说Linux&#xff0c;平时不会也没有可以问的人&#xff0c;也幸好有CSDN&#xff0c;遇到…...

Linux学习教程(第二章 Linux系统安装)1

第二章 Linux系统安装 学习 Linux&#xff0c;首先要学会搭建 Linux 系统环境&#xff0c;也就是学会在你的电脑上安装 Linux 系统。 很多初学者对 Linux 望而生畏&#xff0c;多数是因为对 Linux 系统安装的恐惧&#xff0c;害怕破坏电脑本身的系统&#xff0c;害怕硬盘数据…...

vue2手机项目如何使用蓝牙功能

要在Vue2手机项目中使用蓝牙功能&#xff0c;你需要先了解基本的蓝牙知识和API。以下是一些基本的步骤&#xff1a; 确认你的手机设备支持蓝牙功能。在Vue2项目中安装蓝牙插件或库&#xff0c;例如vue-bluetooth或vue-bluetooth-manager。你可以通过npm安装它们。在Vue2项目中…...

魔兽服务器学习-笔记1

文章目录 一、环境准备1&#xff09;依赖安装2&#xff09;源码下载和编译 二、生成数据信息1&#xff09;地图数据信息&#xff08;客户端信息&#xff09;2&#xff09;数据库信息 三、启动服务器四、日志模块五、数据库模块六、场景模块1&#xff09;地图管理2&#xff09;A…...

代码随想录day60|84.柱状图中最大的矩形

84.柱状图中最大的矩形&#xff08;找到右边第一个更小的元素&#xff09; 1、对于每一个柱子&#xff1a;找到左边第一个比他矮的&#xff0c;再找到右边第一个比他矮的。 2、首尾加0&#xff1a; 为什么要在末尾加0&#xff1a;否则如果原数组就是单调递增的话&#xff0c;就…...

常见面试题-分布式锁

Redisson 分布式锁&#xff1f;在项目中哪里使用&#xff1f;多久会进行释放&#xff1f;如何加强一个分布式锁&#xff1f; 答&#xff1a; 什么时候需要使用分布式锁呢&#xff1f; 在分布式的场景下&#xff0c;使用 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 传递数据给子组件&#xff0c;但如果子组件要把数据传递回去&#xff0c;就需要使用自定义事件&#xff01; 我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface)&#xff0c;即&#xff1a; …...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分&#xff1a; 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...