手写模拟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类就可以啦。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

idea连接linux远程docker详细教程操作

1&#xff1a;修改docker配置文件docker.service vi /usr/lib/systemd/system/docker.service2&#xff1a;找到 ExecStart&#xff0c;在最后面添加 -H tcp://0.0.0.0:2375 # for containers run by docker ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/…

基于Jenkins构建生产CICD环境(第三篇)

目录 基于Jenkins自动打包并部署docker环境 1、安装docker-ce 2、阿里云镜像加速器 3、构建tomcat 基础镜像 4、构建一个Maven项目 基于Jenkins自动化部署PHP环境 基于rsync部署 基于Jenkins自动打包并部署docker环境 1、安装docker-ce 在192.168.2.123 机器上&#x…

无涯教程-PHP - File 函数

文件系统功能用于访问和操纵文件系统&#xff0c;PHP为您提供了操纵文件的所有功能。 运行时配置 这些功能的行为受php.ini中的设置影响。 NameDefaultChangeableChangelogallow_url_fopen"1"PHP_INI_ALLPHP_INI_ALL in PHP < 4.3.4. PHP_INI_SYSTEM in PHP &l…

PyTorch训练简单的生成对抗网络GAN

文章目录 原理代码结果参考 原理 同时训练两个网络&#xff1a;辨别器Discriminator 和 生成器Generator Generator是 造假者&#xff0c;用来生成假数据。 Discriminator 是警察&#xff0c;尽可能的分辨出来哪些是造假的&#xff0c;哪些是真实的数据。 目的&#xff1a;使…

机器学习笔记之优化算法(十六)梯度下降法在强凸函数上的收敛性证明

机器学习笔记之优化算法——梯度下降法在强凸函数上的收敛性证明 引言回顾&#xff1a;凸函数与强凸函数梯度下降法&#xff1a;凸函数上的收敛性分析 关于白老爹定理的一些新的认识梯度下降法在强凸函数上的收敛性收敛性定理介绍结论分析证明过程 引言 本节将介绍&#xff1a…

基于旗鱼算法优化的BP神经网络(预测应用) - 附代码

基于旗鱼算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于旗鱼算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.旗鱼优化BP神经网络2.1 BP神经网络参数设置2.2 旗鱼算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

消息队列前世今生 字节跳动 Kafka #创作活动

消息队列前世今生 1.1 案例一&#xff1a; 系统崩溃 首先大家跟着我想象一下下面的这个的场景&#xff0c; 看到新出的游戏机&#xff0c;太贵了买不起&#xff0c;这个时候你突然想到&#xff0c;今天抖音直播搞活动&#xff0c;打开抖音搜索&#xff0c;找到直播间以后&am…

Node基础--Node简介以及安装教程

1.Node简介 Node.js发布于2009年5月&#xff0c;由Ryan Dahl开发&#xff0c;是一个基于Chrome V8引擎的JavaScript运行环境&#xff0c;使用了一个事件驱动、非阻塞式I/O模型&#xff0c;让JavaScript 运行在服务端的开发平台&#xff0c;它让JavaScript成为与PHP、Python、Pe…

RabbitMq-3入门案例

rabbitmq入门 1.生产者&#xff08;服务提供方&#xff09; //依赖<dependencies> <!-- rabbitmq客户端依赖--><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.8.0<…

Docker打包JDK20镜像

文章目录 Docker 打包 JDK 20镜像步骤1.下载 jdk20 压缩包2.编写 dockerfile3.打包4.验证5.创建并启动容器6.检查 Docker 打包 JDK 20镜像 步骤 1.下载 jdk20 压缩包 https://www.oracle.com/java/technologies/downloads/ 2.编写 dockerfile #1.指定基础镜像&#xff0c;并…

Linux: 使用scp命令复制文件夹报 not a regular file 错误解决

使用scp 命令复制文件夹报 not a regular file 错误解决 解决办法&#xff1a; 加入参数 -r&#xff1a; 递归复制整个目录 scp命令参数详解

【C++小项目】实现一个日期计算器

目录 Ⅰ. 引入 Ⅱ. 列轮廓 Ⅲ. 功能的实现 构造函数 Print 判断是否相等 | ! ➡️: ➡️!: 判断大小 > | > | < | < ➡️>&#xff1a; ➡️<&#xff1a; ➡️>&#xff1a; ➡️<&#xff1a; 加减天数 | | - | - ➡️&#xff1a;…