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

手写模拟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 "";  
}

具体步骤为:

  1. 拿到@BerConditionalOnClass中的value属性
  2. 类加载器进行加载,加载到了特定的类名,则符合条件;否则不符合条件
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文件,具体路径如图所示:

JDK的SPI.png

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的切换 前言 上一篇文章我们聊到&#xff0c;SpringBoot中内置了web服务器&#xff0c;包括Tomcat、Jetty&#xff0c;并且实现了SpringBoot启动Tomcat的流程。 那么SpringBoot怎样自动切换成Jetty服务器呢&#xff1f; 接下来我们继续学习如何实现Tomcat…...

Python土力学与基础工程计算.PDF-土的三项组成

5.3 Python求解 Python 求解代码如下&#xff1a; 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.…...

危化安全生产信息化平台在煤化领域的应用

一、背景介绍 煤化工行业是一个集煤炭、石油、化工等多种产业于一体的综合性行业&#xff0c;其特点是工艺流程复杂、设备繁多、安全隐患大。近年来&#xff0c;随着煤化工行业的快速发展&#xff0c;安全生产问题日益凸显。为了有效提高危化安全生产水平&#xff0c;某煤化工…...

Linux(CentOS)运维脚本工具集合

使用说明 备份指定目录 # 备份指定目录文件到指定目录,备份文件名称为&#xff1a;备份目录最后一层目录"_"日期.tar.gz # 第一个参数&#xff1a;backdir 第二参数&#xff1a;备份文件保存目录 第三个参数&#xff1a;备份目录/文件 sh script.sh backdir /root/…...

【Java alibabahutool】JSON、Map、实体对象间的相互转换

首先要知道三者的互转关系&#xff0c;可以先将JSON理解成是String类型。这篇博文主要是记录阿里巴巴的JSONObject的两个方法。toJSONString()以及parseObject()方法。顺便巩固Map与实体对象的转换技巧。 引入依赖 <!-- 阿里巴巴 JSON转换 以下二选一即可 没有去细研究两者…...

按软件开发阶段的角度划分:单元测试、集成测试、系统测试、验收测试

1.单元测试&#xff08;Unit Testing&#xff09; 单元测试&#xff0c;又称模块测试。对软件的组成单位进行测试&#xff0c;其目的是检验软件基本组成单位的正确性。测试的对象是软件里测试的最小单位&#xff1a;模块。 测试阶段&#xff1a;编码后或者编码前&#xff08;…...

【python】Leetcode(primer-dict-list)

文章目录 260. 只出现一次的数字 III&#xff08;字典 / 位运算&#xff09;136. 只出现一次的数字&#xff08;字典&#xff09;137. 只出现一次的数字 II&#xff08;字典&#xff09;169. 求众数&#xff08;字典&#xff09;229. 求众数 II&#xff08;字典&#xff09;200…...

网络安全(黑客)入门

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全…...

在CSS中,盒模型中的padding、border、margin是什么意思?

在CSS中&#xff0c;盒模型&#xff08;Box Model&#xff09;是用来描述和布局HTML元素的基本概念。它将每个HTML元素看作是一个矩形的盒子&#xff0c;这个盒子包括了内容&#xff08;content&#xff09;、内边距&#xff08;padding&#xff09;、边框&#xff08;border&a…...

有线耳机插入电脑没声音

有线耳机插入电脑没声音 首先确保耳机和电脑都没问题&#xff0c;那就有可能是声音输出设备设置错误 右击任务栏的声音图标-打开声音设置-选择输出设备。...

【面试 反思】Retrofit源码与设计 7 连问

前言 在实际项目中往往是使用Retrofit来做网络请求工作。Retrofit采用RESTful风格&#xff0c;本质上只是对OkHttp进行封装&#xff0c;今天我们根据几个问题来进一步学习一下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)

损失函数&#xff08;Loss Function&#xff09;是机器学习和深度学习中的关键概念&#xff0c;它用于衡量模型的预测与实际目标之间的差异或误差。损失函数的选择对于模型的训练和性能评估至关重要&#xff0c;不同的任务和问题通常需要不同的损失函数。 以下是一些常见的损失…...

创邻科技张晨:图数据库,激活数据要素的新基建

“数据经济时代&#xff0c;数据要素产业链的各细分领域均蕴含机遇&#xff0c;图技术作为网络协同和数据智能的底层发动机&#xff0c;将深度掘金数字中国价值潜能”。 8月22日&#xff0c;在2023中国&#xff08;南京&#xff09;国际软件产品和信息服务交易博览会的信息技术…...

使用端口映射实现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

今天&#xff0c;记录一下stm32如何点亮一个LED,程序本身十分简单&#xff0c;但主要是学习编程的格式。 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认证是什么&#xff1f; SA8000即“社会责任标准”&#xff0c;是Social Accountability 8000的英文简称&#xff0c;由社会责任国际组织(SAI)制定与执行&#xff0c;是全球首个道德规范国际标准。自1997年问世以来&#xff0c;它创建了一个衡量社会责任的共同语言&#…...

Java可视化物联网智慧工地SaaS平台源码:人脸识别考勤

基于微服务JavaSpring Cloud Vue UniApp MySql实现的智慧工地云平台源码 智慧工地是指利用云计算、大数据、物联网、移动互联网、人工智能等技术手段&#xff0c;为建筑施工现场提供智能硬件及物联网平台的解决方案&#xff0c;以实现建筑工地的实时化、可视化、多元化、智慧化…...

告别数字化系统“物理叠加”,华为云推动智慧门店价值跃迁

文|智能相对论 作者|叶远风 有大屏幕滚动播放广告&#xff1b; 有人脸识别系统让消费者自助结账&#xff1b; 有订单管理系统综合分析一段时间内总体经营情况&#xff1b; 有全门店监控直连总部机房&#xff1b; …… 以搭载数字化系统的硬件设备为表面特征的智慧门店&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、修改/更新&#xff08;镜像、…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...