【Shopee】Config Priority

配置优先级

在Spring Boot的配置体系中,application.yaml(默认配置文件)和application-{env}.yaml(环境特定配置文件,如application-prod.yaml)的优先级遵循明确的规则,核心原则是环境配置优先于默认配置。以下是具体分析:


⚙️ 1. 基本优先级规则

  • 环境配置文件 > 默认配置文件

    当通过spring.profiles.active激活某个环境(如prod)时,application-prod.yaml中的配置会覆盖application.yaml中的同名配置项。若配置项不同,则合并生效。

  • 示例

    • application.yaml设置 server.port: 8080

    • application-prod.yaml设置 server.port: 80

      激活prod环境后,最终端口为 80


🔧 2. 环境配置的激活方式

环境配置文件的生效依赖于显式激活环境,方式包括:

  1. 配置文件指定

    application.yaml中声明:

    spring:
      profiles:
        active: prod   # 激活prod环境
    
  2. 命令行参数

    启动时动态指定:

    java -jar app.jar --spring.profiles.active=prod
    

    命令行优先级最高,可覆盖配置文件中的active设置。

  3. 环境变量

    通过操作系统环境变量设置:

    export SPRING_PROFILES_ACTIVE=prod
    

🔄 3. 配置覆盖与合并机制

  • 同名配置项:环境配置文件直接覆盖默认配置。

  • 异名配置项:环境配置与默认配置合并,共同生效。

    例如:

    • application.yaml定义数据库公共参数(如db.url)。
    • application-prod.yaml仅覆盖密码(db.password),其他参数继承默认值。

📊 4. 完整配置源优先级链条

