手写模拟SpringBoot核心流程(二):实现Tomcat和Jetty的切换

news2024/11/26 1:41:30

实现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类就可以啦。

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

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

相关文章

网络编程面试笔试题

一、OSI 7层模型&#xff0c;TCP/IP 4层模型 5层模型。 以及每一层的功能&#xff08;重点&#xff1a;第三层 第四层&#xff09; 答&#xff1a; 7层模型&#xff08;①物理层&#xff1a;二进制比特流传输&#xff0c;②数据链路层&#xff1a;相邻结点的可靠传输&#xf…

Ctfshow web入门 命令执行RCE篇 web29-web77 与 web118-web124 详细题解 持续更新中

Ctfshow 命令执行 web29 pregmatch是正则匹配函数&#xff0c;匹配是否包含flag&#xff0c;if(!preg_match("/flag/i", $c))&#xff0c;/i忽略大小写 可以利用system来间接执行系统命令 flag采用f*绕过&#xff0c;或者mv fl?g.php 1.txt修改文件名&#xff0c…

Docker搭建LNMP----(超详细)

目录 ​编辑 一、项目环境 1.1 所有安装包下载&#xff1a; 1.3 服务器环境 1.4任务需求 二、Ngin 2.1、建立工作目录 2.2 编写 Dockerfile 脚本 2.3准备 nginx.conf 配置文件 2.4生成镜像 2.5创建自定义网络 2.6启动镜像容器 2.7验证 nginx、 三、Mysql 3.1建立…

生产环境下的终极指南:使用 Docker 部署 Nacos 集群和 MySQL

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

WPF入门到精通:1.新建项目及项目结构

WPF&#xff08;Windows Presentation Foundation&#xff09;是一种用于创建 Windows 应用程序的技术&#xff0c;它可以通过 XAML&#xff08;Extensible Application Markup Language&#xff09;和 C# 或其他 .NET 语言来实现。WPF 提供了许多强大的 UI 控件和样式&#xf…

OpenCV实例(九)基于深度学习的运动目标检测(二)YOLOv2概述

基于深度学习的运动目标检测&#xff08;二&#xff09;YOLOv2&YOLOv3概述 1.YOLOv2概述2.YOLOv3概述2.1 新的基础网络结构&#xff1a;2.2 采用多尺度预测机制。2.3 使用简单的逻辑回归进行分类 1.YOLOv2概述 对YOLO存在的不足&#xff0c;业界又推出了YOLOv2。YOLOv2主要…

17.HPA和rancher

文章目录 HPA部署 metrics-server部署HPA Rancher部署Rancherrancher添加集群仪表盘创建 namespace仪表盘创建 Deployments仪表盘创建 service 总结 HPA HPA&#xff08;Horizontal Pod Autoscaling&#xff09;Pod 水平自动伸缩&#xff0c;Kubernetes 有一个 HPA 的资源&…

openai多模态大模型:clip详解及实战

引言 CLIP全称Constrastive Language-Image Pre-training&#xff0c;是OpenAI推出的采用对比学习的文本-图像预训练模型。CLIP惊艳之处在于架构非常简洁且效果好到难以置信&#xff0c;在zero-shot文本-图像检索&#xff0c;zero-shot图像分类&#xff0c;文本→图像生成任务…

728. 自除数 题解

题目描述&#xff1a;728. 自除数 - 力扣&#xff08;LeetCode&#xff09; 自除数 是指可以被它包含的每一位数整除的数。 例如&#xff0c;128 是一个 自除数 &#xff0c;因为 128 % 1 0&#xff0c;128 % 2 0&#xff0c;128 % 8 0。 自除数 不允许包含 0 。 给定两个整…

VCS与Verdi联仿,简要万能工程模板,持续更新中...

VCS与Verdi联仿&#xff0c;简要工程模板&#xff0c;持续更新中… 文章目录 VCS与Verdi联仿&#xff0c;简要工程模板&#xff0c;持续更新中...背景编写工程模块使用工程模板仿真结果工程下载地址 背景 学习verilog&#xff0c;故用vcs来编译verilog&#xff0c;用verdi来查…

