手写模拟SpringBoot核心流程(二):实现Tomcat和Jetty的切换
实现Tomcat和Jetty的切换
前言
上一篇文章我们聊到,SpringBoot中内置了web服务器,包括Tomcat、Jetty,并且实现了SpringBoot启动Tomcat的流程。
那么SpringBoot怎样自动切换成Jetty服务器呢?
接下来我们继续学习如何实现Tomcat和Jetty的自动切换。
定义WebServer接口并实现
package com.ber.springboot; import org.springframework.web.context.WebApplicationContext; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 19:44 * @Version 1.0 */public interface WebServer { void start(WebApplicationContext applicationContext);
}
将BerSpringApplication类中startTomcat写到TomcatWebServer实现类中。
package com.ber.springboot; import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 19:45 * @Version 1.0 */
public class TomcatWebServer implements WebServer{ @Override public void start(WebApplicationContext applicationContext) { System.out.println("启动Tomcat"); Tomcat tomcat = new Tomcat(); Server server = tomcat.getServer(); Service service = server.findService("Tomcat"); Connector connector = new Connector(); connector.setPort(8023); 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(); } }
}
JettyWebServer类同样实现WebServer接口,不过具体启动Jetty代码省略,不在本文探讨范围内。
package com.ber.springboot; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 19:46 * @Version 1.0 */
public class JettyWebServer implements WebServer{ @Override public void start() { System.out.println("启动Jetty"); }
}
修改BerSpringApplication类
package com.ber.springboot; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import java.util.Map; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 14:08 * @Version 1.0 */
public class BerSpringApplication { public static void run(Class clazz) { // 1. 创建Spring 容器 AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(clazz); applicationContext.refresh(); // 2. 获取特定WebServer类型的Bean WebServer webServer = getWebServer(applicationContext); // 3. 调用start方法 webServer.start(applicationContext); } private static WebServer getWebServer(AnnotationConfigWebApplicationContext applicationContext) { // key为beanName, value为Bean对象 Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class); if (webServers.isEmpty()) { throw new NullPointerException(); } if (webServers.size() > 1) { throw new IllegalStateException(); } return webServers.values().stream().findFirst().get(); }
}
在run方法中,获取到特定的web服务器,并通过start方法进行 启动。
getWebServer方法实现判断web服务器,并处理特殊情况——没有web服务器或者出现多个web服务器。
条件注解
package com.ber.springboot; import org.springframework.context.annotation.Conditional; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 20:06 * @Version 1.0 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(BerOnClassConsition.class)
public @interface BerConditionalOnClass { String value() default "";
}
具体步骤为:
- 拿到@BerConditionalOnClass中的value属性
- 类加载器进行加载,加载到了特定的类名,则符合条件;否则不符合条件
package com.ber.springboot; import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Map; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 20:08 * @Version 1.0 */
public class BerOnClassConsition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(BerConditionalOnClass.class.getName()); // 1. 拿到@BerConditionalOnClass中的value属性 String className = (String) annotationAttributes.get("value"); // 2. 类加载器进行加载 try { // 2.1 加载到了特定的类名,则符合条件 true context.getClassLoader().loadClass(className); return true; } catch (ClassNotFoundException e) { // 2.2 加载不到,则不符合条件 false return false; } }
}
自动配置类
package com.ber.springboot; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 20:34 * @Version 1.0 */
@Configuration
public class WebServiceAutoConfiguration implements AutoConfiguration{ @Bean @BerConditionalOnClass("org.apache.catalina.startup.Tomcat") public TomcatWebServer tomcatWebServer() { return new TomcatWebServer(); } @Bean @BerConditionalOnClass("org.eclipse.jetty.server.Server") public JettyWebServer jettyWebServer() { return new JettyWebServer(); }
}
自动配置类在Spring Boot应用程序中起着关键的作用,它们是实现自动化配置的核心组件。
这里定义满足各自条件的Bean,当org.apache.catalina.startup.Tomcat类存在时,TomcatWebServer的Bean才存在,另一个亦是如此。
当spring容器存在Bean时,就可以通过BerSpringApplication类getWebServer方法中的applicationContext.getBeansOfType(WebServer.class)获取到,并由此可以进行对web服务器是否存在的判断。
SPI机制发现WebServiceAutoConfiguration
刚刚我们定义了自动配置类,但运行user模块的Userapplication启动类时,发现是无法发现WebServiceAutoConfiguration配置类的。
这是因为我们传入了Userapplication作为配置类,扫描路径为Userapplication所在的包路径,是无法扫描到WebServiceAutoConfiguration类的。
在springboot中实现了类似SPI的思想,就是项目中的spring.factories文件,提供了一种可插拔的扩展机制,使开发人员能够轻松地定制应用程序的行为和功能,同时又能保持主应用程序的稳定性。
这里我们可以借助JDK的SPI机制实现发现WebServiceAutoConfiguration类。
在springboot模块中增加resources/META-INF/services/com.ber.springboot.AutoConfiguration文件,具体路径如图所示:

