手写SpringBoot核心功能流程

        本文通过手写模拟实现一个简易版的Spring Boot 程序,让大家能以非常简单的方式知道Spring Boot大概的工作流程。

工程依赖

创建maven工程,并创建两个module

springboot模块:手写模拟springboot框架的源码实现

test模块:业务系统,用来写业务代码来测试我们所模拟出来的SpringBoot

springboot模块依赖:SpringBoot基于的Spring,依赖Spring同上也支持Spring MVC,所以也要依赖Spring MVC,包括Tomcat等,在SpringBoot模块中要添加以下依赖:

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.18</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></dependencies>

test模块依赖:依赖springboot模块依赖即可:

<dependencies><dependency><groupId>org.example</groupId><artifactId>springboot</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>

然后再test模块中创建相关的controllerservice测试类:

/*** @author liupeng* @version 1.0* @description: TODO* @date 2024/5/7 17:05*/
@RestController
public class TestController {@Autowiredprivate TestService testService;@GetMapping("/test")public String test(@RequestParam("name") String name){return testService.sayHello(name);}
}
/*** @author liupeng* @version 1.0* @description: TODO* @date 2024/5/7 17:06*/
@Component
public class TestService {public String sayHello(String name) {return "Hello " + name + "!";}
}

创建核心注解和核心执行类

springboot中,最重要的是一个注解和一个启动类上的:

SpringApplication:该类有个run方法,主启动main中进行调用
@SpringBootApplication:该注解是添加在主启动类上的

因此我们可以在springboot模块中模拟实现以上核心注解和核心类。

@LPSpringBootApplication 注解:

/*** @author liupeng* @version 1.0* @description: TODO* @date 2024/5/7 17:10*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
public @interface LPSpringBootApplication {
}

启动类:


/*** @author liupeng* @version 1.0* @description: TODO* @date 2024/5/7 17:11*/
public class LPSpringApplication {public static void run(Class clazz) {}}

然后在test模块中LPApplication类的进行使用


/*** @author liupeng* @version 1.0* @description: TODO* @date 2024/5/7 17:12*/
@LPSpringBootApplication
public class LPApplication {public static void main(String[] args) {LPSpringApplication.run(LPApplication.class);}
}

填充完善run方法

        首先,我们希望run方法执行完后,能在浏览器中访问到UserController,那么run方法
中需要启动Tomcat,通过Tomcat接收到http请求。
        在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法。
因此,在run方法中,我们需要实现如下步骤:
        创建Spring容器
        创建Tomcat对象
        生成DispatcherServlet对象,并且创建出来的Spring容器进行绑定
        将DispatcherServlet添加到Tomcat中
        启动Tomcat

创建Spring容器

/*** @author liupeng* @version 1.0* @description: TODO* @date 2024/5/7 17:11*/
public class LPSpringApplication {public static void run(Class clazz) {System.out.println("Hello World!");AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();webApplicationContext.register(clazz);webApplicationContext.refresh();}

        我们创建AnnotationConfigWebApplicationContext 容器,并且将run方法传入的Class作为容器的配置类,比如上面的LPApplication.Class传入到了run方法中,于是LPApplication就变成容器的配置类,由于LPApplication 类上添加了@LPSpringBootApplication注解,同时该注解中存在@ComponentScan注解,于是该配置类会去扫描LPApplication所在的包路径,从而会将TestController TestService 类进行扫描并添加在容器内部,并在容器内部存在了这两个bean对象。

 Tomcat启动

使用Embed-Tomcat,模拟真正的springboot使用的内嵌Tomcat

public static void startTomcat(WebApplicationContext applicationContext){Tomcat tomcat = new Tomcat();Server server = tomcat.getServer();Service service = server.findService("Tomcat");Connector connector = new Connector();connector.setPort(8080);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);tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet(applicationContext));context.addServletMappingDecoded("/*", "dispatcher");try {tomcat.start();} catch (LifecycleException e) {e.printStackTrace();}}

然后在run方法中执行startTomcat方法便可启动Tomcat容器

效果如下

然后访问controller中的测试方法

我们再test的业务模块中只需要用到@LPSpringBootApplication注解和LPSpringApplication类即可。

实现多http(servlet)服务器的切换

上面我们模拟实现了简单的sprinboot,但在真正的springboot中是可以进行使用多种httpservlet)服务器的比如Tomcatundertowjetty等。

那么我们就需要进行动态切换

        如果项目中有Tomcat的依赖,那就启动Tomcat
        如果项目中有Jetty的依赖就启动Jetty
        如果两者都没有或者都没有则进行报错

