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

实现Tomcat和Jetty的切换

前言

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

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

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

定义WebServer接口并实现

java 复制代码
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实现类中。

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

java 复制代码
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类

java 复制代码
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服务器。

条件注解

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

自动配置类

java 复制代码
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文件,具体路径如图所示:

复制代码
com.ber.springboot.WebServiceAutoConfiguration

增加AutoConfiguration接口类和实现类。

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

java 复制代码
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 复制代码
<?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类就可以啦。

相关推荐
KK溜了溜了2 小时前
JAVA-springboot log日志
java·spring boot·logback
我命由我123453 小时前
Spring Boot 项目集成 Redis 问题:RedisTemplate 多余空格问题
java·开发语言·spring boot·redis·后端·java-ee·intellij-idea
面朝大海,春不暖,花不开3 小时前
Spring Boot消息系统开发指南
java·spring boot·后端
hshpy3 小时前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端
jay神3 小时前
基于Springboot的宠物领养系统
java·spring boot·后端·宠物·软件设计与开发
不知几秋4 小时前
Spring Boot
java·前端·spring boot
howard20055 小时前
5.4.2 Spring Boot整合Redis
spring boot·整合redis
TracyCoder1235 小时前
接口限频算法:漏桶算法、令牌桶算法、滑动窗口算法
spring boot·spring·限流
饮长安千年月5 小时前
JavaSec-SpringBoot框架
java·spring boot·后端·计算机网络·安全·web安全·网络安全
眠修6 小时前
Nginx + Tomcat负载均衡群集
nginx·tomcat·负载均衡