环境配置文件(application-{env}.yaml)的优先级需置于整个配置体系中理解。从高到低的完整顺序为:

  1. 命令行参数(如 --server.port=8081
  2. 环境变量(如 SPRING_PROFILES_ACTIVE=prod
  3. 外部配置文件(如Jar包外部的config/application-prod.yaml
  4. 内部环境配置文件(如classpath:/application-prod.yaml
  5. 内部默认配置文件(如classpath:/application.yaml
  6. @PropertySource注解或默认配置。

关键结论

application-{env}.yaml优先级高于 application.yaml,但低于命令行参数、环境变量及外部配置文件。


⚠️ 5. 注意事项

  1. 文件格式优先级

    若同一位置存在不同格式文件(如application-prod.propertiesapplication-prod.yaml),.properties优先级高于.yaml

  2. 多环境配置冲突

    激活多个环境时(如--spring.profiles.active=dev,prod),按从左到右顺序加载,右侧覆盖左侧同名配置。

  3. Profile分组

    可通过spring.profiles.group合并环境配置组(如production = prod1,prod2),再用--spring.profiles.active=production激活整组。


💎 总结

  • 默认规则application-{env}.yaml> application.yaml(需显式激活环境)。
  • 灵活控制:通过命令行、环境变量或配置文件动态切换环境,实现配置覆盖。
  • 最佳实践
    • 通用配置(如日志级别)放在application.yaml
    • 环境差异配置(如数据库连接、端口)放在application-{env}.yaml
    • 敏感信息(如密码)通过命令行或外部配置注入,避免硬编码。

子模块

在Spring Cloud微服务架构中,每个微服务通常是一个独立的子模块,但这并非绝对要求,而是取决于项目结构的设计选择。以下是具体分析:


1. 微服务作为子模块的常见实践

在基于Maven或Gradle的多模块项目中,每个微服务通常被设计为一个独立的子模块。这种结构具有以下优势:

  • 统一依赖管理:父模块(Parent Module)统一管理Spring Boot、Spring Cloud版本及公共依赖,避免版本冲突。
  • 代码隔离与独立部署:每个微服务子模块(如order-serviceuser-service)拥有独立的代码库、配置和启动类,可独立编译、测试和部署。
  • 团队协作友好:不同团队可并行开发各自负责的微服务模块,减少代码冲突。

典型项目结构示例

spring-cloud-project/  
├── parent-module/              # 父模块(统一依赖管理)
├── order-service/              # 订单微服务(子模块)
│   ├── src/  
│   ├── pom.xml                 # 专属依赖配置  
├── user-service/               # 用户微服务(子模块)
│   ├── src/  
│   ├── pom.xml  
├── api-gateway/                # API网关(子模块)
└── eureka-server/              # 注册中心(子模块)

注:每个微服务子模块均为独立Spring Boot应用,通过父模块协调依赖。


⚠️ 2. 微服务不作为子模块的场景

某些项目可能采用完全独立的代码仓库(而非子模块),此时微服务间无直接代码依赖:

  • 技术栈异构性:不同微服务可使用不同语言(如Java + Go)或框架,无法通过父模块统一管理。
  • 独立演进需求:大型团队中,服务需完全解耦,独立版本发布(如订单服务v1.2,用户服务v1.5)。
  • 避免循环依赖:子模块间若相互引用(如A依赖B,B又依赖A),会导致Maven构建失败。

独立仓库的通信方式

  • 通过HTTP/REST、gRPC或消息队列(如Kafka)远程调用。
  • 依赖服务注册中心(如Eureka)动态发现服务实例。

🔧 3. 关键设计考量因素

因素作为子模块独立仓库
依赖管理父模块统一管理,减少冲突各服务独立管理,可能版本不一致
构建与部署可整体构建,但需独立部署完全独立构建部署
团队协作适合中小团队,依赖协调简单适合大型团队,完全解耦
技术栈灵活性受限(需兼容父模块约束)自由选择不同技术栈
代码复用通过公共子模块共享工具类需复制代码或发布共享库

💡 推荐选择

  • 中小型项目:优先采用多模块结构,简化依赖管理。
  • 大型分布式系统:微服务独立仓库,通过API契约和注册中心协作。

🛠️ 4. 实现建议

若采用子模块方案,需注意:

  1. 明确模块边界:按业务功能拆分(如订单、用户),避免模块职责重叠。
  2. 定义公共模块:抽取工具类、DTO、Feign客户端接口到独立子模块(如common-utils),供其他微服务依赖。
  3. 禁用直接数据库访问:微服务间仅通过API通信,数据库严格隔离(如订单服务不直接查询用户库)。

💎 总结

在Spring Cloud微服务架构中,每个微服务通常设计为Maven/Gradle子模块,以简化依赖管理和项目结构;但在需要技术异构性或完全解耦的场景下,微服务也可作为独立代码仓库存在。实际选择需权衡项目规模、团队协作和技术需求。

JPMS

Maven/Gradle 子模块和 Java 中的 Module(JPMS)不是一回事。它们分别属于构建工具的项目管理Java 语言层面的运行时封装两个不同维度,虽有交集但核心目标和技术实现差异显著。以下是详细对比分析:


📦 1. 核心概念与目标

维度Maven/Gradle 子模块Java Module (JPMS)
定义层级构建工具(Build Tool)的项目管理单元Java 语言规范(JSR 376)的运行时封装单元
主要目的代码组织、依赖复用、构建效率优化(如分模块编译)强封装性、显式依赖管理、运行时安全隔离(解决 “JAR Hell”)
核心机制父项目聚合子模块,通过配置文件(pom.xml/build.gradle)管理依赖与构建流程通过 module-info.java声明模块边界、导出包和依赖关系
作用阶段构建时(开发阶段)编译时 + 运行时(影响类加载机制)

⚙️ 2. 技术实现对比

Maven/Gradle 子模块

  • 配置方式
    • Maven:父 POM 通过 <modules>定义子模块,子模块继承父依赖(<dependencyManagement>)。
    • Gradle:根项目通过 settings.gradleinclude声明子模块,依赖通过 implementation project(':sub')引用。
  • 核心作用
    • 模块间代码隔离(如微服务的独立服务模块)。
    • 统一管理依赖版本(避免冲突)。
    • 支持增量构建(仅编译修改的模块)。

Java Module (JPMS)

  • 配置方式

    • 每个模块需定义 module-info.java,声明:

      module com.example.core {
          requires java.sql;          // 依赖其他模块
          exports com.example.api;    // 导出公共 API
          opens com.example.impl;     // 允许反射访问
      }
      
  • 核心作用

    • 强封装:未导出(exports)的包对其他模块不可见(即使 public 类)。
    • 依赖显式化:编译期和运行时会检查 requires声明的依赖是否满足。
    • 运行时优化:通过 jlink生成最小化 JRE(仅包含所需模块)。

🔗 3. 关联性与协作场景

虽然两者独立,但在项目中可协同工作:

  1. 子模块作为 Java Module
    • 每个构建子模块(如 user-service)可独立定义为 Java Module(含 module-info.java),实现业务逻辑的运行时隔离。
  2. 依赖传递的差异
    • 构建工具依赖:子模块 A 依赖 B,仅需在 build.gradle声明 implementation project(':B')
    • JPMS 依赖:模块 A 需在 module-info.java中显式声明 requires B,否则无法访问 B 的导出包。
  3. 兼容性处理
    • 非模块化 JAR(如传统库)可作为自动模块(Automatic Module)或放入类路径(未命名模块)使用。

⚖️ 4. 关键差异总结

特性Maven/Gradle 子模块Java Module
问题域项目构建管理与组织代码封装与运行时安全
配置文件pom.xml/ build.gradlemodule-info.java
依赖范围构建期 + 编译期(传递性依赖)编译期 + 运行时(显式声明)
跨模块访问控制无强制限制(代码可见即可用)严格通过 exports/opens控制
工具支持Maven/Gradle 插件JDK 内置(Java 9+)

💡 5. 实际应用建议

  • 新项目
    • 优先用 Gradle/Maven 子模块管理代码结构,提升构建效率。
    • 核心服务模块可启用 JPMS,强化边界设计(如 API 模块 exports接口,实现模块隐藏细节)。
  • 遗留系统迁移
    • 先拆分为构建子模块,再逐步为关键模块添加 module-info.java
    • 使用 jdeps分析依赖,解决非法反射访问(--add-opens临时绕过)。

特别注意

  • 构建工具的子模块是物理层级划分(目录分离),Java Module 是逻辑边界封装(包可见性控制)。
  • Spring Boot 等框架需配合 spring-modulith等方案适配 JPMS,避免反射访问失败。

通过厘清两者定位,可在项目中分层应用:构建工具解决工程效率问题,JPMS 解决架构治理问题,共同打造高内聚、低耦合的现代化 Java 应用。

区别

在 Java 项目架构中,将 Gradle/Maven 子模块视为一个逻辑上的整体 Java Module 是合理的理解方式。这种逻辑统一性源于项目设计的模块化目标,但需结合构建工具与语言规范的具体实现机制来分层解读。以下是综合分析:


1. 逻辑统一性的依据

  • 共同目标与边界

    多个子模块(如 user-serviceorder-service)服务于同一个业务系统,通过父模块统一管理依赖和配置(如 Maven 的父 POM 或 Gradle 的根构建脚本),形成内聚的代码单元。这种设计符合模块化“高内聚、低耦合”的核心原则,可视为逻辑整体 。

  • 显式依赖关系

    子模块间通过构建工具声明依赖(如 Gradle 的 implementation project(':module')或 Maven 的子模块坐标引用),构成有向无环图(DAG),共同实现业务功能 。


⚠️ 2. 与 Java Module (JPMS) 的本质区别

尽管子模块在逻辑上属于同一项目,但不等于 JPMS 的 Module,差异主要体现在:

维度构建工具子模块 (Gradle/Maven)Java Module (JPMS)
核心目标代码组织、依赖管理、构建效率优化强封装性、运行时隔离、显式依赖管理
配置方式pom.xml/ build.gradlemodule-info.java
作用阶段开发与构建时编译时 + 运行时
访问控制无强制封装(代码可见即可用)严格通过 exports/opens控制包可见性
技术实现物理目录隔离逻辑边界封装(JVM 类加载机制)

关键区别

  • 子模块是物理层级划分(目录分离),JPMS 是 JVM 层级的逻辑封装(包可见性控制)。

🔧 3. 协作场景:逻辑整体性的实现方式

虽然技术实现分离,但两者可协同强化模块化设计:

  1. 子模块作为 JPMS 的基础单元

    每个构建子模块(如 common-utils)可独立定义为 Java Module(含 module-info.java),实现业务内聚与运行时隔离的双重目标 。

  2. 依赖传递的协同管理

    • 构建工具依赖:在 build.gradle中声明 implementation project(':B')

    • JPMS 依赖:在 module-info.java中显式声明 requires B

      两者共同确保模块间依赖的显式化和一致性 。

  3. 规避冲突

    父模块统一管理依赖版本(如 Maven 的 <dependencyManagement>),避免子模块间或 JPMS 的版本冲突 。


💎 总结:分层理解逻辑整体性

  • 项目架构层:✅ 是逻辑整体

    Gradle/Maven 多模块项目通过父模块统一配置、子模块协同实现业务目标,构成完整的应用系统 。

  • 语言规范层:❌ 不是 JPMS Module

    子模块无运行时强封装能力,需额外通过 module-info.java实现 JPMS 的隔离 。

  • 最佳实践

    • 新项目:用构建工具管理物理结构(子模块),为核心模块添加 JPMS 封装 。
    • 遗留系统:先拆分为子模块,再逐步引入 JPMS 治理边界 。

示例关系图

逻辑项目 (MyApp)
├── 构建子模块 [Maven/Gradle]
│   ├── user-service (含 module-info.java → JPMS Module)
│   └── order-service (依赖 user-service)
└── 父模块 (统一依赖管理)

通过分层设计,兼顾工程效率与架构治理

访问权限

在 Java 中,protecteddefault(无修饰符)和 private是三种访问修饰符,用于控制类成员的可见性和访问范围。它们的核心区别在于访问权限的严格程度和应用场景,具体对比如下:


📊 访问范围对比表

访问修饰符当前类同一包内不同包子类其他包(非子类)
private
default
protected

default是隐式修饰符(不写任何关键字),protectedprivate需显式声明。


🔍 详细区别解析

  1. private(最严格)

    • 访问范围:仅限当前类内部,其他类(包括同包或子类)均不可访问。

    • 使用场景

      • 封装敏感数据(如密码),通过 getter/setter方法间接访问。
      • 隐藏工具方法(如内部计算逻辑),避免外部调用。
    • 示例

      public class User {
          private String password; // 仅本类可访问
      }
      
  2. default(包级私有)

    • 访问范围当前类 + 同一包内的其他类。不同包中的类(包括子类)均不可访问。

    • 使用场景

      • 共享工具类或配置给同包模块(如工具类 StringUtils)。
      • 限制跨包访问,实现模块化封装。
    • 示例

      class Logger { // 默认修饰符
          void log(String message) { // 同包内可调用
              System.out.println(message);
          }
      }
      
  3. protected(继承友好)

    • 访问范围当前类 + 同一包内类 + 不同包子类。非子类的外部包类不可访问。

    • 使用场景

      • 父类定义可被子类重写的方法(如模板方法模式)。
      • 子类需直接访问父类成员(如 protected int id;)。
    • 示例

      public class Animal {
          protected void breathe() { // 子类可访问
              System.out.println("Breathing...");
          }
      }
      public class Dog extends Animal {
          public void test() {
              breathe(); // ✔ 子类调用父类protected方法
          }
      }
      

⚠️ 关键注意事项

  1. 类修饰符的限制
    • privateprotected不能修饰外部类(仅能修饰内部类),外部类只能用 publicdefault
  2. 继承中的可见性
    • 子类可访问父类的 protected成员,但不能访问 private成员(即使继承也隐藏)。
  3. 设计原则
    • 封装性优先:尽量用 private,必要时通过 protected开放给子类,避免滥用 public
    • 包内协作:同包模块间用 default共享,减少跨包耦合。

💎 总结:选择策略

  • private:隐藏实现细节,强制通过接口访问。
  • default:同包内共享工具或配置,禁止跨包访问。
  • protected:支持跨包继承,允许子类扩展父类行为。

实际开发中,优先选择最严格的权限(如能用 private则不用 default),再按需放宽。

@Order

在 Java Spring 框架中,当多个相同类型的 @Component中部分指定了 @Order值而其他未指定时,其行为主要由以下规则决定:

⚙️ 1. 默认值机制

  • 未指定 @Order的组件:会被隐式赋予默认值 Ordered.LOWEST_PRECEDENCE(即 Integer.MAX_VALUE),表示最低优先级。
  • 指定 @Order的组件:按注解值排序,值越小优先级越高(例如 @Order(1)优先于 @Order(2))。

🔀 2. 排序规则与位置

  • 整体排序逻辑:所有组件按优先级分组排序:
    • 最高优先级:实现 PriorityOrdered接口的组件(如框架内部扩展)。
    • 中等优先级:显式指定 @Order或实现 Ordered接口的组件。
    • 最低优先级:未指定 @Order的组件(默认 Integer.MAX_VALUE)。
  • 未指定值组件的顺序
    • 在注入集合(如 List<Service>)时,它们会排在所有有序组件之后
    • 多个未指定值的组件之间顺序不确定(可能受类加载顺序影响)。

⚠️ 3. 典型场景示例

  • 集合注入顺序

    @Component @Order(1) class ServiceA implements MyService {}
    @Component @Order(2) class ServiceB implements MyService {}
    @Component class ServiceC implements MyService {} // 默认 Order=Integer.MAX_VALUE
    
    @Autowired
    private List<MyService> services; // 顺序:ServiceA → ServiceB → ServiceC
    
  • 执行顺序:在切面(AOP)、过滤器(Filter)、事件监听器等场景中,未指定值的组件最后执行。

🛠️ 4. 冲突与注意事项

  • 相同 @Order:若多个组件指定相同值(如均 @Order(1)),其相对顺序不确定。
  • @Primary的关系@Primary注解的组件在单独注入时优先,但不影响集合中的排序。
  • 性能影响:大规模组件排序可能增加启动开销,建议仅对必要组件显式指定顺序。

💎 总结

未指定 @Order的组件会以最低优先级(Integer.MAX_VALUE)参与排序,始终位于有序组件之后,且同类未指定组件间顺序不可控。若需精确控制顺序,应为所有组件显式赋值并预留间隔值(如 @Order(100)@Order(200))。

💡 最佳实践:使用常量类管理顺序值(如 OrderConstants.SECURITY_FILTER),并添加文档说明优先级逻辑。

Licensed under CC BY-NC-SA 4.0
Last updated on Aug 26, 2025 19:33 CST
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy