手写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;且总价值最大。 输出最大价值。 输入格式 第一行两个整…

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

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

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

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

三步提升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"…

如何在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;或者有补充的…

【Entity Framework】EF中的增删改查

【Entity Framework】EF中的增删改查 文章目录 【Entity Framework】EF中的增删改查一、概述二、DbContext数据上下文三、EntityState五个状态值四、EF添加数据4.1 EF Add方式4.2 EF 通过改变对象的状态为 Added4.3 调用方sql4.4 调用存储过程 五、EF修改数据5.1 不查询数据库&…

项目管理系统在制造业的应用,提高生产效率的秘诀与解决方案

缩短产品交货周期&#xff0c;提高产品交付率是当下很多制造业面临的难题&#xff0c;项目管理系统业务流程自动化&#xff0c;能够显著改善项目效率。接下来我们说一说项目管理系统在制造业的应用&#xff0c;项目管理系统制造业解决方案。 制造业典型的项目背景 随着企业体量…

学习【Redis原理篇】这一篇就够了

目录 1. 数据结构1-1. 动态字符串&#xff08;SDS&#xff09;1-2. intset1-3. Dict 2. 网络模型3. 通信协议4. 内存策略 1. 数据结构 1-1. 动态字符串&#xff08;SDS&#xff09; 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字…

【Java项目】基于SpringBoot的【就业信息管理系统】

在当今这个科技迅猛发展的时代&#xff0c;计算机技术在生活中扮演着至关重要的角色&#xff0c;特别是在信息管理领域。在这样的背景下&#xff0c;学习计算机知识不只是简单地掌握一项技能&#xff0c;更关键的是将所学知识应用于实际&#xff0c;以创新的思维不断简化人们的…

on-my-zsh 命令自动补全插件 zsh-autosuggestions 安装和配置

首先 Oh My Zsh 是什么? Oh My Zsh 是一款社区驱动的命令行工具&#xff0c;正如它的主页上说的&#xff0c;Oh My Zsh 是一种生活方式。它基于 zsh 命令行&#xff0c;提供了主题配置&#xff0c;插件机制&#xff0c;已经内置的便捷操作。给我们一种全新的方式使用命令行。…

热门IT【视频教程】-华为/思科/红帽/oracle

华为认证 网络工程师-入门基础课&#xff1a;华为HCIA认证课程介绍-CSDN博客 网络工程师进阶课&#xff1a;华为HCIP认证课程介绍-CSDN博客 职场进阶&#xff0c;踏上高峰——HCIE-Datacom认证-CSDN博客 华为HCIA试听课程 &#xff1a; 超级实用&#xff0c;华为VRP系统文件…

8.6 循环神经网络的简洁实现

每个步长共用参数 加载数据 虽然 8.5节 对了解循环神经网络的实现方式具有指导意义&#xff0c;但并不方便。 本节将展示如何使用深度学习框架的高级API提供的函数更有效地实现相同的语言模型。 我们仍然从读取时光机器数据集开始。 import torch from torch import nn from…

#include<初见C语言之指针(5)>

目录 一、sizeof和strlen的对比 1. sizeof 2.strlen 二、数组和指针题解析 1. ⼀维数组 1.1数组名理解 2.字符数组 3. ⼆维数组 三、指针运算题解析 总结 一、sizeof和strlen的对比 1. sizeof 我们前面介绍过sizeof是单目操作符 sizeof括号中有表达式&#xff0c;不…