SpringBoot--------TomCat端口號(三)

轉載:https://blog.csdn.net/zknxx/article/details/53433592

轉載:https://blog.csdn.net/weixin_33795093/article/details/89941428

 

一.  SpringBoot修改默認端口號

有時候我們可能需要啓動不止一個SpringBoot,而SpringBoot默認的端口號是8080,所以這時候我們就需要修改SpringBoot的默認端口了。修改SpringBoot的默認端口有兩種方式。下面就分別說一下這兩種方式。

修改application.properties
第一種方式我們只需要在application.properties中加這樣的一句話就可以了:server.port=8004。爲什麼這種方式可以實現修改SpringBoot的默認端口呢?因爲在SpringBoot中有這樣的一個類:ServerProperties。我們可以大致看一下這個類:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
        implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
 
    /**
     * Server HTTP port.
     */
    private Integer port;


在這個類裏有一個@ConfigurationProperties註解,這個註解會讀取SpringBoot的默認配置文件application.properties的值注入到bean裏。這裏定義了一個server的前綴和一個port字段,所以在SpringBoot啓動的時候會從application.properties讀取到server.port的值。我們接着往下看一下:

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        if (getPort() != null) {
            container.setPort(getPort());
        }


這裏有一個customize的方法,這個方法裏會給SpringBoot設置讀取到的端口號。
實現EmbeddedServletContainerCustomizer
我們在上面看到了端口號是在customize這個方法中設置的,而這個方法是在EmbeddedServletContainerCustomizer這個接口中的,所以我們可以實現這個接口,來更改SpringBoot的默認端口號。具體代碼如下:

@RestController
@EnableAutoConfiguration
@ComponentScan
public class FirstExample implements EmbeddedServletContainerCustomizer {
 
    @RequestMapping("/first.do")
    String home() {
        return "Hello World!世界你好!O(∩_∩)O哈哈~!!!我不是太很好!";
    }
 
    public static void main(String[] args) {
 
        SpringApplication.run(FirstExample.class, args);
    }
 
    @Override
    public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
 
        configurableEmbeddedServletContainer.setPort(8003);
    }
}


然後你在啓動SpringBoot的時候,發現端口號被改成了8003.
使用命令行參數
如果你只是想在啓動的時候修改一次端口號的話,可以用命令行參數來修改端口號。配置如下:java -jar 打包之後的SpringBoot.jar  --server.port=8000
使用虛擬機參數
你同樣也可以把修改端口號的配置放到JVM參數裏。配置如下:-Dserver.port=8009。 這樣啓動的端口號就被修改爲8009了。

二.  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如下所示:

C:\Users\72908\Desktop\1591082395(1).png

ServerProperties實現了EnvironmentAware接口,說明它可以獲取Environment中的屬性值,它也實現了Ordered接口,這個這裏先記着,我們在後面再說,它也實現了EmbeddedServletContainerCustomizer接口,我們在上面說的第二種修改TomCat端口號的方式就是實現EmbeddedServletContainerCustomizer接口,並注入爲Spring的Bean,而ServerProperties就實現了這個接口。但是這裏還有一個問題,在這個類上沒有添加Component註解(或者是相同作用的註解)。但是我們在SpringBoot中還發現了這樣的一個類:ServerPropertiesAutoConfiguration。從名字我們可以猜出這個類應該是爲ServerProperties提供自動配置的一個類,這個類也確實是這樣的一個作用,關於SpringBoot的自動配置功能比較複雜,我們這裏先不展開,有這方面疑問的童鞋可以在下面留言。我們去ServerPropertiesAutoConfiguration這個類中看一下這個類的代碼:

//Configuration相當於<beans>標籤
//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<String, String> contextParameters = new HashMap<String, String>();

以及它的內部類,分別用來做和TomCat設置相關的內容、Jetty設置相關的內容、Undertow設置相關的內容以及Session設置相關的內容。關於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<EmbeddedServletContainerCustomizer> getCustomizers() {
        if (this.customizers == null) {
            // Look up does not include the parent context
            //從當前的Spring容器中獲取EmbeddedServletContainerCustomizer 類型的Bean
            this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
                    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;
    }

------------------------------------------------ 我是低調的分隔線  ----------------------------------------------------

640?wx_fmt=gif 面向對象,面向卿;不負代碼,不負心... ...  640?wx_fmt=gif

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章