天天看點

Spring Boot 建構war 部署到tomcat下無法在Nacos中注冊服務

文章目錄

  • ​​1. 問題​​
  • ​​2. 分析​​
  • ​​3. 解決方案​​

1. 問題

使用Nacos作為注冊中心的Spring Boot項目,以war包形式部署到伺服器上,啟動項目發現該服務無法在Nacos中注冊。

2. 分析

SpringCloud 項目打 war 包部署時,也就是使用外部 Tomcat 部署,其啟動指令、端口等是由外部容器 Tomcat 配置的,而 Nacos 或者其他服務注冊方式需要目前項目的端口号用于注冊微服務。

以 Nacos 為例,其自動注冊微服務的類是 NacosAutoServiceRegistration,我們看一下它的源碼:

public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration<Registration> {

    private NacosRegistration registration;

    @Deprecated
    public void setPort(int port) {
        this.getPort().set(port);
    }

    protected NacosRegistration getRegistration() {
        if (this.registration.getPort() < 0 && this.getPort().get() > 0) {
            this.registration.setPort(this.getPort().get());
        }

        Assert.isTrue(this.registration.getPort() > 0, "service.port has not been set");
        return this.registration;
    }      

我們看到 NacosAutoServiceRegistration 使用了 this.registration.setPort(this.getPort().get()); 來設定端口号。

而端口号是從其父類 AbstractAutoServiceRegistration 中的方法擷取的:

public abstract class AbstractAutoServiceRegistration<R extends Registration>
    implements AutoServiceRegistration, ApplicationContextAware,
    ApplicationListener<WebServerInitializedEvent> {

  private AtomicInteger port = new AtomicInteger(0);


  @Deprecated
  public void bind(WebServerInitializedEvent event) {
    ApplicationContext context = event.getApplicationContext();
    if (context instanceof ConfigurableWebServerApplicationContext) {
      if ("management".equals(((ConfigurableWebServerApplicationContext) context)
          .getServerNamespace())) {
        return;
      }
    }
    this.port.compareAndSet(0, event.getWebServer().getPort());
    this.start();
  }      

這段代碼監聽了内置容器啟動完成事件,監聽擷取到容器端口後,向注冊中心注冊服務。

是以,當使用外部容器時,如此處的 Tomcat 來部署項目,AbstractAutoServiceRegistration 就不能監聽到容器啟動事件了,也就不會嘗試向服務注冊中心注冊目前這個微服務,那麼注冊就失敗了,并且也就沒有異常資訊了。

SpringBoot微服務項目建構war包參考:

​SpringBoot微服務項目建構war包 部署排除指定jar

3. 解決方案

自定義擷取擷取外部容器端口的方法, 然後監聽應用啟動事件,當應用被啟動時,擷取外部容器啟動的端口号,然後将這個 port 設定到 NacosAutoServiceReigistration 中。

package com.gblfy;

import java.lang.management.ManagementFactory;
import java.util.Set;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration;
import com.alibaba.cloud.nacos.registry.NacosRegistration;

import lombok.extern.slf4j.Slf4j;

/**
 * 項目打包war情況下部署外部tomcat,需該方式注冊nacos
 *
 * @author gblfy
 * @date 2022-03-29
 */
@Component
@Slf4j
public class NacosRegisterOnWar implements ApplicationRunner {

    @Autowired
    private NacosRegistration registration;

    @Autowired
    private NacosAutoServiceRegistration nacosAutoServiceRegistration;

    @Value("${server.port}")
    Integer port;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        if (registration != null && port != null) {
            Integer tomcatPort = port;
            try {
                tomcatPort = new Integer(getTomcatPort());
            } catch (Exception e) {
                log.warn("擷取外部Tomcat端口異常:", e);
            }
            registration.setPort(tomcatPort);
            nacosAutoServiceRegistration.start();
        }
    }

    /**
     * 擷取外部tomcat端口
     */
    public String getTomcatPort() throws Exception {
        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
        Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
        String port = objectNames.iterator().next().getKeyProperty("port");
        return port;
    }
}