手写SpringBoot(二)之动态切换Servlet容器

news/2024/5/10 1:19:33

系列文章目录

手写SpringBoot(一)之简易版SpringBoot
手写SpringBoot(二)之动态切换Servlet容器

手写SpringBoot(二)之动态切换Servlet容器

文章目录

  • 系列文章目录
    • 手写SpringBoot(二)之动态切换Servlet容器

本节着重介绍@ConditionOnClass的由来

我们在切换serlvet容器的时候,会将SpringBoot默认的tomcat jar包给排除掉,换上我们需要的jar包,比如jetty。如下图所示

<?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"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.axj</groupId><artifactId>spring-boot-base</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>user-service</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>cn.axj</groupId><artifactId>my-spring-boot</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>

实现思路:

  1. 定义一个WebServer顶层接口
  2. 将tomcat和jetty的实现类加载到容器中,并根据条件判断,动态加载tomcat或者jetty的实现类
  3. 在servlet容器启动前动态获取WebServer,并通过WebServer启动

定义webServer

package cn.axj.springboot.my.web.container;public interface WebServer {void start(WebApplicationContext webApplicationContext);
}

实现WebServer

package cn.axj.springboot.my.web.container;public class TomcatWebServer implements WebServer{@Overridepublic void start(WebApplicationContext webApplicationContext) {}
}
package cn.axj.springboot.my.web.container;public class JettyWebServer implements WebServer{@Overridepublic void start(WebApplicationContext webApplicationContext) {}
}

定义WebServerAutoConfiguration

package cn.axj.springboot.my.config;import cn.axj.springboot.my.annnotation.MyConditionalOnClass;
import cn.axj.springboot.my.web.container.JettyWebServer;
import cn.axj.springboot.my.web.container.TomcatWebServer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class WebServerAutoConfiguration {/*** 根据jar包是否有 org.apache.catalina.startup.Tomcat类来判断是否加载tomcatServer* @return*/@Bean@MyConditionalOnClass("org.apache.catalina.startup.Tomcat")public TomcatWebServer tomcatWebServer() {return new TomcatWebServer();}/*** 根据jar包是否有 org.eclipse.jetty.server.Server类来判断是否加载jettyServer* @return*/@Bean@MyConditionalOnClass("org.eclipse.jetty.server.Server")public JettyWebServer jettyWebServer(){return new JettyWebServer();}
}

如何实现动态加载?

  1. 定义MyConditionalOnClass注解
  2. 利用Spring的@Conditional注解标记
  3. 定义Conditional条件判断类

定义MyConditionalOnClass注解,利用@Conditional注解定义动态加载逻辑

@Conditional源码如下,内部有一个Class对象需要实现Condition接口

public @interface Conditional {Class<? extends Condition>[] value();
}

@Conditional(MyClassCondition.class) 逻辑是通过Condition接口里面的matches方法动态判断

package cn.axj.springboot.my.annnotation;import cn.axj.springboot.my.condition.MyClassCondition;
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;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Conditional(MyClassCondition.class)
public @interface MyConditionalOnClass {String value();
}

MyClassConditional如下

package cn.axj.springboot.my.condition;import cn.axj.springboot.my.annnotation.MyConditionalOnClass;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;import java.util.Map;
import java.util.Objects;/*** 定义一个自定义的条件类* 该类主要用于根据条件动态加载Bean**/
public class MyClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyConditionalOnClass.class.getName());/*** 获取{@link MyConditionalOnClass}注解中的属性值* 例如:@MyConditionalOnClass(value = "com.example.MyBean")* 则可以通过annotationAttributes.get("value")获取到"com.example.MyBean"*/String className = (String) annotationAttributes.get("value");try {Objects.requireNonNull(context.getClassLoader()).loadClass(className);} catch (ClassNotFoundException e) {//没有找到该类,则返回falsereturn false;}return true;}
}

总体实现逻辑,由Spring提供的@Conditional条件注解动态加载bean机制,

  1. 封装@ConditionOnClass注解,并将@Conditional注解组合到该注解上面,@conditionOnClass的核心就是@Condition
  2. 通过定义value属性,来暴力传参,将tomcat或者jetty的核心类名传到Condition接口的matches方法下
  3. 通过Condition的matches方法匹配是否加载该bean

至此已实现在Spring中动态加载WebServer,在MyApplication.run方法中,从Spring容器中获取WebServer对象,并开启WebServer

