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

系列文章目录

手写SpringBoot(一)之简易版SpringBoot
手写SpringBoot(二)之动态切换Servlet容器
手写SpringBoot(三)之自动配置
手写SpringBoot(四)之bean动态加载
手写SpringBoot(五)之整合AOP

手写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只需提供接口。


手写SpringBoot(一)之简易版SpringBoot
手写SpringBoot(二)之动态切换Servlet容器
手写SpringBoot(三)之自动配置
手写SpringBoot(四)之bean动态加载
手写SpringBoot(五)之整合AOP

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

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

相关文章

衍生品交易概况

场内 场外 交易台架构 报价、交易、研究、程序个股、股指Flow、Exotic线性、非线性 对冲管理 管理风险敞口 做好情景分析 尊重市场选择 及时调整策略 理解头寸 善于学习 场外衍生品交易员的一天 盘前 回顾市场、决定今天总体方向处理隔夜敞口 盘中 处理客户询价…

C语言中入门到实战————动态内存管理

目录 前言 一、为什么要有动态内存分配 二、 malloc和free 2.1 malloc 2.2 free 三、calloc和realloc 3.1 calloc 3.2 realloc 四. 常见的动态内存的错误 4.1 对NULL指针的解引用操作 4.2 对动态开辟空间的越界访问 4.3 对非动态开辟内存使用free释放 4.4 使…

【算法】01背包问题(代码+详解+练习题)

题目&#xff1a; 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输出最大价值。 输入格式 第一行两个整…

面经(七)南京先维

总体评价&#xff1a; 哈哈&#xff0c;感觉又没了前半段发挥还挺好&#xff0c;但到了后面&#xff0c;听录屏的声音&#xff0c;越来越底气不足&#xff0c;回答也是极尽含糊虽然问的问题不难&#xff0c;都是基础知识&#xff0c;但关键是&#xff0c;只会单纯地用&#xf…

视频素材库有哪些网站?八大平台视频素材库创作推荐

视频创作的小达人们&#xff0c;是不是经常在想&#xff0c;视频素材库有哪些网站能提供高质量的素材呢&#xff1f;别担心&#xff0c;今天我要为你们揭秘八个超棒的视频素材网站&#xff0c;让你的视频制作更加轻松在创作的路上如鱼得水&#xff01; 蛙学网&#xff1a;海量…

【BlossomConfig】配置中心Core核心功能代码的编写

文章目录 Core什么是配置中心&#xff1f;以及如何实现一个配置中心&#xff1f;SpringBoot如何实现配置的管控&#xff1f;SpringCloud项目是如何对bootstrap配置文件进行加载的&#xff1f;Nacos是如何实现配置文件的读取加载的&#xff1f;开发配置中心前必须了解的前置知识…

关于 MySQL 优化(详解)

文章目录 关于 MySQL 优化一、硬件方面的优化1、关于 CPU2、关于内存3、关于磁盘 二、MySQL 配置文件1、 default-time-zone8:002、interactive_timeout 1203、wait_timeout 1204、open_files_limit 102405、group_concat_max_len 1024006、usermysql7、character-set-serv…

深入探索Yarn:安装与使用指南

Yarn 是一个由 Facebook 开发的 JavaScript 包管理器&#xff0c;旨在提供更快、更可靠的包管理体验。它与 npm 类似&#xff0c;但在某些方面更加高效和可靠。本文将介绍如何安装 Yarn&#xff0c;并展示如何使用它来管理 JavaScript 项目的依赖。 1. 安装 Yarn Yarn 可以通…

Web软件测试面试总结

整理下最近遇到的Web软件测试会被问到的问题&#xff0c;来自测下你掌握得怎么样&#xff1f; 一、编程语言相关 1、字符串反转 s "I am Donny" reversed_s s[::-1] print(reversed_s)这将输出 “ynnoD ma I”。 2、基础数据类型 基本数据类型&#xff08;Prim…

三步提升IEDA下载速度——修改IDEA中镜像地址