com.ber.springboot.WebServiceAutoConfiguration
增加AutoConfiguration接口类和实现类。
package com.ber.springboot; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 21:08 * @Version 1.0 */
public interface AutoConfiguration {
}
package com.ber.springboot; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /** * @Author 鳄鱼儿 * @Description TODO * @date 2023/8/19 20:34 * @Version 1.0 */
@Configuration
public class WebServiceAutoConfiguration implements AutoConfiguration{ @Bean @BerConditionalOnClass("org.apache.catalina.startup.Tomcat") public TomcatWebServer tomcatWebServer() { return new TomcatWebServer(); } @Bean @BerConditionalOnClass("org.eclipse.jetty.server.Server") public JettyWebServer jettyWebServer() { return new JettyWebServer(); }
}
并在注解类@BerSpringBootApplication上增加@Import(BerImportSelect.class)注解,BerImportSelect类从com.ber.springboot.AutoConfiguration文件中获取类名,然后添加到spring容器。
package com.ber.springboot; import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata; import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader; /** * @Author 鳄鱼儿 * @Description * @date 2023/8/19 21:15 * @Version 1.0 */
public class BerImportSelect implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { /** 使用Java的ServiceLoader机制加载实现了AutoConfiguration接口的类 * AutoConfiguration是Spring Boot中用于自动配置的接口 * AutoConfiguration的实现类通常包含了一些配置信息,帮助应用程序在不需要显式配置的情况下自动完成一些功能 */ ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class); List<String> list = new ArrayList<>(); for (AutoConfiguration autoConfiguration : serviceLoader) { list.add(autoConfiguration.getClass().getName()); } // 返回包含所有加载的AutoConfiguration实现类名的字符串数组 return list.toArray(new String[0]); }
}
添加Jetty依赖
修改user模块的依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>simulate-springboot</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>user</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.example</groupId> <artifactId>springboot</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <exclusion> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>9.4.43.v20210629</version> </dependency> </dependencies> </project>
这里需要排除tomcat依赖,因为springboot中已经添加了tomcat的依赖。
不排除就会出来既有tomcat又有Jetty,就会出现IllegalStateException异常。
到此运行user模块的UserApplication类就可以啦。
相关文章:
手写模拟SpringBoot核心流程(二):实现Tomcat和Jetty的切换
实现Tomcat和Jetty的切换 前言 上一篇文章我们聊到,SpringBoot中内置了web服务器,包括Tomcat、Jetty,并且实现了SpringBoot启动Tomcat的流程。 那么SpringBoot怎样自动切换成Jetty服务器呢? 接下来我们继续学习如何实现Tomcat…...
Python土力学与基础工程计算.PDF-土的三项组成
5.3 Python求解 Python 求解代码如下: 1. # 定义已知参数 2. G_s 2.7 # 比重 3. w 0.2 # 含水量 4. e 0.6 # 孔隙比 5. gamma_w 9.81 # 水的重度 6. 7. # 根据公式计算饱和度 8. S_r G_s * w / e 9. print("饱和度为", S_r) 10. 11.…...
危化安全生产信息化平台在煤化领域的应用
一、背景介绍 煤化工行业是一个集煤炭、石油、化工等多种产业于一体的综合性行业,其特点是工艺流程复杂、设备繁多、安全隐患大。近年来,随着煤化工行业的快速发展,安全生产问题日益凸显。为了有效提高危化安全生产水平,某煤化工…...
Linux(CentOS)运维脚本工具集合
使用说明 备份指定目录 # 备份指定目录文件到指定目录,备份文件名称为:备份目录最后一层目录"_"日期.tar.gz # 第一个参数:backdir 第二参数:备份文件保存目录 第三个参数:备份目录/文件 sh script.sh backdir /root/…...
【Java alibabahutool】JSON、Map、实体对象间的相互转换
首先要知道三者的互转关系,可以先将JSON理解成是String类型。这篇博文主要是记录阿里巴巴的JSONObject的两个方法。toJSONString()以及parseObject()方法。顺便巩固Map与实体对象的转换技巧。 引入依赖 <!-- 阿里巴巴 JSON转换 以下二选一即可 没有去细研究两者…...
按软件开发阶段的角度划分:单元测试、集成测试、系统测试、验收测试
1.单元测试(Unit Testing) 单元测试,又称模块测试。对软件的组成单位进行测试,其目的是检验软件基本组成单位的正确性。测试的对象是软件里测试的最小单位:模块。 测试阶段:编码后或者编码前(…...
【python】Leetcode(primer-dict-list)
文章目录 260. 只出现一次的数字 III(字典 / 位运算)136. 只出现一次的数字(字典)137. 只出现一次的数字 II(字典)169. 求众数(字典)229. 求众数 II(字典)200…...
网络安全(黑客)入门
想自学网络安全(黑客技术)首先你得了解什么是网络安全!什么是黑客! 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全…...
在CSS中,盒模型中的padding、border、margin是什么意思?
在CSS中,盒模型(Box Model)是用来描述和布局HTML元素的基本概念。它将每个HTML元素看作是一个矩形的盒子,这个盒子包括了内容(content)、内边距(padding)、边框(border&a…...
有线耳机插入电脑没声音
有线耳机插入电脑没声音 首先确保耳机和电脑都没问题,那就有可能是声音输出设备设置错误 右击任务栏的声音图标-打开声音设置-选择输出设备。...
【面试 反思】Retrofit源码与设计 7 连问
前言 在实际项目中往往是使用Retrofit来做网络请求工作。Retrofit采用RESTful风格,本质上只是对OkHttp进行封装,今天我们根据几个问题来进一步学习一下Retrofit的源码与设计思想。 1. 使用方法 直接看一下官方介绍的使用方法。 public final class S…...
flutter 雷达图
通过CustomPainter自定义雷达图 效果如下 主要代码 import package:flutter/material.dart; import dart:math; import dash_painter.dart; import model/charts_model.dart;class RadarChart extends StatelessWidget {final List<ChartModel> list;final double maxV…...
机器学习之损失函数(Loss Function)
损失函数(Loss Function)是机器学习和深度学习中的关键概念,它用于衡量模型的预测与实际目标之间的差异或误差。损失函数的选择对于模型的训练和性能评估至关重要,不同的任务和问题通常需要不同的损失函数。 以下是一些常见的损失…...
创邻科技张晨:图数据库,激活数据要素的新基建
“数据经济时代,数据要素产业链的各细分领域均蕴含机遇,图技术作为网络协同和数据智能的底层发动机,将深度掘金数字中国价值潜能”。 8月22日,在2023中国(南京)国际软件产品和信息服务交易博览会的信息技术…...
使用端口映射实现Spring Boot服务端接口的公网远程调试:详细配置与步骤解析
文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…...
stm32之点亮LED
今天,记录一下stm32如何点亮一个LED,程序本身十分简单,但主要是学习编程的格式。 led.h #ifndef _led_H #define _led_H#include "system.h"/* LED时钟端口、引脚定义 */ #define LED1_PORT GPIOB #define LED1_PIN GPIO_Pin_5 #d…...
SA8000认证的难点及注意事项
SA8000认证是什么? SA8000即“社会责任标准”,是Social Accountability 8000的英文简称,由社会责任国际组织(SAI)制定与执行,是全球首个道德规范国际标准。自1997年问世以来,它创建了一个衡量社会责任的共同语言&#…...
Java可视化物联网智慧工地SaaS平台源码:人脸识别考勤
基于微服务JavaSpring Cloud Vue UniApp MySql实现的智慧工地云平台源码 智慧工地是指利用云计算、大数据、物联网、移动互联网、人工智能等技术手段,为建筑施工现场提供智能硬件及物联网平台的解决方案,以实现建筑工地的实时化、可视化、多元化、智慧化…...
告别数字化系统“物理叠加”,华为云推动智慧门店价值跃迁
文|智能相对论 作者|叶远风 有大屏幕滚动播放广告; 有人脸识别系统让消费者自助结账; 有订单管理系统综合分析一段时间内总体经营情况; 有全门店监控直连总部机房; …… 以搭载数字化系统的硬件设备为表面特征的智慧门店&a…...
k8s 常用命令(四)
12、删除pod中的nginx服务及service [rootmaster ~]# kubectl delete deployment nginx -n kube-public [rootmaster ~]# kubectl delete svc -n kube-public nginx-service 13、查看endpoint的信息 [rootmaster ~]# kubectl get endpoints 14、修改/更新(镜像、…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
