Spring Boot动态切换Web服务器实践:基于配置文件实现Tomcat/Jetty/Undertow无缝切换

原创
03/18 10:57
阅读数 1.8K

文章首发公众号『风象南』

使用springboot开发项目过程有可能会遇到需要更换web服务器的需求,springboot默认内置的web服务器为tomcat,现在搜到的文章都是通过排除法进行实现的,以切换为jetty为例,也就是下面这样

排除法

<dependencies>
        <!--引入启动器依赖 里面就有默认的tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--排除tomcat-->
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--引入Jetty-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    </dependencies>

通过排除默认的tomcat, 然后再加入新的jetty依赖,这样做虽然能实现,但是每次切换需要重新打包部署,有没有一种方式可以实现通过配置文件进行动态切换,而不用重新进行打包部署。

答案是可以的。

动态切换实现方案

经过分析源码,并验证,最终实现了动态切换效果,整体实现思路为

  1. 首先排除springboot默认加载web服务器的配置类 ServletWebServerFactoryAutoConfiguration
  2. 在配置文件自定义配置用于切换web服务器的属性,如 server.type
  3. 分别定义各个web服务器的自定义ServletWebServerFactory,并结合ConditionalOnProperty通过上面的server.type属性值进行动态控制该工厂类Bean的加载
  4. 经过以上过程,就可以实现web服务器的动态控制

Demo示例

工程结构

POM定义

<?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>

    <groupId>io.web</groupId>
    <artifactId>web-server-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
            <version>2.7.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <version>2.7.8</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

application.yaml

server:
  port: 9090
  type: undertow # 控制切换

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

Tomcat自定义配置

package io.web.embed.tomcat;

import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

[@Configuration](https://my.oschina.net/pointdance)
@ConditionalOnProperty(name = "server.type", havingValue = "tomcat")
public class TomcatConfiguration {

	@Autowired
	private Environment environment;

	[@Bean](https://my.oschina.net/bean)
	TomcatServletWebServerFactory tomcatServletWebServerFactory(){
		TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory();
		webServerFactory.addErrorPages(new ErrorPage("/error"));
		webServerFactory.addConnectorCustomizers(new TomcatConnectorCustomizer() {

			public void customize(Connector conn) {
				int port = environment.getProperty("server.port",Integer.class,8080);
				conn.setPort(port);

				// todo 其他自定义配置
			}
		});

		return webServerFactory;
	}

}

Jetty自定义配置

package io.web.embed.jetty;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

[@Configuration](https://my.oschina.net/pointdance)
@ConditionalOnProperty(name = "server.type", havingValue = "jetty")
public class JettyConfiguration {

	@Autowired
	private Environment environment;

	@Bean
	ServletWebServerFactory jettyServletWebServerFactory(){
		JettyServletWebServerFactory jettyServletWebServerFactory = new JettyServletWebServerFactory();
		jettyServletWebServerFactory.addErrorPages(new ErrorPage("/error"));
		jettyServletWebServerFactory.addServerCustomizers(new JettyServerCustomizer() {

			public void customize(Server server) {
                ServerConnector httpConnector = new ServerConnector(server);

				int port = environment.getProperty("server.port",Integer.class,8080);
				httpConnector.setPort(port);
				// todo 其他自定义配置

				server.setConnectors(new Connector[] {httpConnector});
			}
		});
		return jettyServletWebServerFactory;
	}

}

Undertow自定义配置

package io.web.embed.undertow;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
@ConditionalOnProperty(name = "server.type", havingValue = "undertow")
public class UndertowConfiguration {

	@Autowired
	private Environment environment;

	@Bean
	UndertowServletWebServerFactory undertowServletWebServerFactory(){
		int port = environment.getProperty("server.port",Integer.class,8080);
		UndertowServletWebServerFactory webServerFactory = new UndertowServletWebServerFactory(port);
		webServerFactory.addErrorPages(new ErrorPage("/error"));

		// todo 其他自定义配置
		return webServerFactory;
	}

}

测试接口

package io.web.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @Autowired
    private Environment environment;

    @RequestMapping("/")
    public String hello(){
        return "hello " + environment.getProperty("server.type");
    }

}

演示效果

修改配置文件 server.type 为 undertow, 启动服务器

服务已切换为 undertow

打开浏览器访问,查看切换成功。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
4 收藏
0
分享
返回顶部
顶部