一篇文章了解编译类成员定义

文章目录 一篇文章了解编译类成员定义 %Dictionary.CompiledClass - 编译类定义表简介索引示例表结构 %Dictionary.CompiledConstraint - 编译约束表简介索引示例表结构 %Dictionary.CompiledConstraintMethod - 编译约束表简介索引示例表结构 %Dictionary.CompiledForeignKey …

通过docker-Compose快速搭建OwnCloud网盘

目录 docker-compose文件信息 nginx文件信息 证书生成 查看文件有哪些&#xff01;&#xff01;&#xff01; 在 .yml 文件目录运行 查看容器情况并访问网页 当然&#xff0c;以下是一个使用 MySQL 5.7、Nginx 和 ownCloud 的完整 Docker Compose 示例&#xff0c;同时启用…

《Zookeeper》源码分析(十六)之 Leader是如何运行的

目录 Leader创建Leader实例lead() Leader Leader选举结束后&#xff0c;成为leader的服务器开始进行leader的工作&#xff0c;过程如下&#xff1a; 从源码中看出&#xff0c;第一步先创建Leader实例&#xff0c;第二步调用Leader.lead()方法&#xff0c;Leader的所有工作都…

【ElasticSearch】一键安装ElasticSearch与Kibana以及解决遇到的问题

目录 一、安装ES 二、安装Kibana 三、遇到的问题 一、安装ES 按顺序复制即可 docker network create es-net # 创建网络 docker pull images:7.12.1 # 拉取镜像 mkdir -p /root/es/data # 创建数据卷 mkdir -p /root/es/plugins # 创建数据卷 chmod 777 /root/es/** # 设置权…

Ubuntu20.04搭建OpenGL环境(glfw+glad)

Ubuntu20.04搭建OpenGL环境(glfwglad) Linux环境搭建 本文在VMware安装Ubuntu20.04桌面版的环境下搭建OpenGL&#xff0c;按照本文搭建完成后可以执行LearnOpenGL网站上的demo。 关于VMware可自行到VMware Workstation Pro | CN下载 关于Ubuntu20.04桌面版可自行到官网或In…

MNIST手写数字数据集+7000张图片下载

MNIST手写数字图像数据集是一个经典的用于图像分类任务的数据集&#xff0c;其中包含了大量的手写数字图像样本 数据集点击下载&#xff1a; MNIST手写数字数据集7000张图片.rar

【MySQL】好好学习一下InnoDB中的页

文章目录 一. 前言二. 从宏观层面看页三. 页的基本内容3.1 页的数据结构3.2 用户空间内的数据行结构3.3 页目录 四. 问题集4.1 索引 和 数据页 有什么区别4.2 页的大小是什么决定的4.3 页的大小对哪些情况有影响4.4 一般情况下说的链表有哪几个4.5 如果页的空间满了怎么办4.6 如…

微服务中间件--Ribbon负载均衡

Ribbon负载均衡 a.Ribbon负载均衡原理b.Ribbon负载均衡策略 (IRule)c.Ribbon的饥饿加载 a.Ribbon负载均衡原理 1.发起请求http://userservice/user/1&#xff0c;Ribbon拦截该请求 2.Ribbon通过EurekaServer拉取userservice 3.EurekaServer返回服务列表给Ribbon做负载均衡 …

线性代数的学习和整理9(草稿-----未完成)

3.3 特征值和特征向量是什么&#xff1f; 直接说现在&#xff1a;特征向量这个块往哪个方向进行了拉伸&#xff0c;各个方向拉伸了几倍。这也让人很容易理解为什么&#xff0c;行列式的值就是特征值的乘积。 特征向量也代表了一些良好的性质&#xff0c;即这些线在线性变换后…

[LeetCode111双周赛LeetCode359周赛] DP双指针

参考灵神和闫总的讲解和代码&#xff1a; https://www.bilibili.com/video/BV1rP411s7Z5 https://space.bilibili.com/206214 7006. 销售利润最大化 https://leetcode.cn/problems/maximize-the-profit-as-the-salesman/ Solution 动态规划 哈希表 首先按照 end 的顺序分…