背景:
我们系统服务是用的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-only=true 时,在 Spring 环境最早阶段注入以下属性, * 阻止当前服务实例注册到 Nacos,同时保留从 Nacos 发现/调用其他服务的能力: * * spring.cloud.nacos.discovery.register-enabled=false * dubbo.registry.register=false * * 开发者启动服务时只需加一个 JVM 参数或环境变量即可: * -Dyzcc.local-only=true * 或 YZCC_LOCAL_ONLY=true */ 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; } Map<String, 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-only=true 时激活,在启动日志中明确提示当前处于本地模式, * 方便开发者确认配置已生效。 * * 实际的属性注入由 {@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-only=true第七步:
启动项目!!!
然后你就会发现,本地启动的项目不再会自动上线到nacos了
代码详解
文件一:LocalOnlyProperties.java
@ConfigurationProperties(prefix = "yzcc") public class LocalOnlyProperties { private boolean localOnly; // getter / setter }作用:把yzcc.local-only=true这个配置项绑定成一个 Java 对象。
为什么要写这个?
Spring Boot 有一套"宽松绑定"(relaxed binding)机制:
yzcc.local-only=trueyzcc.localOnly=trueYZCC_LOCAL_ONLY=true(环境变量)-Dyzcc.local-only=true(JVM 参数)
这四种写法都会被 Spring 识别为同一个属性,绑定到localOnly字段上。
为什么 prefix 是yzcc而不是yzcc.local?
因为属性名是yzcc.local-only,prefix = "yzcc"对应前缀,localOnly字段对应后缀local-only(中划线自动转驼峰)。
文件二:LocalOnlyEnvironmentPostProcessor.java(核心)
这是整个方案最关键的文件,我逐段讲解。
类声明
public class LocalOnlyEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {EnvironmentPostProcessor:Spring 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 参数、环境变量、配置文件),更可靠。
构造属性 Map
Map<String, Object> props = new LinkedHashMap<>(); props.put("spring.cloud.nacos.discovery.register-enabled", false); props.put("dubbo.registry.register", false);第一条:spring.cloud.nacos.discovery.register-enabled=false
这是 Spring Cloud Alibaba Nacos 官方支持的属性。
Nacos Discovery 的注册逻辑分两部分:
- 订阅(subscribe):从 Nacos 拉取其他服务的实例列表,用于 Feign / LoadBalancer 调用
- 注册(register):把自己的 IP + 端口发布到 Nacos,让别人能发现你
register-enabled=false只关闭"注册",不关闭"订阅"。
所以你本地服务仍然能通过 Feign 调用开发环境的其他服务,但你自己不会出现在 Nacos 实例列表里。
第二条:dubbo.registry.register=false
你们项目里部分服务(如yzcc-order、yzcc-message)同时使用了 Dubbo,配置了:
dubbo: registry: address: nacos://${discovery.server-addr}这意味着 Dubbo 的 provider 也会注册到 Nacos。如果只关闭 Spring Cloud 的注册,Dubbo 这条链路还是会把你的本地服务暴露出去。
dubbo.registry.register=false告诉 Dubbo:连接注册中心,但不把自己注册进去。
Dubbo consumer 的订阅能力不受影响,仍然可以调用远端 Dubbo 服务。
注入属性源
MapPropertySource propertySource = new MapPropertySource(PROPERTY_SOURCE_NAME, props); environment.getPropertySources().addLast(propertySource);MapPropertySource:Spring 提供的一种属性源,把一个Map包装成可以被Environment读取的属性源。addLast:把这个属性源加到最低优先级位置。
为什么用addLast而不是addFirst?
Spring 的属性源是有优先级的,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-only=true,然后再注入我们的属性。
文件三:LocalOnlyAutoConfiguration.java
@Slf4j @Configuration @EnableConfigurationProperties(LocalOnlyProperties.class) @ConditionalOnProperty(prefix = "yzcc", name = "local-only", havingValue = "true") public class LocalOnlyAutoConfiguration {@ConditionalOnProperty:只有当yzcc.local-only=true时,这个配置类才会被加载。@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-enabled=false 和 dubbo.registry.register=false │ ├─ Spring 容器初始化,开始创建 Bean │ ├─ Nacos 客户端初始化(此时读到 register-enabled=false,不注册) │ ├─ Dubbo 初始化(此时读到 registry.register=false,不注册) │ └─ LocalOnlyAutoConfiguration 初始化(打印启动日志) │ └─ 服务启动完成 └─ 本服务不在 Nacos 实例列表,但可以正常调用其他服务一句话总结每个文件的职责
| 文件 | 职责 |
|---|---|
LocalOnlyProperties.java | 把yzcc.local-only绑定成 Java 对象,支持多种写法 |
LocalOnlyEnvironmentPostProcessor.java | 核心:在最早阶段注入禁止注册的属性,赶在 Nacos/Dubbo 初始化之前 |
LocalOnlyAutoConfiguration.java | 打印启动日志,让开发者确认本地模式已生效 |
spring.factories | 注册上面两个类,让 Spring Boot 能发现它们 |
ProjectNameConfig.java | 把开关值同步写入系统属性,兼容不走 Spring Environment 的场景 |
------------------------------------------------------------------------打完收工,准备下班---------------------------------------------------------------------------------