Springboot War包部署下nacos无法注册问题

[TOC]
如果需要解决方法,请直接看9. 如何在WAR包部署下成功注册nacos部分

1. @EnableDiscoveryClient的使用

在项目中需要使用nacos进行服务治理时,需要在启动类上添加该注解来实现服务的自动注册

/**
 * Annotation to enable a DiscoveryClient implementation.
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

    /**
     * If true, the ServiceRegistry will automatically register the local server.
     * @return - {@code true} if you want to automatically register.
     */
    boolean autoRegister() default true;

}

autoRegister默认是注册的,引入EnableDiscoveryClientImportSelector.class

2. EnableDiscoveryClientImportSelector类的作用

首先将源码贴出来,该类继承SpringFactoryImportSelector(用来选择与泛型相关的配置,加载其相应实现)

/**
 * @author Spencer Gibb
 */
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
        extends SpringFactoryImportSelector<EnableDiscoveryClient> {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        String[] imports = super.selectImports(metadata);

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));

        boolean autoRegister = attributes.getBoolean("autoRegister");

        if (autoRegister) {
            List<String> importsList = new ArrayList<>(Arrays.asList(imports));
            importsList.add(
                    "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
            imports = importsList.toArray(new String[0]);
        }
        else {
            Environment env = getEnvironment();
            if (ConfigurableEnvironment.class.isInstance(env)) {
                ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
                LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                map.put("spring.cloud.service-registry.auto-registration.enabled", false);
                MapPropertySource propertySource = new MapPropertySource(
                        "springCloudDiscoveryClient", map);
                configEnv.getPropertySources().addLast(propertySource);
            }
        }
        return imports;
    }

    @Override
    protected boolean isEnabled() {
        return getEnvironment().getProperty("spring.cloud.discovery.enabled",
                Boolean.class, Boolean.TRUE);
    }

    @Override
    protected boolean hasDefaultFactory() {
        return true;
    }

}

1.在selectImports(AnnotationMetadata)方法中获取@EnableDiscoveryClient中的autoRegister参数,如过为True则将
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration加入加载列表中。

