SpringBoot之浅析TomCat端口号设置 您所在的位置:网站首页 springtoolsuite配置tomcat SpringBoot之浅析TomCat端口号设置

SpringBoot之浅析TomCat端口号设置

2024-06-09 10:48| 来源: 网络整理| 查看: 265

我们在之前的文章中说过怎么去修改TomCat的端口号(SpringBoot修改默认端口号),我们在这篇文章中简单的说一下SpringBoot是怎么实现修改TomCat端口号的。修改TomCat的端口号大概可以分为这样的两类吧,一种是用配置项的方式,另一种是用程序实现的方式。配置项包含:设置命令行参数、系统参数、虚拟机参数、SpringBoot默认的application.properties(或者是application.yml等类似的方式)。用程序实现的方式,则需要实现EmbeddedServletContainerCustomizer接口,并将此实现类注入为Spring的Bean。我们先说配置项的方式。通常我们用配置项的方式来修改TomCat端口号的时候,需要进行这样的配置(或类似的方式):

server.port=8081

看到这样的一个配置项再结合我们自己在使用ConfigurationProperties的时候所进行的设置,我们可以推断一下应该会存在一个这样的JavaBean,在这个JavaBean上使用了ConfigurationProperties注解,并且它的prefix的值为server。既然有了一个这样的推想,那么我们就要去证明这个推想。在SpringBoot中也确实存在了我们所推想的这样的一个JavaBean:ServerProperties。ServerProperties这个类的UML如下所示:ServerPropertiesServerProperties实现了EnvironmentAware接口,说明它可以获取Environment中的属性值,它也实现了Ordered接口,这个这里先记着,我们在后面再说,它也实现了EmbeddedServletContainerCustomizer接口,我们在上面说的第二种修改TomCat端口号的方式就是实现EmbeddedServletContainerCustomizer接口,并注入为Spring的Bean,而ServerProperties就实现了这个接口。但是这里还有一个问题,在这个类上没有添加Component注解(或者是相同作用的注解)。但是我们在SpringBoot中还发现了这样的一个类:ServerPropertiesAutoConfiguration。从名字我们可以猜出这个类应该是为ServerProperties提供自动配置的一个类,这个类也确实是这样的一个作用,关于SpringBoot的自动配置功能比较复杂,我们这里先不展开,有这方面疑问的童鞋可以在下面留言。我们去ServerPropertiesAutoConfiguration这个类中看一下这个类的代码:

//Configuration相当于标签 //EnableConfigurationProperties使ConfigurationProperties注解生效,并将EnableConfigurationProperties这个注解中执行的类注入为Spring的Bean //ConditionalOnWebApplication 必须是在web开发环境中 @Configuration @EnableConfigurationProperties @ConditionalOnWebApplication public class ServerPropertiesAutoConfiguration { //如果在当前容器中 不存在ServerProperties类型的Bean,则创建ServerProperties Bean @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public ServerProperties serverProperties() { return new ServerProperties(); } //这个Bean也实现了 EmbeddedServletContainerCustomizer 接口,它同时还实现了ApplicationContextAware 接口,说明在这个类中可以获取到Spring容器的应用上下文 这个类的作用是检测在Spring 容器中是否有多于一个ServerProperties类型的Bean存在 如果是则抛出异常 @Bean public DuplicateServerPropertiesDetector duplicateServerPropertiesDetector() { return new DuplicateServerPropertiesDetector(); } private static class DuplicateServerPropertiesDetector implements EmbeddedServletContainerCustomizer, Ordered, ApplicationContextAware { private ApplicationContext applicationContext; @Override public int getOrder() { return 0; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void customize(ConfigurableEmbeddedServletContainer container) { // ServerProperties handles customization, this just checks we only have // a single bean 主要作用就是检测当前容器中是否存在多于一个ServerProperties类型的Bean存在 String[] serverPropertiesBeans = this.applicationContext .getBeanNamesForType(ServerProperties.class); Assert.state(serverPropertiesBeans.length == 1, "Multiple ServerProperties beans registered " + StringUtils .arrayToCommaDelimitedString(serverPropertiesBeans)); } } }

通过上面的分析,我们看到了在哪里将ServerProperties包装为Spring容器的Bean的。下面我们来简单的说一下ServerProperties这个类中都为我们提供了什么东西:

/** * 启动端口号 * Server HTTP port. */ private Integer port; /** * ServletConetxt上下文路径 * Context path of the application. */ private String contextPath; /** * DispatcherServlet 主要的 servlet Mapping * Path of the main dispatcher servlet. */ private String servletPath = "/"; /** * ServletContext参数 * ServletContext parameters. */ private final Map contextParameters = new HashMap();

以及它的内部类,分别用来做和TomCat设置相关的内容、Jetty设置相关的内容、Undertow设置相关的内容以及Session设置相关的内容。关于ServerProperties中的属性值的设置请参考之前的文章,这里就不再多说了。ServerProperties在ServerProperties中最重要的一个方法是customize方法,这个方法是用来设置容器相关的内容的。

