北海网站开发河南品牌网站建设
- 作者: 五速梦信息网
- 时间: 2026年03月21日 09:59
当前位置: 首页 > news >正文
北海网站开发,河南品牌网站建设,福州做网站设计,教育机构的网站怎么做目录 前言阅读对象阅读导航前置知识笔记正文一、工程项目准备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;启动… 目录 前言阅读对象阅读导航前置知识笔记正文一、工程项目准备1.1 新建项目1.1 pom.xml1.2 业务模拟 二、模拟SpringBoot启动好戏开场2.1 启动配置类2.1.1 shen-base-springboot新增2.1.2 shen-example客户端新增启动类 三、run方法的实现3.1 步骤一启动一个Spring容器3.2 步骤二初始化SpringMVC3.3 完整步骤二启动Tomcat容器初始化SpringMVC3.4 试运行 四、升级改造4.1 在shen-base-springboot中新增WebServer接口4.2 getWebServer实现4.2.1 实现一个ShenConditionalOnClass及逻辑ShenOnClassConditional4.2.2 补充getWebServer逻辑 五、模拟SpringBoot自动装配5.1 Java SPI5.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 1shen-base-springboot模块的pom.xml dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion\({spring.version}/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion\){spring.version}/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-beans/artifactIdversion${spring.version}/version/dependencydependencygroupIdjavax.servlet/groupIdartifactIdjavax.servlet-api/artifactIdversion4.0.1/version/dependencydependencygroupIdorg.apache.tomcat.embed/groupIdartifactIdtomcat-embed-core/artifactIdversion9.0.60/version/dependency2shen-example的pom.xml dependencygroupIdorg.example/groupIdartifactIdshen-base-springboot/artifactIdversion1.0-SNAPSHOT/version/dependency1.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-ControllerMVC设计模式。它的主要作用是将请求和响应分开处理使应用程序的逻辑更加清晰提高代码的可维护性和可重用性。 它的运行原理如下
在SpringMVC运行原理中Tomcat会先启动然后加载SpringMVC的核心控制器DispatcherServlet当用户向服务器发送请求时DispatcherServlet将请求交给HandlerMapper做解析HandlerMapper将要访问的Controller返回给DispatcherServletDispatcherServlet将用户的请求发送给指定的Controller做业务处理当业务处理完后SpringMVC将需要传递的数据和跳转的视图名称封装为一个ModelAndView对象将ModelAndView对象发送给DispatcherServletDispatcherServlet从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的依赖就可以了。如下 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactIdversion2.7.0/versionexclusionsexclusiongroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-tomcat/artifactId/exclusion/exclusions/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jetty/artifactIdversion2.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的依赖虽然我偷懒给了空实现但是这一步是必要的 dependencygroupIdorg.eclipse.jetty/groupIdartifactIdjetty-server/artifactIdversion9.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) {MapString, 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;BeanShenConditionalOnClass(TOMCAT_PATH)public TomcatWebServer tomcatWebServer() {return new TomcatWebServer();}BeanShenConditionalOnClass(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对象MapString, 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则是通过自定义的一个叫做SPIService 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机制》
- 上一篇: 北海手机网站制作网站制作自己做
- 下一篇: 北湖区网站建设专家建议未来三年不宜买房
相关文章
-
北海手机网站制作网站制作自己做
北海手机网站制作网站制作自己做
- 技术栈
- 2026年03月21日
-
北海市住房和城乡建设局网站销售技巧
北海市住房和城乡建设局网站销售技巧
- 技术栈
- 2026年03月21日
-
北海市建设局网站哪个网站可以做投资回测
北海市建设局网站哪个网站可以做投资回测
- 技术栈
- 2026年03月21日
-
北湖区网站建设专家建议未来三年不宜买房
北湖区网站建设专家建议未来三年不宜买房
- 技术栈
- 2026年03月21日
-
北郊网站建设wordpress插件目录下
北郊网站建设wordpress插件目录下
- 技术栈
- 2026年03月21日
-
北滘做网站合肥seo收费
北滘做网站合肥seo收费
- 技术栈
- 2026年03月21日