2.当autoRegister=false时,设spring.cloud.service-registry.auto-registration.enabled=false,这样跟注册相关的类将不会自动装配,因为自动注册相关的类都有一个条件装配@ConditionalOnProperty(value ="spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)。

3.AutoServiceRegistrationConfiguration

@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public class AutoServiceRegistrationConfiguration {

}

@ConditionOnProperty为条件装配,只有spring.cloud.service-registry.auto-registration.enabled=true时才装配。
该类更像是一个信号量,用来若其加载则会加载其后的那些注册类

4.NacosDiscoveryAutoConfiguration

/**
 * @author xiaojing
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
@Configuration
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
        AutoServiceRegistrationAutoConfiguration.class })
public class NacosDiscoveryAutoConfiguration {

    @Bean
    public NacosServiceRegistry nacosServiceRegistry(
            NacosDiscoveryProperties nacosDiscoveryProperties) {
        return new NacosServiceRegistry(nacosDiscoveryProperties);
    }

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosRegistration nacosRegistration(
            NacosDiscoveryProperties nacosDiscoveryProperties,
            ApplicationContext context) {
        return new NacosRegistration(nacosDiscoveryProperties, context);
    }

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(
            NacosServiceRegistry registry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
        return new NacosAutoServiceRegistration(registry,
                autoServiceRegistrationProperties, registration);
    }
}

该类的类加载条件为
@ConditionalOnNacosDiscoveryEnabled即配置中spring.cloud.nacos.discovery.enabled=true
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) 对应项目中是否使用到@EnableDiscoveryClient且其autoRegistry是否为true。

加载顺序为@AutoConfigureAfter({AutoServiceRegistrationConfiguration.class,AutoServiceRegistrationAutoConfiguration.class })
意味着若AutoServiceRegistrationConfiguration.class,AutoServiceRegistrationAutoConfiguration.class加载失败则不进行该类的加载。

同时,在该类中创建NacosServiceRegistry,NacosRegistration,NacosAutoServiceRegistration三个类,后续的注册调用主要依赖于这三个类进行。

5. NacosServiceRegistry

该类主要实现org.springframework.cloud.client.serviceregistry.ServiceRegistry中的方法
如void register(R registration);方法和void deregister(R registration);方法。其中register中通过层层调用,通过api方式请求nacos的服务。
这里的R泛型的策略是<R extends Registration>

ServiceRegistry接口

/**
 * Contract to register and deregister instances with a Service Registry.
 *
 */
public interface ServiceRegistry<R extends Registration> {

    /**
     * Registers the registration. A registration typically has information about an
     * instance, such as its hostname and port.
     * @param registration registration meta data
     */
    void register(R registration);

    /**
     * Deregisters the registration.
     * @param registration registration meta data
     */
    void deregister(R registration);

    /**
     * Closes the ServiceRegistry. This is a lifecycle method.
     */
    void close();

    /**
     * Sets the status of the registration. The status values are determined by the
     * individual implementations.
     * @param registration The registration to update.
     * @param status The status to set.
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     */
    void setStatus(R registration, String status);

    /**
     * Gets the status of a particular registration.
     * @param registration The registration to query.
     * @param <T> The type of the status.
     * @return The status of the registration.
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     */
    <T> T getStatus(R registration);

}

6. NacosRegistration

该类主要用来做注册信息的管理实现了Registration, ServiceInstance接口

7. NacosAutoServiceRegistration

该类主要用来调用NacosServiceRegistry中的registry方法进行注册

public class NacosAutoServiceRegistration
        extends AbstractAutoServiceRegistration<Registration> {
    private static final Logger log = LoggerFactory
            .getLogger(NacosAutoServiceRegistration.class);

    private NacosRegistration registration;

    public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
        super(serviceRegistry, autoServiceRegistrationProperties);
        this.registration = registration;
    }

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

    @Override
    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;
    }

    @Override
    protected NacosRegistration getManagementRegistration() {
        return null;
    }

    @Override
    protected void register() {
        if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
            log.debug("Registration disabled.");
            return;
        }
        if (this.registration.getPort() < 0) {
            this.registration.setPort(getPort().get());
        }
        super.register();
    }

    @Override
    protected void registerManagement() {
        if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
            return;
        }
        super.registerManagement();

    }

    @Override
    protected Object getConfiguration() {
        return this.registration.getNacosDiscoveryProperties();
    }

    @Override
    protected boolean isEnabled() {
        return this.registration.getNacosDiscoveryProperties().isRegisterEnabled();
    }

    @Override
    @SuppressWarnings("deprecation")
    protected String getAppName() {
        String appName = registration.getNacosDiscoveryProperties().getService();
        return StringUtils.isEmpty(appName) ? super.getAppName() : appName;
    }

}

该类中的register()方法最终会调用super.register();

AbstractAutoServiceRegistration类

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

    private static final Log logger = LogFactory
            .getLog(AbstractAutoServiceRegistration.class);

    private final ServiceRegistry<R> serviceRegistry;

    private boolean autoStartup = true;

    private AtomicBoolean running = new AtomicBoolean(false);

    private int order = 0;

    private ApplicationContext context;

    private Environment environment;

    private AtomicInteger port = new AtomicInteger(0);

    private AutoServiceRegistrationProperties properties;

    @Deprecated
    protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,
            AutoServiceRegistrationProperties properties) {
        this.serviceRegistry = serviceRegistry;
        this.properties = properties;
    }

    protected ApplicationContext getContext() {
        return this.context;
    }

    @Override
    @SuppressWarnings("deprecation")
    public void onApplicationEvent(WebServerInitializedEvent event) {
        bind(event);
    }

    @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();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.context = applicationContext;
        this.environment = this.context.getEnvironment();
    }

    @Deprecated
    protected Environment getEnvironment() {
        return this.environment;
    }

    @Deprecated
    protected AtomicInteger getPort() {
        return this.port;
    }

    public boolean isAutoStartup() {
        return this.autoStartup;
    }

    public void start() {
        if (!isEnabled()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Discovery Lifecycle disabled. Not starting");
            }
            return;
        }

        // only initialize if nonSecurePort is greater than 0 and it isn't already running
        // because of containerPortInitializer below
        if (!this.running.get()) {
            this.context.publishEvent(
                    new InstancePreRegisteredEvent(this, getRegistration()));
            register();
            if (shouldRegisterManagement()) {
                registerManagement();
            }
            this.context.publishEvent(
                    new InstanceRegisteredEvent<>(this, getConfiguration()));
            this.running.compareAndSet(false, true);
        }

    }


    public void stop() {
        if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
            deregister();
            if (shouldRegisterManagement()) {
                deregisterManagement();
            }
            this.serviceRegistry.close();
        }
    }

}