//这里的ConfigurableEmbeddedServletContainer 请看这个类中的内容EmbeddedServletContainerAutoConfiguration,看完你应该就会明白它是什么了 public void customize(ConfigurableEmbeddedServletContainer container) { //端口号 if (getPort() != null) { container.setPort(getPort()); } //IP地址 if (getAddress() != null) { container.setAddress(getAddress()); } //ContextPath if (getContextPath() != null) { container.setContextPath(getContextPath()); } if (getDisplayName() != null) { container.setDisplayName(getDisplayName()); } //Session超时时间 if (getSession().getTimeout() != null) { container.setSessionTimeout(getSession().getTimeout()); } container.setPersistSession(getSession().isPersistent()); container.setSessionStoreDir(getSession().getStoreDir()); //SSL if (getSsl() != null) { container.setSsl(getSsl()); } //JspServlet if (getJspServlet() != null) { container.setJspServlet(getJspServlet()); } if (getCompression() != null) { container.setCompression(getCompression()); } container.setServerHeader(getServerHeader()); //如果是TomCat服务器 if (container instanceof TomcatEmbeddedServletContainerFactory) { getTomcat().customizeTomcat(this, (TomcatEmbeddedServletContainerFactory) container); } //如果是Jetty服务器 if (container instanceof JettyEmbeddedServletContainerFactory) { getJetty().customizeJetty(this, (JettyEmbeddedServletContainerFactory) container); } //如果是Undertow服务器 if (container instanceof UndertowEmbeddedServletContainerFactory) { getUndertow().customizeUndertow(this, (UndertowEmbeddedServletContainerFactory) container); } container.addInitializers(new SessionConfiguringInitializer(this.session)); //ServletContext 参数 container.addInitializers(new InitParameterConfiguringServletContextInitializer( getContextParameters())); }

现在的关键问题是customize这个方法是在什么时候被调用的呢?通过翻开它的调用链,我们在SpringBoot中发现了这样的一个类:EmbeddedServletContainerCustomizerBeanPostProcessor在这个类中有这样的一个方法,

//这个方法 如果你对Spring中的生命周期熟悉的话,那么你看到这个方法的时候一定不会陌生,同时这个类应该是实现了BeanPostProcessor 那么现在还存在的一个问题是,只有这个类是一个Spring中的Bean的时候,它才会被调用到,那么这个类是什么时候被注入到Spring的容器中的呢 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //如果是ConfigurableEmbeddedServletContainer类型 才会继续下面的动作 //所以这里是对我们在应用程序中所使用的应用服务器进行设置 if (bean instanceof ConfigurableEmbeddedServletContainer) { postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); } return bean; } private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { //getCustomizers()获取容器中的EmbeddedServletContainerCustomizer的实现类,ServerProperties当然算是一个,我们上面提到的DuplicateServerPropertiesDetector 也是一个 for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { //调用EmbeddedServletContainerCustomizer的实现类中的customize方法 customizer.customize(bean); } }

如果你对Spring中的生命周期熟悉的话,那么你看到postProcessBeforeInitialization这个方法的时候一定不会陌生,首先应该想到它应该是实现了BeanPostProcessor这个接口。那么现在还存在的一个问题是,只有这个类是一个Spring中的Bean的时候,它才会被调用到,那么这个类是什么时候被注入到Spring的容器中的呢 ?答案就在EmbeddedServletContainerAutoConfiguration这个类中。这个类的作用是自动配置嵌入式的Servlet容器。在这个类上用了这样的一个注解:

@Import(BeanPostProcessorsRegistrar.class)

Import这个注解在实现SpringBoot的自动配置功能的时候起到了非常重要的作用!Import这个注解中的value所指定的Class可以分为这样的三类:一类是实现了ImportSelector接口,一类是实现了ImportBeanDefinitionRegistrar接口,不属于前面说的这两种的就是第三种,具体的可以看一下这个方法org.springframework.context.annotation.ConfigurationClassParser#processImports。而上面所提到的BeanPostProcessorsRegistrar这个类就是实现了ImportBeanDefinitionRegistrar这个接口的。我们看一下这个类中的内容(EmbeddedServletContainerAutoConfiguration的内部类):

//这个类实现了ImportBeanDefinitionRegistrar接口,同时也实现了BeanFactoryAware 接口 public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } } //注入Bean定义 注意这里的Bean 都是用注解的方法注入的bean 如标注Component注解的Bean //这个方法的调用链先不介绍的 @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } //注入EmbeddedServletContainerCustomizerBeanPostProcessor registerSyntheticBeanIfMissing(registry, "embeddedServletContainerCustomizerBeanPostProcessor", EmbeddedServletContainerCustomizerBeanPostProcessor.class); //注入ErrorPageRegistrarBeanPostProcessor registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class beanClass) { //如果容器中不存在指定类型的Bean定义 if (ObjectUtils.isEmpty( this.beanFactory.getBeanNamesForType(beanClass, true, false))) { //创一个RootBean定义 RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); //合成的Bean 不是应用自己创建的基础建设角色的Bean beanDefinition.setSynthetic(true); //注入Spring容器中 registry.registerBeanDefinition(name, beanDefinition); } } }

到现在为止,关于SpringBoot设置TomCat启动端口号的简单分析就算是结束了。但是这里还有一个问题,如果我们既用配置项的形式设置了TomCat的端口号,同时又自定义了一个实现了EmbeddedServletContainerCustomizer接口的Bean,并且没有Order相关的设置,那么最终生效的会是哪个配置呢?答案是实现了EmbeddedServletContainerCustomizer接口的Spring Bean。在org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor#postProcessBeforeInitialization(org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer)中的方法的内容如下:

private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { //getCustomizers() 从当前的Spirng容器中获取所有EmbeddedServletContainerCustomizer 类型的Bean //getCustomizers() 中的Bean是进行过排序之后的 所以这里Order值大的会覆盖Order值小的设置 for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { customizer.customize(bean); } } private Collection getCustomizers() { if (this.customizers == null) { // Look up does not include the parent context //从当前的Spring容器中获取EmbeddedServletContainerCustomizer 类型的Bean this.customizers = new ArrayList( this.beanFactory .getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false) .values()); //将获取到的Bean进行排序 排序是根据Order的值进行排序的 如果你的Bean没有进行过任何关于Order值的设置的话,那么你的Bean将位于最后的位置了 Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有