我们可以定义一个webserver接口,在接口进行定义抽象的start方法,不同的服务器进行各种的自定义实现

public interface WebServer {public void start();
}
public class TomcatWebServer implements WebServer{@Overridepublic void start() {System.out.println("TomcatServer  is starting...");}
public class JettyWebServer implements WebServer {@Overridepublic void start() {System.out.println("JettyWebServer is starting...");}}

然后再run方法中我们需要进行获取对应的webserver实现,然后进行启动start方法

public class LPSpringApplication {public static void run(Class clazz) {System.out.println("Hello World!");AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();webApplicationContext.register(clazz);webApplicationContext.refresh();//        TomcatWebServer.startTomcat(webApplicationContext);getWebServer(webApplicationContext).start();}public static WebServer getWebServer(WebApplicationContext applicationContext){Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);if (beansOfType.isEmpty()) {throw new NullPointerException();}if (beansOfType.size() > 1) {throw new IllegalStateException();}return beansOfType.values().stream().findFirst().get();}}

模拟使用条件注解

首先我们需要实现Contion接口和一个自定义的条件注解@LPConditionalOnClass

public class LPCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(LPConditionalOnClass.class.getName());String className = (String) annotationAttributes.get("value");try {context.getClassLoader().loadClass(className);return true;} catch (ClassNotFoundException e) {return false;}}
}@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(LPCondition.class)
public @interface LPConditionalOnClass {String value();
}

LPCondition 的逻辑是拿到LPConditionalOnClass 注解的value值然后进行类加载器加载,加载成功则符合条件,反之亦然。

模拟实现自动配置类

创建了条件注解我们应该咋样使用呢?

我们需要使用自动配置的概念,我们只需要将满足条件的类进行注入到spring容器中即可

public interface AutoConfiguration {
}@Configuration
public class WebServerAutoConfiguration implements AutoConfiguration {@Bean@LPConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer(){return new TomcatWebServer();}@Bean@LPConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}
}

然后再容器中进行根据WebServer类型进行加载便可获取到对应的bean对象:

public static WebServer getWebServer(WebApplicationContext applicationContext){Map<String, WebServer> beansOfType = applicationContext.getBeansOfType(WebServer.class);if (beansOfType.isEmpty()) {throw new NullPointerException();}if (beansOfType.size() > 1) {throw new IllegalStateException();}return beansOfType.values().stream().findFirst().get();}

        整体SpringBoot大概启动逻辑:

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


        有了以上步骤,但是还差了一个关键步骤,就是Spring要能解析到WebServiceAutoConfiguration这个自动配置类,此时我们需要SpringBootrun方法中,能找到WebServiceAutoConfiguration这个配置类并添加到Spring容器中。
        为了让Spring解析到WebServiceAutoConfiguration这个自动配置类并将其添加到Spring容器中,Spring Bootrun方法中需要找到这个自动配置类。虽然LPApplication是Spring的配置类,并且我们可以将其传递给Spring Boot以添加到Spring容器中,但WebServiceAutoConfiguration的自动发现需要借助Spring Boot的机制,而不是通过手动配置。

        由于LPApplication是我们显式提供给Spring Boot的配置类,Spring Boot会将其添加到Spring容器中。然而,WebServiceAutoConfiguration需要Spring Boot自动发现,并且不能依赖常规的组件扫描,因为它的包路径不同于应用程序的扫描路径。LPApplication所在的扫描路径是"com.lp.test",而WebServiceAutoConfiguration"com.lp.springboot"中。

        Spring Boot是如何实现自动发现并将自动配置类添加到Spring容器中的呢?关键在于Spring BootSPI机制。Spring Boot实现了自己的Service Provider Interface (SPI)机制,通过spring.factories文件来自动配置应用程序所需的类。通过这个机制,Spring Boot能够自动发现WebServiceAutoConfiguration这样的自动配置类,而无需显式配置。

        因此,如果我们希望Spring Boot自动发现自定义的自动配置类,可以通过在spring.factories文件中列出相应的配置类来实现。该文件通常位于META-INF目录中,Spring Boot在启动时会读取它,并将指定的自动配置类添加到Spring容器中。

        这个过程使得Spring Boot能够灵活地扩展和自动配置,而无需开发人员显式指定所有配置类。这种自动化机制大大简化了配置过程,并提供了高度可扩展性。

        那么我们模拟就可以直接用JDK自带的SPI机制。

解析自动配置类

我们只需要在springboot模块的resource文件下创建如下文件

