轉載: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如下所示:
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;
}
------------------------------------------------ 我是低調的分隔線 ----------------------------------------------------
面向對象,面向卿;不負代碼,不負心... ...