该类继承了一个springboot的监听ApplicationListener器用来监听WebServerInitializedEvent该事件。
当该事件完成后将调用onApplicationEvent()方法,并调用bind(WebServerInitializedEvent)方法。
在bind(WebServerInitializedEvent)方法中调用start()方法,并在start()方法中调用register()方法。

private final ServiceRegistry<R> serviceRegistry;

@Deprecated
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
}

protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry,
        AutoServiceRegistrationProperties properties) {
    this.serviceRegistry = serviceRegistry;
    this.properties = properties;
}

protected void register() {
    this.serviceRegistry.register(getRegistration());
}

其中ServiceRegistry其实现类就是NacosServiceRegistry

8.WebServerInitializedEvent

该事件将在应用上下文完成刷新并准备好之后发布。主要用来获取正在运行的服务器本地端口。

public abstract class WebServerInitializedEvent extends ApplicationEvent {

    protected WebServerInitializedEvent(WebServer webServer) {
        super(webServer);
    }

    /**
     * Access the {@link WebServer}.
     * @return the embedded web server
       获取嵌入式的web服务器(即SpringBoot中内置的tomcat)
     */
    public WebServer getWebServer() {
        return getSource();
    }

    /**
     * Access the application context that the server was created in. Sometimes it is
     * prudent to check that this matches expectations (like being equal to the current
     * context) before acting on the server itself.
     * @return the applicationContext that the server was created from
       获取服务创建时的上下文。有时会谨慎的检查配置是否复合预期(如 正在作用的是否是当前上下文)
     */
    public abstract WebServerApplicationContext getApplicationContext();

    /**
     * Access the source of the event (an {@link WebServer}).
     * @return the embedded web server
     访问事件源
     */
    @Override
    public WebServer getSource() {
        return (WebServer) super.getSource();
    }

}

可以看到该类主要是获取Springboot内置的tomcat端口。当使用外置tomcat时,该事件并不能获取到对应的服务信息。所以也无法在nacos中注册成功。

9. 如何在WAR包部署下成功注册nacos

实现ApplicationRunner接口,在应用启动成功后完成某些初始化工作。


@Component
public class NacosConfig implements ApplicationRunner {
 
    @Autowired(required = false)
    private NacosAutoServiceRegistration registration;
 
    @Value("${baseConfig.nacos.port}")
    Integer port;
 
    @Override
    public void run(ApplicationArguments args) {
        if (registration != null && port != null) {
            Integer tomcatPort = port;
            try {
                tomcatPort = new Integer(getTomcatPort());
            } catch (Exception e) {
                e.printStackTrace();
            }
 
            registration.setPort(tomcatPort);
            registration.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;
    }
}

实现一个监听

@Component
public class NacosListener implements ApplicationListener<ApplicationReadyEvent> {


    @Autowired
    private NacosRegistration registration;

    @Autowired
    private NacosAutoServiceRegistration nacosAutoServiceRegistration;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        String property = event.getApplicationContext().getEnvironment().getProperty("server.port");
        registration.setPort(Integer.valueOf(property));
        nacosAutoServiceRegistration.start();
    }
}
@EventListener(ApplicationReadyEvent.class)
public void onWebServerReady(ApplicationReadyEvent event) {
    String property = event.getApplicationContext().getEnvironment().getProperty("server.port");
    registration.setPort(Integer.valueOf(property));
    nacosAutoServiceRegistration.start();
}

参考地址:

实现ApplicationRunner接口:https://my.oschina.net/yuhuashang/blog/3086167

源码:127.0.0.1

springboot事件: https://docs.spring.io/spring-boot/docs/2.1.11.BUILD-SNAPSHOT/reference/html/boot-features-spring-application.html#boot-features-application-events-and-listeners

@EnableDiscoveryClient 注解如何实现服务注册与发现:https://blog.csdn.net/z694644032/article/details/96706593

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容