也就是上面的AutoConfiguration接口,然后通过SPI机制和Import机制将WebServerAutoConfiguration配置类型进行导入到spring容器中

public class LPDeferredImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {ServiceLoader<AutoConfiguration> load = ServiceLoader.load(AutoConfiguration.class);List<String> list = new ArrayList<>();for (AutoConfiguration autoConfiguration : load) {list.add(autoConfiguration.getClass().getName());}return list.toArray(new String[0]);}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Import(LPDeferredImportSelector.class)
public @interface LPSpringBootApplication {
}

这样就完成了在从com.lp.springboot.AutoConfiguration文件中获取自动配置类的名称并且进行导入到spring容器中,然后spring容器就可以获取到对应的配置类信息。

然后在test模块中添加jetty的依赖同时保留Tomcat依赖

        <dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.43.v20210629</version></dependency>

然后启动test的启动类

便会进行异常处理。

这样我们就可以在test业务工程进行自定义使用http(servlet)服务器的切换。

结语

我们通过模拟了springboot的主要启动流程:核心注解、核心启动类、创建spring容器、对内嵌Tomcat的启动和注入springDispatcherServlet容器、使用多servlet服务器的切换、条件注解、自动配置类的解析等关键功能节点完成了一个简单版本的Springboot,使用这样的方式让我们对Springboot项目能有个更加深刻的理解。

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

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

相关文章

【GDAL应用】基于rasterstats的矢量数据分区统计栅格值信息

文章目录 1 实现效果2 实现功能3 实现代码 1 实现效果 矢量数据&#xff1a; 栅格数据&#xff1a;只有一个value值&#xff08;像素值或DN值&#xff09;&#xff0c;为1&#xff0c;计算统计时nodata作为0值处理。 输出结果&#xff1a; 2 实现功能 基于单波段的栅格数…

代码随想录刷题打卡day22

1 最大二叉树 使用递归的思路构造二叉树&#xff0c;一般选择前序遍历对树进行构造&#xff0c;需要先构造中间节点&#xff0c;然后递归构造左子树和右子树。 三部曲 确定递归函数的参数和返回值 参数传入的是存放元素的数组&#xff0c;返回该数组构造的二叉树的头结点&…

关于勒索攻击,绝大多数企业存在的三个认知误区

网络空间&#xff0c;有一个挥之不去的“幽灵”&#xff0c;它的名字就叫勒索攻击。 近年来&#xff0c;企业遭受勒索攻击的事件被频频曝光。就在不久前&#xff0c;国家安全部曝光了一起境外黑客组织对我国某高新科技企业实施勒索攻击的案例&#xff0c;该企业的相关信息化系统…

Java云商城系统 云端商城 一站式系统Java源码 云商城自助下单平台 云商城虚拟交易网

内容目录 一、详细介绍二、效果展示2.效果图展示 三、学习资料下载 一、详细介绍 云商城系统&#xff0c;无后门&#xff0c;一站式系统Java源码&#xff0c;心权益商品数量不限数量 系统对接 手动发货 自动发货 兑 换 码 订单监控 商品监控 对象存储 邮箱提醒 加价模板 密价…

【ElementUI -- 优化小技巧系列】 -- el-tree 节点内容过长优化 以及选中默认节点

在使用elementui过程中经常碰到关于样式的问题&#xff0c;我曾经很喜欢通过类名修改css样式来做&#xff0c;其实原生封装的elementui库的样式对于普通开发来说已经足够了&#xff0c;通过类名修改css只会让组件臃肿难以维护&#xff0c;现在真的越来越怕写css&#xff0c;经常…

解决html2canvas生成图片慢的问题

// 主要看那个点击事件就行 <divclass"textBox-right-board-group"v-for"item in screenList":key"item.id"><!-- 获取不同分辨率下的屏幕的展示的文字大小DPI&#xff1a; fontSize: getFontSize(item.resolutionRatio), --><di…

AI智能化逐渐趋于成熟后,预测今后最吃香的开发职业

AI智能化正在成熟的路途中&#xff0c;这中间会有波折&#xff0c;但终有一天会来的&#xff0c;我相信等到了这一天&#xff0c;我们的开发效率和代码质量&#xff0c;将会大大不同&#xff0c;而我们的团队与个人&#xff0c;也会面临着很棒的体验。 那么在AI智能化真正趋于成…

前端开发攻略---使用Sass调整颜色亮度,实现Element组件库同款按钮

目录 1、演示 2、实现原理 3、实现代码 1、演示 2、实现原理 改变颜色亮度的原理是通过调整颜色的 RGB 值中的亮度部分来实现的。在 Sass 中&#xff0c;可以使用颜色函数来操作颜色的 RGB 值&#xff0c;从而实现亮度的调整。 具体来说&#xff0c;亮度调整函数通常会改变颜…

武汉星起航:展望跨境电商新篇章,创新发展助力品牌国际化

随着全球经济一体化的深入发展&#xff0c;跨境电商行业正迎来前所未有的发展机遇。在这个充满机遇的时代&#xff0c;武汉星起航电子商务有限公司以其独特的自营亚马逊跨境电商模式和卖家孵化服务&#xff0c;成为了行业内的一股强劲力量。展望未来&#xff0c;武汉星起航将继…

VLM与基础分割模型的联合使用

最近做的项目里有涉及大模型&#xff0c;里面有一部分的功能是&#xff1a; 将图片输入VLM(视觉语言模型&#xff0c;我使用的是llava)&#xff0c;询问图中最显著的物体&#xff0c;将其给出的答案作为基础分割模型&#xff08;我使用的是Grounded-SAM&#xff09;的text prom…

云原生测试实战-云计算大数据云原生架构容器技术Kubernetes计算机软件工程软件开发

系列文章目录 送书第一期 《用户画像&#xff1a;平台构建与业务实践》 送书活动之抽奖工具的打造 《获取博客评论用户抽取幸运中奖者》 送书第二期 《Spring Cloud Alibaba核心技术与实战案例》 送书第三期 《深入浅出Java虚拟机》 送书第四期 《AI时代项目经理成长之道》 …

Gradle报错Cause: zip END header not found,构建问题解决

问题描述 构建报错&#xff1a;Cause: zip END header not found 解决办法 File>>setting>>Build,Execution,Deployment>>Gradle 选择你本地的Gradke路径 问题解决

2024年数维杯数学建模C题思路

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

拼多多投产比怎么逐步调高

提高拼多多的投产比&#xff08;ROI&#xff09;需要综合考虑多个因素&#xff0c;包括点击量、转化率、客单价以及点击花费。以下是一些有效的方法&#xff1a; 拼多多推广可以使用3an推客。3an推客&#xff08;CPS模式&#xff09;给商家提供的营销工具&#xff0c;由商家自…

8-3 html中的表单标签 select和textarea

跟学b站黑马程序员pink老师&#xff0c;之前发过长篇&#xff0c;太长不好阅读&#xff0c;拆分成短篇 8.4.3 select下拉表单元素 如果在页面中有多个选项让用户选择&#xff0c;并且想要节约页面空间&#xff0c;我们可以用<select>标签来定义下拉列表 1.<select&g…

图片批量处理:批量调整图片色调,简单方法与高级技巧

随着数字摄影和社交媒体的普及&#xff0c;我们每天都接触到大量的图片。为了提升图片的观感和视觉效果&#xff0c;对图片进行色调调整变得至关重要。而对于那些需要处理大量图片的用户来说&#xff0c;批量调整图片色调则是一个能够大大提高工作效率的功能。本文将介绍办公提…

layui 数据表格 自动定位新增行位置

由于数据表格新增行后没有到新增到当前位置 继续增加的需求&#xff1a; 因为自己是新增行后到最后一行的 所以 就定位到最后一行 并且 高亮 高亮颜色浅 可自行更改 整理了一下 可根据 情况 修改 // 初始化滚动条位置变量 let tableScroll {scrollTob: 0,scrollLeft: 0,…

【Node.js工程师养成计划】之使用Node连接MongoDB进行增删改查

一、Node连接MongoDB mongodb npm install mongodb # or ... yarn add mongodbdemo: const { MongoClient } require(mongodb); // or as an es module: // import { MongoClient } from mongodb// Connection URL const url mongodb://localhost:27017; const client ne…

PyQt程序的打包

Qt hello - 专注于Qt的技术分享平台 记录下PyQt程序的打包。 一&#xff0c;安装 pip3 install PyInstaller 二&#xff0c;打包 pyinstaller -w -n app app.py 根据需要选择打包参数&#xff0c;例如&#xff1a;-F表示生成单文件模式&#xff0c;即只有一个可执行文件…

windows驱动开发-内核调度(二)

这篇文档记录剩下的内核调度对象。 信号灯 任何驱动程序都可以使用信号量对象在其驱动程序创建的线程和其他驱动程序例程之间同步操作。 例如&#xff0c;当驱动程序没有未完成的 I/O 请求时&#xff0c;驱动程序专用线程可能会将自身置于等待状态&#xff0c;并且驱动程序的…