找到IDEA的本地安装地址 D:\tool\IntelliJ IDEA 2022.2.4\plugins\maven\lib\maven3\conf 搜索阿里云maven仓库 复制https://developer.aliyun.com/mvn/guide中红框部分代码 这里也是一样的&#xff1a; <mirror><id>aliyunmaven</id><mirrorOf>*&…

【c++初阶】类与对象(下)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

ESP32学习---ESP-NOW

ESP32学习---ESP-NOW 基于Arduino IDE环境获取mac地址单播通讯一对多通讯多对一通讯多对多通讯模块1代码模块2模块3 广播通讯 基于ESP-IDF框架 乐鑫编程指南中关于ESP-NOW的介绍&#xff1a;https://docs.espressif.com/projects/esp-idf/zh_CN/v5.2.1/esp32/api-reference/net…

7.1 Mysql shell 定时备份

直接上脚本----linu 定时任务执行 #!/bin/bash# 配置信息 DB_USER"your_username" # 数据库用户名 DB_PASSWORD"your_password" # 数据库密码 DB_NAME"your_database_name" # 要备份的数据库名 BACKUP_DIR"/path/to/backup/directory"…

深度学习训练中的种子设置

文章目录 深度学习训练中的种子设置1. 为什么需要设置随机种子2. 随机种子的设置及使用 深度学习训练中的种子设置 1. 为什么需要设置随机种子 在神经网络训练过程中&#xff0c;经常会通过随机的方式对一些数据进行初始化&#xff1a; 1、随机权重&#xff0c;网络有些部分…

C语言--实现判断输入数字是几位数的函数

用起来很方便&#xff0c;代码如下 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int digit (int x){int count 0;do {count;x x / 10;} while (x > 0); return count;} int main() {int x 0;scanf("%d", &x);int number digit(x);printf…

如何在Ubuntu系统部署Z-blog博客结合cpolar实现无公网IP访问本地网站

文章目录 1. 前言2. Z-blog网站搭建2.1 XAMPP环境设置2.2 Z-blog安装2.3 Z-blog网页测试2.4 Cpolar安装和注册 3. 本地网页发布3.1. Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 想要成为一个合格的技术宅或程序员&#xff0c;自己搭建网站制作网页是绕…

CrossOver玩游戏会损害电脑吗 CrossOver玩游戏会卡吗 Mac玩游戏 crossover24免费激活

CrossOver是一款可以在macOS上运行Windows应用程序的软件&#xff0c;它利用了Wine技术&#xff0c;无需安装虚拟机或双系统&#xff0c;可以直接在苹果系统下运行Windows游戏。那么&#xff0c;使用CrossOver玩游戏会损害电脑吗&#xff1f;CrossOver玩游戏会卡吗&#xff1f;…

LLaMA-Factory参数的解答

打开LLaMA-Factory的web页面会有一堆参数 &#xff0c;但不知道怎么选&#xff0c;选哪个&#xff0c;这个文章详细解读一下&#xff0c;每个参数到底是什么含义这是个人写的参数解读&#xff0c;我并非该领域的人如果那个大佬看到有参数不对请反馈一下&#xff0c;或者有补充的…

【软件测试】教程及案例

软件测试是软件开发过程中的关键环节&#xff0c;它确保软件产品符合设计要求并且能够在各种条件下正常运行。以下是关于软件测试的教程和案例的详细介绍&#xff1a; ### 软件测试基础 软件测试的目的是发现和修复软件中的缺陷&#xff0c;以确保软件的质量。测试可以在软件…

面试题(六)

目录 101.RocketMQ的事务消息是如何实现的 102.为什么RocketMQ不使⽤Zookeeper作为注册中⼼呢&#xff1f; 103.RocketMQ的实现原理 104.RocketMQ为什么速度快 105.消息队列如何保证消息可靠传输 106.消息队列有哪些作⽤ 107.死信队列是什么&#xff1f;延时队列是什么&a…