public static void run(Class<?> clazz,String[] args) {//启动Spring容器AnnotationConfigWebApplicationContext annotationConfigApplicationContext = new AnnotationConfigWebApplicationContext();annotationConfigApplicationContext.register(clazz);annotationConfigApplicationContext.refresh();//启动tomcat容器WebServer webServer = getWebServer(annotationConfigApplicationContext);webServer.start();}private static WebServer getWebServer(AnnotationConfigWebApplicationContext annotationConfigApplicationContext) {Map<String, WebServer> webServerMap = annotationConfigApplicationContext.getBeansOfType(WebServer.class);if(webServerMap.isEmpty()){throw new RuntimeException("web server is null");}if(webServerMap.size() > 1){throw new RuntimeException("找到多个web server,只能有一个WebServer" + webServerMap.values());}return webServerMap.values().stream().findFirst().get();}

至此,项目结构如下图

在这里插入图片描述

WebContainer已废弃

启动user-service模块,抛出异常

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.axj.springboot.my.web.container.WebServer' available

由于Spring容器中不存在WebServer对象,这是为什么?

在 WebServerAutoConfiguration 中定义的WebServer两个对象不会被Spring扫描到,因为在@MySpringBootApplication中配置的@ComponentScan扫描的包路径并不包括my-spring-boot中的路径。所以不会被Spring容器扫描到,自然不会加载到容器中。

解决办法

  1. 在UserApplication中使用@Import(WebServerAutoConfiguration.class)将WebServerAutoConfiguration 配置类加载到Spring的Configuration中。但是这样对于用户来说,不太美好。
  2. @Import(WebServerAutoConfiguration.class)加载到@MySpringbootApplication注解上,这样Spring在扫描该组合注解的时候,会扫描到Import标签,并将WebServerAutoConfiguration配置类解析并加载到容器中。

最后,实现TomcatWebServer和JettyWebServer的start()方法

tomcat

package cn.axj.springboot.my.web.container;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;public class TomcatWebServer implements WebServer{@Overridepublic void start(WebApplicationContext webApplicationContext) {System.out.println("启动TomcatWeb容器");Tomcat tomcat = new Tomcat();Server server = tomcat.getServer();Service service = server.findService("Tomcat");Connector connector = new Connector();connector.setPort(8080);StandardEngine 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);//配置dispatcherServlet,Springmvc专属tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet(webApplicationContext));context.addServletMappingDecoded("/*","dispatcher");try {tomcat.start();} catch (LifecycleException e) {throw new RuntimeException(e);}}
}

jetty

这里先留个坑,这里实现应该不是由SpringBoot去实现。想一想,SpringBoot不可能将所有serlvet容器的jar包都引入,如果不引入,没有这个jar包如何实现?这里应该是由各servlet去适配。所以SpringBoot只需提供接口。


http://www.mrgr.cn/p/66232332

相关文章

在服务器(Ubuntu20.04)安装用户级别的cuda11.8(以及仿照前面教程安装cuda11.3后安装cudnn和pytorch1.9.0)

1、cuda11.8的下载 首先在cuda官网下载我们需要的cuda版本&#xff0c;这里我下载的是cuda11.8&#xff08;我的最高支持cuda12.0&#xff09; 这里我直接使用wget命令下载不了&#xff0c;于是我直接在浏览器输入后面的链接下载到本地&#xff0c;之后再上传至服务器的&am…

阿里云倚天服务器是什么?倚天服务器c8y、g8y和r8y详细介绍

阿里云倚天云服务器CPU采用倚天710处理器&#xff0c;租用倚天服务器c8y、g8y和r8y可以享受优惠价格&#xff0c;阿里云服务器网aliyunfuwuqi.com整理倚天云服务器详细介绍、倚天710处理器性能测评、CIPU架构优势、倚天服务器使用场景及生态支持&#xff1a; 阿里云倚天云服务…

上架难、买量贵?即构 Web 端 1v1 方案全力加速泛娱乐出海

1v1 视频社交以其功能简洁、互动性强、匹配效率高等特点,在陌生人社交玩法中饱受用户欢迎,平台仅需让两个用户通过匹配、选择等形式连通后,用户即可在房内进行 1v1 视频社交,且在社交的同时又极具私密性,满足了用户社交与 dating 的需求,盛行在中东土耳其、印度、南美等一…

SQL server 迁移至GBase 8c操作指南

SQL server 迁移至GBase 8c操作指南迁移数据库SQL server GBase 8c 首先下载依赖包perl 5.0以上版本,kettle linux 环境可以部署在gbase环境上使用手册:1、导出需要迁移数据库及表对象、视图、存储过程、自定义函数 登陆sqlserver management studio 选择数据库—右击--任务…

iOS系统下最佳的3款HTTP抓包工具:Thor、克魔助手和Http Catcher

引言 在苹果手机的iOS系统相对封闭的情况下,抓包工具并不是很常见。大多数人可能对这类工具并不熟悉,除了少数安全专家之外,一般用户很少会接触到这些工具。然而,在某些特定场景下,比如网络调试、安全测试等,抓包工具的作用不可忽视。本文将介绍三款在iOS系统下比较优秀的…

老阳:一文解答现在做Temu跨境电商还能不能赚钱

近年来&#xff0c;跨境电商行业持续火热&#xff0c;Temu作为其中的一员&#xff0c;也吸引了众多创业者和投资者的目光。然而&#xff0c;随着市场的不断饱和和竞争的加剧&#xff0c;很多人开始疑虑&#xff1a;现在做Temu跨境电商还能赚钱吗?一起去看看吧&#xff01; 任何…

AES加密解密算法

一&#xff0c;AES算法概述 AES属于分组加密&#xff0c;算法明文长度固定为128位&#xff08;单位是比特bit&#xff0c;1bit就是1位&#xff0c;128位等于16字节&#xff09; 而密钥长度可以是128、192、256位 当密钥为128位时&#xff0c;需要循环10轮完成加密&#xff0…

BOSHIDA DC电源模块的设计与制造流程

BOSHIDA DC电源模块的设计与制造流程DC电源模块是一种用于将交流电转换为直流电的设备。它广泛应用于各种电子设备中,如电子产品、工业仪器、电视等。下面是DC电源模块的设计与制造流程的简要描述: 1. 需求分析:在设计DC电源模块之前,首先需要进行需求分析。这包括确定输出…

ABC346 A-G 题解

ABC346 A-G题解 A题目AC Code&#xff1a;时间复杂度 B题目时间复杂度AC Code&#xff1a; C题目时间复杂度AC Code&#xff1a; D题目时间复杂度AC Code&#xff1a; E题目时间复杂度AC Code&#xff1a; F题目时间复杂度AC Code&#xff1a; G题目时间复杂度AC Code&#xff…

linux虚拟机没有ip,网卡服务无法启动的解决

最近使用虚拟机做实验,挂起虚拟机后再回复,发现经常无法使用xshell连接。 进入虚拟机后,使用 ifconfig 命令查看网卡状态,发现网卡的ip没有了 [root@host103 ~]# ifconfig ens33 重启网卡,发现报错。查看网卡目录,也就只有这一个网卡文件,也就是不存在其他网卡配置错误导…

如何制定具有挑战性的绩效目标,同时又能激励员工积极投入工作?

在现代企业管理中,绩效目标的设定不仅是评价员工工作成果的依据,更是激励员工积极投入工作的重要手段。然而,如何制定出既具有挑战性又能激励员工的目标,往往成为管理者需要深思熟虑的问题。本文将探讨如何平衡这两点,实现绩效目标的有效设定。 一、明确绩效目标的重要性 …

20240319-图论

图论练习题目 拓扑排序深度优先搜索方法广度优先搜索方法 无向无权图无向有权图有向无权图 利用广度优先搜索算法有向有权图 带排序的广度优先算法/dijkstra最小生成树prims算法Kruskals Algorithm 最小割 min-cut二分图 Bipartite Graph 队列例题1 所有可能的路径例题2 岛屿数…

列举和删除.NET的版本

删除前 dotnet --list-sdksdotnet --list-runtimesdotnet --info从工具的发布页面下载 .NET 卸载工具 仅删除标记为预览版的 .NET SDK (最高预览版除外)。 dotnet-core-uninstall remove --all-previews-but-latest --sdk因为没有符合条件的,所有并未产生删除。

【详细讲解React 快速入门教程】

&#x1f525;博主&#xff1a;程序员不想YY啊&#x1f525; &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f4ab; &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 &#x1f308;希望本文对您有所裨益&#xff0c;如有…

Jenkins磁盘空间批量清理脚本

一、简介 Jenkins如果没有设置保留构建历史数&#xff0c;磁盘会随着使用次数增加而越来越满&#xff0c;于是需要批量清理一下。 二、清理脚本 找到Script Console 输入脚本&#xff0c;并点击执行&#xff0c;需要注意期望删除的构建历史编号&#xff08;可以查看下面的效果…

用Facebook开发客户的7点心得和技巧

一、在Facebook上通过“关键词搜用户&#xff0c;关键词搜公共页面&#xff0c;关键词搜小组”可以找到一些客户 关键词尽量用行业大词&#xff0c;不要加限定修饰词&#xff0c;太多限制的话&#xff0c;搜索结果会很少。 二、通过投Facebook广告 这里我使用的是Fomepay的卡…

Unity网络通信系统设计.md

Unity网络通信系统设计Buffer报文 BufferEntity类作为报文基类的作用包括:封装数据:BufferEntity类可以用来封装网络通信中的数据,方便在网络传输中进行处理和管理。提供数据缓冲区:BufferEntity类通常会包含一个数据缓冲区,用来存储待发送或接收的数据,以便进行网络通信…

CAE科普!电池仿真的必要性

在当前高油价与低排放的双重挑战下,新能源汽车以其动力电池驱动的特性成为了低碳环保、节能减排的必然选择。然而,电池系统性能的好坏直接关系到新能源电动汽车的行驶里程和使用便利性,其中充电时间、效率、能量密度以及体积、材质、安全和质量等因素都是目前亟待突破的技术…

WordPress分页函数function

1、可以通过下面的代码在编辑器上添加一个分页符按钮。 2、将下面的代码添加到当前主题functions.php即可。 3、代码如下&#xff1a; function mce_page_break($mce_buttons) { $pos array_search(wp_more, $mce_buttons, true); if ($pos ! false) { $buttons …

支持MacOS苹果操作系统的网卡你用过吗?

Marvell AQC113以太网控制器支持苹果操作系统(MacOS),进一步扩展搭载了AQC113设备的应用领域。 众所周知,苹果操作系统应用生态完善,是业内备受瞩目的巨头级操作系统,其应用领域覆盖了游戏、社交、娱乐、工具,甚至NAS存储、工作站、家用PC及其他嵌入式应用等。 Marvell …