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

实现Tomcat和Jetty的切换

前言

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

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

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

定义WebServer接口并实现

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实现类中。

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

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类

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服务器。

条件注解

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

自动配置类

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

手写模拟SpringBoot核心流程(二):实现Tomcat和Jetty的切换_第1张图片

com.ber.springboot.WebServiceAutoConfiguration

增加AutoConfiguration接口类和实现类。

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

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模块的依赖如下:

  
<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-springbootartifactId>  
        <groupId>org.examplegroupId>  
        <version>1.0-SNAPSHOTversion>  
    parent>  
    <modelVersion>4.0.0modelVersion>  
  
    <artifactId>userartifactId>  
  
    <properties>  
        <maven.compiler.source>8maven.compiler.source>  
        <maven.compiler.target>8maven.compiler.target>  
    properties>  
  
    <dependencies>  
        <dependency>  
            <groupId>org.examplegroupId>  
            <artifactId>springbootartifactId>  
            <version>1.0-SNAPSHOTversion>  
            <exclusions>  
                <exclusion>  
                    <groupId>org.apache.tomcat.embedgroupId>  
                    <artifactId>tomcat-embed-coreartifactId>  
                exclusion>  
            exclusions>  
        dependency>  
  
        <dependency>  
            <groupId>org.eclipse.jettygroupId>  
            <artifactId>jetty-serverartifactId>  
            <version>9.4.43.v20210629version>  
        dependency>  
    dependencies>  
  
project>

这里需要排除tomcat依赖,因为springboot中已经添加了tomcat的依赖。

不排除就会出来既有tomcat又有Jetty,就会出现IllegalStateException异常。

到此运行user模块的UserApplication类就可以啦。

你可能感兴趣的:(微服务专题,spring,boot,tomcat,jetty)