透过源码分析,配置spring boot工程同时支持https和http协议请求

前言

spring boot工程需要同时支持http请求和https请求,spring boot 2.x的官方文档解释的很清晰,这里是地址:Configure SSL,上面也有github的示例,也可以参考配置多个连接器这个示例,以硬编码的一种方式来配置https连接器,地址:Enable Multiple Connectors with Tomcat

不过我用的是spring boot 1.x的版本,这里的代码方案是不支持的。因为有些类,我在spring boot 1.x的版本是没有找到的,官方文档我也没找到1.x版本的,所以只能通过去看下源码找下有没有突破口。

源码分析

首先是查找spring boot的servlet容器,在它的自动配置包(autoconfigure)下,org.springframework.boot.autoconfigure.web在这个路径下,找到了一个类:EmbeddedServletContainerAutoConfiguration, 下面是几行关键的源码:

public class EmbeddedServletContainerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}

	/**
	 * Nested configuration if Jetty is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
			WebAppContext.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
			return new JettyEmbeddedServletContainerFactory();
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
			return new UndertowEmbeddedServletContainerFactory();
		}

	}
//...
}

根据源码,可以看到是配置哪个servlet容器的工厂bean,默认是tomcat的,因为最下面2个条件不满足,通过IDEA很清楚就看到了,如下:

这几个类在类路径下是不存在的,除非显式引入相关依赖。

所以,我们只看第一个tomcat的servlet容器工厂bean。

代码中,配置bean的时候,只是很简单new 了一个TomcatEmbeddedServletContainerFacotry的实例,并没有其它额外配置。然后看下TomcatEmbeddedServletContainerFacotry的源码,类上的注释说明这个工厂用来创建tomcat的内嵌的servlet容器。

看下这个类的方法:

方法比较多,分析了下之后,定位到了上面红色标记的方法`addAdditionalTomcatConnectors`方法,看下源码及注释:

	/**
	 * Add {@link Connector}s in addition to the default connector, e.g. for SSL or AJP
	 * @param connectors the connectors to add
	 */
	public void addAdditionalTomcatConnectors(Connector... connectors) {
		Assert.notNull(connectors, "Connectors must not be null");
		this.additionalTomcatConnectors.addAll(Arrays.asList(connectors));
	}

可以用来增加一些额外的连接器,比如SSL 或者AJP。

所以我们只要自定义TomcatEmbeddedServletContainerFacotry这个bean在构造的设置增加一个额外的https连接器即可。这里不用担心会与spring boot这个冲突,上面的代码有这个条件:

所以,只要我们自定义的这个优先级高,最先被解析加载,spring boot在解析它这个自动配置的bean定义的时候,发现已经有这个bean了,就不会再配置它的默认的TomcatEmbeddedServletContainerFacotry这个bean了。

实现

因为application.properties不支持同时配置Http和https两种协议,只能配置一个,另一个必须通过编码的方式,所以我在实现的时候,想到通过编码方式配置https的复杂性(2.x版本虽然配置方式不支持1.x版本,但是给了一个建议,就是通过application.properties配置https,编码的方式配置http),所以我就采用了这个建议,在application.properties中配置https,如下:

server.port=8443
server.ssl.key-store=classpath:sample.jks
server.ssl.key-store-password=secret
server.ssl.key-password=password

这个配置文件的sample.jks这个签名证书是我直接从2.x版本的github的示例代码中下载的,也可以通过keytool自己生成,我这里图个省事。

sample.jks的工程类路径(resources目录)下,如果因为工程环境原因,这样写解析不到这个smaple.jks文件,也可以把路径写成绝对文件路径。

然后在启动类里配置TomcatEmbeddedServletContainerFacotry类的bean,如下:

@SpringBootApplication
public class WebApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class, args);
    }

    @Bean
    public TomcatEmbeddedServletContainerFactory embeddedServletContainerFactory() {
        TomcatEmbeddedServletContainerFactory containerFactory = new TomcatEmbeddedServletContainerFactory();
        Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
        connector.setPort(8080);
        containerFactory.addAdditionalTomcatConnectors(connector);
        return containerFactory;
    }
}

启动之后,成功日志如下(有两个端口,一个http,一个https) :

这样一个基本的配置就完了。

其它想法

在最初我是想通过另一处方式配置,不是自定义这个bean,而是在spring 初始化的过程,从spring 容器拿到TomcatEmbeddedServletContainerFacotry这个bean,然后调用addAdditionalTomcatConnectors方法完成。后来分析了它的初始化顺序,发现还是不要采用这种方式了:

原因是这样,如果我想在初始化的时候,拿到这个bean进行一些操作,可以实现spring 提供的一些回调接口来实现,一般是:BeanFactoryPostProcessor、BeanPostProcessor、XXXAware、InitializingBean、SmartInitializingSingleton...等这些个,但是servlet容器的初始化是在onRefresh这个方法内,只有BeanFactoryPostProcessor的执行时机早于它。但是实在是不建议在BeanFactoryPostProcessor类的postProcessBeanFactory方法内做这个获取bean的操作,很容易导致某些bean的提前初始化,稍为复杂点的工程,这都是很容易出一些无法预料的问题的。

因此,思来想去,还是用上面那种方案比较好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不识君的荒漠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值