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

news2024/12/26 10:38:17

系列文章目录

手写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{
    @Override
    public void start(WebApplicationContext webApplicationContext) {

    }
}
package cn.axj.springboot.my.web.container;

public class JettyWebServer implements WebServer{
    @Override
    public 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 {

    @Override
    public 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) {
            //没有找到该类,则返回false
            return 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{
    @Override
    public 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.coloradmin.cn/o/1552205.html

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

相关文章

面向图像分类的视觉Transformer

一种面向对象分类的视觉Transformer&#xff0c;即ViT。该模型避免了卷积带来的归纳偏置&#xff0c;弥补了卷积神经网络在超长周期建模的不足。 1、DeiT&#xff08;data-efficient image transformer&#xff09;采用了知识蒸馏策略&#xff08;将大型模型的复杂知识&#xf…

工厂数据分析系统用这个开源库准没错

ScottPlot是一款简单易用、高度定制、性能卓越的.NET绘图库&#xff0c;支持跨平台操作。除提供标准图表类型外&#xff0c;还支持交互式操作&#xff0c;呈现生动的数据展示。在工厂数字化系统中&#xff0c;可用于生产数据可视化、设备监测和质量控制。无论用于科学研究、数据…

蓝桥杯 2022 省A 选数异或

一种比较无脑暴力点的方法&#xff0c;时间复杂度是(nm)。 (注意的优先级比^高&#xff0c;记得加括号(a[i]^a[j])x&#xff09; #include <iostream> #include <vector> #include <bits/stdc.h> // 包含一些 C 标准库中未包含的特定实现的函数的头文件 usi…

ThreadLocal和Synchronized的区别

目录 背景过程ThreadLocal什么是ThreadLocal&#xff1f;既然都是保证线程访问的安全性&#xff0c;那么和Synchronized区别是什么呢&#xff1f;ThreadLocal的使用TheadLocal使用场景原理高并发场景下ThreadLocal会造成内存泄漏吗&#xff1f;什么原因导致&#xff1f;如何避免…

JavaScript 权威指南第七版(GPT 重译)(四)

第九章&#xff1a;类 JavaScript 对象在第六章中有所涉及。该章将每个对象视为一组独特的属性&#xff0c;与其他对象不同。然而&#xff0c;通常有必要定义一种共享某些属性的对象类。类的成员或实例具有自己的属性来保存或定义它们的状态&#xff0c;但它们还具有定义其行为…

蓝桥杯省三保底代码——数显+按键功能实现

目录 前言 一、为什么能保底省三 二、数显模块的实现 1.数码管显示​编辑 1&#xff09;断码表 2&#xff09;位选 3&#xff09;段选 4&#xff09;扫描 2.菜单 三、按键功能的实现 1.按键扫描 2.菜单切换 四、完整代码演示 五、结语 前言 上一期介绍全家桶时&…

C语言分支循环语句详解

分支和循环语句是什么 在我们写程序的时候&#xff0c;总会遇到想一直循环执行某种语句的时候&#xff0c;这时候我们就要使用一种语句叫循环语句&#xff0c;或者带一些判断条件的语句&#xff0c;在C语言中提供了像这些的循环语句和分支语句 if else 语句 这是一种判断语句…

Model-Free Q-Learning for the Tracking Problem of Linear Discrete-Time Systems

Model-Free Q-Learning for the Tracking Problem of Linear Discrete-Time Systems&#xff0c;2024&#xff0c; Chun Li , Jinliang Ding , Senior Member, IEEE, Frank L. Lewis , Life Fellow, IEEE, and Tianyou Chai , Life Fellow, IEEE 对完全未知动力学的线性离散时…

远程桌面连接弹出“出现身份验证错误,要求的函数不受支持”解决办法

windows远程桌面连接,出现如图所示“出现身份验证错误&#xff0c;要求的函数不受支持”错误&#xff0c;无法连接。 解决办法&#xff1a; 打开本地组策略编辑器&#xff0c;按winr组合键输入gpedit.msc命令打开。 选择“计算机配置”--“管理模板”--“系统”--“凭据分配”…

OpenHarmony实战开发-Web组件的使用

介绍 本篇Codelab使用ArkTS语言实现一个简单的免登录过程&#xff0c;向大家介绍基本的cookie管理操作。主要包含以下功能&#xff1a; 获取指定url对应的cookie的值。设置cookie。清除所有cookie。免登录访问账户中心。 原理说明 本应用旨在说明Web组件中cookie的管理操作。…

【正点原子FreeRTOS学习笔记】————(7)任务调度

这里写目录标题 一、开启任务调度器&#xff08;熟悉&#xff09;二、启动第一个任务&#xff08;熟悉&#xff09;2.1&#xff0c;prvStartFirstTask () /* 开启第一个任务 */2.2&#xff0c;vPortSVCHandler () /* SVC中断服务函数 */ 三、任务切换&#xff08;掌握&#xff…

Deconstructing Denoising Diffusion Models for Self-Supervised Learning解读(超详细)

论文题目&#xff1a;Deconstructing Denoising Diffusion Models for Self-Supervised Learning 原文链接&#xff1a;https://arxiv.org/html/2401.14404v1 本文是对何凯明老师的新作进行的详细解读&#xff0c;其中穿插了一些思考&#xff0c;将从以下四个方面对这篇工作进…

波奇学Linux:http协议

2 个简单预备知识 https://www.baidu.com/ -域名-字符串-域名解析--ip地址 http请求和响应 格式画出来&#xff0c;两个工具见一见 https协议绑定端口号443 http协议绑定端口号 80 url 统一资源定位符 所有网络的资源都可以用唯一的一个字符串标识&#xff0c;并且可以获取…

八种顺序读写函数的介绍(fput/getc;fput/gets;fscanf,fprintf;fwrite,fread)

一&#xff1a;读写的含义的解释&#xff1a; 读&#xff08;读出&#xff09;&#xff1a;即从文件里面读出数据----------->和scanf从键盘里面读出数据类似 写&#xff08;写入&#xff09;&#xff1a;即把数据写入文件里面----------->和printf把数据写入到屏幕上类…

13.Java能干什么?以及Java的三大平台

文章目录 一、JavaSE二、JavaME三、JavaEE JAVA从95年以来&#xff0c;已经问世了20多年了&#xff0c;可能比部分同学的年龄还大。 Java到底能干嘛呢&#xff0c;此时就需要讲到Java的三大平台&#xff0c;其实也就是它的三个分类&#xff1a;JavaSE、JavaME、JavaEE。 一、Ja…

PDFgear:一款免费的PDF编辑、格式转化软件

日常办公中&#xff0c;很多朋友都会接触到PDF文件。把文件转化成PDF是保留文件格式、防范别人修改常用的方法。但是很多人会为PDF文件的生成、压缩、编辑和格式转化而头疼&#xff0c;还有人为了能把PDF转化成Word还购买了不少付费的软件。 为了解决大家这个痛点&#xff0c;…

ES6 学习(三)-- es特性

文章目录 1. Symbol1.1 使用Symbol 作为对象属性名1.2 使用Symbol 作为常量 2. Iterator 迭代器2.1 for...of循环2.2 原生默认具备Interator 接口的对象2.3 给对象添加Iterator 迭代器2.4 ... 解构赋值 3. Set 结构3.1 初识 Set3.2 Set 实例属性和方法3.3 遍历3.4 相关面试题 4…

如何着手写一个自己的网站管理客户端

WebHole 项目地址 https://gitee.com/yiyefangzhou24/web-hole 是什么&#xff1f;能干什么&#xff1f; WebHole是一款网站管理软件&#xff0c;类似但不同于菜刀、冰蝎、蚁剑&#xff0c;能通过C/S的工作模式&#xff0c;方便的管理服务器的文件、数据库&#xff0c;并执…

Redis命令介绍

一、redis启动&#xff1a; 本地启动&#xff1a;redis-cli 远程启动&#xff1a;redis-cli -h host -p port -a password Redis 连接命令 1 AUTH password 验证密码是否正确 2 ECHO message 打印字符串 3 PING 查看服务是否运行 4 QUIT 关闭当前连接 5 SELECT index 切换…

VS2022 使用ClaudiaIDE设置自定义图片背景

ClaudiaIDE的下载 第一步&#xff0c;如下图所示&#xff0c;点击&#xff1a;扩展——管理扩展。 第二步&#xff0c;如下图所示&#xff0c;点击&#xff1a;联机——右上角输入ClaudiaIDE搜索——点击下载。 下载后关闭所有VS窗口&#xff0c;然后等待弹出一个安装窗口&…