前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写模拟SpringBoot核心流程(二):实现Tomcat和Jetty的切换

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

作者头像
鳄鱼儿
发布2024-05-21 15:39:59
1650
发布2024-05-21 15:39:59
举报
文章被收录于专栏:鳄鱼儿的技术分享

实现Tomcat和Jetty的切换

前言

上一篇文章我们聊到,SpringBoot中内置了web服务器,包括Tomcat、Jetty,并且实现了SpringBoot启动Tomcat的流程。

那么SpringBoot怎样自动切换成Jetty服务器呢?

接下来我们继续学习如何实现Tomcat和Jetty的自动切换。

定义WebServer接口并实现

代码语言:javascript
复制
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实现类中。

代码语言:javascript
复制
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代码省略,不在本文探讨范围内。

代码语言:javascript
复制
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类

代码语言:javascript
复制
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服务器。

条件注解

代码语言:javascript
复制
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. 类加载器进行加载,加载到了特定的类名,则符合条件;否则不符合条件
代码语言:javascript
复制
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;  
        }  
    }  
}

自动配置类

代码语言:javascript
复制
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
JDK的SPI.png
代码语言:javascript
复制
com.ber.springboot.WebServiceAutoConfiguration

增加AutoConfiguration接口类和实现类。

代码语言:javascript
复制
package com.ber.springboot;  
  
/**  
 * @Author 鳄鱼儿  
 * @Description TODO  
 * @date 2023/8/19 21:08  
 * @Version 1.0  
 */  
public interface AutoConfiguration {  
}
代码语言:javascript
复制
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容器。

代码语言:javascript
复制
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模块的依赖如下:

代码语言:javascript
复制
<?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类就可以啦。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现Tomcat和Jetty的切换
    • 前言
      • 定义WebServer接口并实现
        • 修改BerSpringApplication类
          • 条件注解
            • 自动配置类
              • SPI机制发现WebServiceAutoConfiguration
                • 添加Jetty依赖
                相关产品与服务
                云服务器
                云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档