SpringCloud项目实现本地启动,不注册nacos

张开发
2026/4/17 20:07:24 15 分钟阅读

分享文章

SpringCloud项目实现本地启动,不注册nacos
背景我们系统服务是用的nacos作为注册中心因为多个服务之前通过Dubbo进行服务之间的调用然后注册到nacos才能彼此之前调用但是存在一个问题就是我本地启动代码也会每次都自动上线到nacos中导致很多时候我本地代码没更新或者打了断电影响开发环境别人测试甚是麻烦因此我考虑在本地服务启动之前增加一下配置实现本地启动完全不会注册到nacos但是又能本地调试调用其他服务。------------------------------------------------------------------------话不多说直接开干---------------------------------------------------------------------------------整体思路Spring Boot 启动时有一条属性加载链越早注入的属性优先级越高。我们的目标是在 Nacos 客户端初始化之前就把禁止注册的属性塞进 Spring 环境里。所以整个方案分两层最早阶段用EnvironmentPostProcessor注入属性在 Nacos 客户端初始化之前Bean 初始化阶段用Configuration打印启动日志让开发者知道当前是本地模式第一步package com.yzcc.autoconfigure; import org.springframework.boot.context.properties.ConfigurationProperties; ConfigurationProperties(prefix yzcc) public class LocalOnlyProperties { private boolean localOnly; public boolean isLocalOnly() { return localOnly; } public void setLocalOnly(boolean localOnly) { this.localOnly localOnly; } }第二步package com.yzcc.autoconfigure; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import java.util.LinkedHashMap; import java.util.Map; /** * 本地仅消费模式处理器。 * * 当 yzcc.local-onlytrue 时在 Spring 环境最早阶段注入以下属性 * 阻止当前服务实例注册到 Nacos同时保留从 Nacos 发现/调用其他服务的能力 * * spring.cloud.nacos.discovery.register-enabledfalse * dubbo.registry.registerfalse * * 开发者启动服务时只需加一个 JVM 参数或环境变量即可 * -Dyzcc.local-onlytrue * 或 YZCC_LOCAL_ONLYtrue */ public class LocalOnlyEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { private static final String PROPERTY_SOURCE_NAME yzccLocalOnlyPropertySource; private static final String LOCAL_ONLY_KEY yzcc.local-only; Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { // 支持 yzcc.local-only 和 yzcc.localOnly 两种写法Spring relaxed binding boolean localOnly environment.getProperty(LOCAL_ONLY_KEY, Boolean.class, false); if (!localOnly) { return; } MapString, Object props new LinkedHashMap(); // 1. 禁止 Spring Cloud Nacos 将本服务注册为在线实例 // 注意只关闭注册不关闭 discovery client本服务仍可发现/调用其他服务 props.put(spring.cloud.nacos.discovery.register-enabled, false); // 2. 禁止 Dubbo 将本服务 provider 注册到 Nacos // consumer 订阅能力不受影响仍可调用远端 Dubbo 服务 props.put(dubbo.registry.register, false); // 使用最低优先级addLast允许用户在 bootstrap.yml 中显式覆盖单个属性 MapPropertySource propertySource new MapPropertySource(PROPERTY_SOURCE_NAME, props); environment.getPropertySources().addLast(propertySource); } Override public int getOrder() { // 尽量早执行但在 ConfigFileApplicationListener 之后确保能读到 bootstrap.yml 里的 yzcc.local-only return Ordered.LOWEST_PRECEDENCE - 10; } }第三步package com.yzcc.autoconfigure; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; /** * 本地仅消费模式自动配置。 * * 当 yzcc.local-onlytrue 时激活在启动日志中明确提示当前处于本地模式 * 方便开发者确认配置已生效。 * * 实际的属性注入由 {link LocalOnlyEnvironmentPostProcessor} 在更早阶段完成。 */ Slf4j Configuration EnableConfigurationProperties(LocalOnlyProperties.class) ConditionalOnProperty(prefix yzcc, name local-only, havingValue true) public class LocalOnlyAutoConfiguration { Value(${spring.application.name:unknown}) private String applicationName; Value(${spring.cloud.nacos.discovery.register-enabled:true}) private boolean nacosRegisterEnabled; Value(${dubbo.registry.register:true}) private boolean dubboRegistryRegister; PostConstruct public void logLocalOnlyMode() { log.warn(\n ╔══════════════════════════════════════════════════════════╗\n ║ [LOCAL-ONLY MODE] 本地仅消费模式 ║\n ╠══════════════════════════════════════════════════════════╣\n ║ 服务名: {}\n ║ Nacos 注册: {} (false 本服务不会上线到 Nacos)\n ║ Dubbo 注册: {} (false 本服务 provider 不会注册到 Nacos)\n ║ 本服务仍可正常调用开发环境其他服务。\n ╚══════════════════════════════════════════════════════════╝, applicationName, nacosRegisterEnabled, dubboRegistryRegister); } }第四步spring.factories追加下列代码配置org.springframework.boot.env.EnvironmentPostProcessor\ com.yzcc.autoconfigure.LocalOnlyEnvironmentPostProcessor第五步package com.yzcc.autoconfigure; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; /** * author: Oliver * date: 2026年4月16日 上午11:20:53 */ Configuration public class ProjectNameConfig implements EnvironmentAware { Override public void setEnvironment(Environment environment) { if (StringUtils.isBlank(System.getProperty(yzcc.local-only))) { System.setProperty(yzcc.local-only, environment.getProperty(yzcc.local-only, false)); } } }第六步在idea的启动参数中也就是 添加虚拟机参数增加-Dyzcc.local-onlytrue第七步启动项目然后你就会发现本地启动的项目不再会自动上线到nacos了代码详解文件一LocalOnlyProperties.javaConfigurationProperties(prefix yzcc) public class LocalOnlyProperties { private boolean localOnly; // getter / setter }作用把yzcc.local-onlytrue这个配置项绑定成一个 Java 对象。为什么要写这个Spring Boot 有一套宽松绑定relaxed binding机制yzcc.local-onlytrueyzcc.localOnlytrueYZCC_LOCAL_ONLYtrue环境变量-Dyzcc.local-onlytrueJVM 参数这四种写法都会被 Spring 识别为同一个属性绑定到localOnly字段上。为什么 prefix 是yzcc而不是yzcc.local因为属性名是yzcc.local-onlyprefix yzcc对应前缀localOnly字段对应后缀local-only中划线自动转驼峰。文件二LocalOnlyEnvironmentPostProcessor.java核心这是整个方案最关键的文件我逐段讲解。类声明public class LocalOnlyEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {EnvironmentPostProcessorSpring Boot 提供的扩展点允许你在应用上下文刷新之前修改Environment也就是属性源。Ordered控制执行顺序因为可能有多个EnvironmentPostProcessor。为什么用EnvironmentPostProcessor而不是Configuration里的Bean因为 Nacos 的注册行为发生在 Spring 容器初始化阶段如果你在Bean里设置属性已经晚了Nacos 客户端早就读完配置开始注册了。EnvironmentPostProcessor是在所有 Bean 初始化之前执行的所以能赶在 Nacos 之前把属性注入进去。读取开关boolean localOnly environment.getProperty(LOCAL_ONLY_KEY, Boolean.class, false); if (!localOnly) { return; }从当前 Spring 环境里读取yzcc.local-only的值默认是false。如果没有开启直接 return什么都不做不影响正常启动流程。这里用environment.getProperty而不是System.getProperty是因为 Spring 的Environment会聚合所有属性源JVM 参数、环境变量、配置文件更可靠。构造属性 MapMapString, Object props new LinkedHashMap(); props.put(spring.cloud.nacos.discovery.register-enabled, false); props.put(dubbo.registry.register, false);第一条spring.cloud.nacos.discovery.register-enabledfalse这是 Spring Cloud Alibaba Nacos 官方支持的属性。Nacos Discovery 的注册逻辑分两部分订阅subscribe从 Nacos 拉取其他服务的实例列表用于 Feign / LoadBalancer 调用注册register把自己的 IP 端口发布到 Nacos让别人能发现你register-enabledfalse只关闭注册不关闭订阅。所以你本地服务仍然能通过 Feign 调用开发环境的其他服务但你自己不会出现在 Nacos 实例列表里。第二条dubbo.registry.registerfalse你们项目里部分服务如yzcc-order、yzcc-message同时使用了 Dubbo配置了dubbo: registry: address: nacos://${discovery.server-addr}这意味着 Dubbo 的 provider 也会注册到 Nacos。如果只关闭 Spring Cloud 的注册Dubbo 这条链路还是会把你的本地服务暴露出去。dubbo.registry.registerfalse告诉 Dubbo连接注册中心但不把自己注册进去。Dubbo consumer 的订阅能力不受影响仍然可以调用远端 Dubbo 服务。注入属性源MapPropertySource propertySource new MapPropertySource(PROPERTY_SOURCE_NAME, props); environment.getPropertySources().addLast(propertySource);MapPropertySourceSpring 提供的一种属性源把一个Map包装成可以被Environment读取的属性源。addLast把这个属性源加到最低优先级位置。为什么用addLast而不是addFirstSpring 的属性源是有优先级的addFirst是最高优先级addLast是最低优先级。我们用addLast的原因是允许用户在bootstrap.yml里显式覆盖单个属性。比如你想在本地模式下单独把某个服务的 Dubbo 注册打开可以在bootstrap.yml里写dubbo: registry: register: true因为bootstrap.yml的优先级高于我们的addLast所以用户的显式配置会覆盖我们的默认行为。执行顺序Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE - 10; }Ordered.LOWEST_PRECEDENCE是Integer.MAX_VALUE表示最后执行。减去 10是为了比最后稍微早一点点给其他可能存在的EnvironmentPostProcessor留出空间。为什么不用Ordered.HIGHEST_PRECEDENCE最早执行因为我们需要先读到yzcc.local-only这个属性的值而这个值可能来自bootstrap.yml或 JVM 参数。如果我们执行得太早bootstrap.yml还没被加载就读不到这个值了。所以我们选择晚一点执行确保能读到用户配置的yzcc.local-onlytrue然后再注入我们的属性。文件三LocalOnlyAutoConfiguration.javaSlf4j Configuration EnableConfigurationProperties(LocalOnlyProperties.class) ConditionalOnProperty(prefix yzcc, name local-only, havingValue true) public class LocalOnlyAutoConfiguration {ConditionalOnProperty只有当yzcc.local-onlytrue时这个配置类才会被加载。EnableConfigurationProperties(LocalOnlyProperties.class)激活LocalOnlyProperties的属性绑定。这个类的唯一职责是打印启动日志让开发者在控制台看到一个醒目的提示确认本地模式已经生效。Value(${spring.cloud.nacos.discovery.register-enabled:true}) private boolean nacosRegisterEnabled; Value(${dubbo.registry.register:true}) private boolean dubboRegistryRegister;这两个字段读取的是实际生效的属性值也就是EnvironmentPostProcessor注入进去的值用来在日志里展示方便你确认属性确实被设置成了false。PostConstruct public void logLocalOnlyMode() { log.warn(╔══...╗\n║ [LOCAL-ONLY MODE] ...); }PostConstruct在 Bean 初始化完成后执行此时所有属性都已经注入完毕。用log.warn而不是log.info是为了让这条日志在控制台更显眼warn 级别通常是黄色。文件四spring.factories的修改org.springframework.boot.env.EnvironmentPostProcessor\ com.yzcc.autoconfigure.LocalOnlyEnvironmentPostProcessor两个注册点对应两种不同的扩展机制EnableAutoConfiguration告诉 Spring Boot 自动装配机制把LocalOnlyAutoConfiguration纳入自动配置扫描。这是Configuration类的标准注册方式。EnvironmentPostProcessor这是一个独立的扩展点不走自动配置机制必须单独在spring.factories里注册。如果不在这里注册LocalOnlyEnvironmentPostProcessor根本不会被 Spring Boot 发现和执行。文件五ProjectNameConfig.java的修改if (StringUtils.isBlank(System.getProperty(yzcc.local-only))) { System.setProperty(yzcc.local-only, environment.getProperty(yzcc.local-only, false)); }为什么要把yzcc.local-only写入System.setProperty有些框架比如某些版本的 Dubbo、或者你们自己的工具类会直接用System.getProperty读取系统属性而不走 Spring 的Environment。把这个值同步写入System.setProperty是为了兼容这类场景确保不管哪种方式读取都能拿到正确的值。这里参考了原有代码里project.name的写法保持一致的风格。整体执行时序JVM 启动 │ ├─ 加载 spring.factories │ ├─ 执行 EnvironmentPostProcessor包括我们的 LocalOnlyEnvironmentPostProcessor │ └─ 读取 yzcc.local-only │ └─ 如果为 true注入 register-enabledfalse 和 dubbo.registry.registerfalse │ ├─ Spring 容器初始化开始创建 Bean │ ├─ Nacos 客户端初始化此时读到 register-enabledfalse不注册 │ ├─ Dubbo 初始化此时读到 registry.registerfalse不注册 │ └─ LocalOnlyAutoConfiguration 初始化打印启动日志 │ └─ 服务启动完成 └─ 本服务不在 Nacos 实例列表但可以正常调用其他服务一句话总结每个文件的职责文件职责LocalOnlyProperties.java把yzcc.local-only绑定成 Java 对象支持多种写法LocalOnlyEnvironmentPostProcessor.java核心在最早阶段注入禁止注册的属性赶在 Nacos/Dubbo 初始化之前LocalOnlyAutoConfiguration.java打印启动日志让开发者确认本地模式已生效spring.factories注册上面两个类让 Spring Boot 能发现它们ProjectNameConfig.java把开关值同步写入系统属性兼容不走 Spring Environment 的场景------------------------------------------------------------------------打完收工准备下班---------------------------------------------------------------------------------

更多文章