别再什么类都写 public 了!教你用 4 道防线锁死 Java 代码边界
2026/7/12 4:43:45 网站建设 项目流程

🛡️ Java 访问控制全景指南:从代码微观到物理宏观的四道防线

📖 故事背景

我们正在开发一个电商的订单模块(Order Module)。这个模块的核心任务是:

  1. 接收订单数据。
  2. 校验金额与库存。
  3. 计算折扣并保存到数据库。
  4. 调用外部的支付模块(Payment Module)进行扣款。

如果缺乏控制,团队成员可能会直接去 new 别人的内部校验类、甚至直接调用底层的数据库连接,导致架构瞬间崩塌。下面我们用四道防线,把这个系统治理得井井有条。


🛠️ 第一道防线:成员级别(Class Member Level)—— 守护类内部的“神经末梢”

这是最细粒度的控制。它的目的是:隐藏类内部的私有状态,防止外部通过“乱改属性”导致程序崩溃。

💻 业务场景

在订单实体类Order中,订单金额(totalAmount)和订单状态(status)是非常敏感的数据,绝不能让外部直接用=赋值。

📝 代码实现

packagecom.shop.order.domain;publicclassOrder{// 🔒 private:对外部绝对隐藏,只有类内部的方法能改privatedoubletotalAmount;privateStringstatus="PENDING";publicOrder(doubletotalAmount){if(totalAmount<=0){thrownewIllegalArgumentException("订单金额必须大于0!");}this.totalAmount=totalAmount;}// 🎯 只暴露只读窗口publicdoublegetTotalAmount(){returnthis.totalAmount;}// 🎯 外部只能通过业务方法修改状态,无法直接 order.status = "SUCCESS"publicvoidmarkAsPaid(){if(!"PENDING".equals(this.status)){thrownewIllegalStateException("当前状态无法支付!");}this.status="PAID";}}
  • 防线效果:外部代码只能调用order.markAsPaid(),如果外部尝试直接操作order.status = "SUCCESS",编译期就会直接报错。

📦 第二道防线:包级访问权限(Package-Private Level)—— 打造包内“黑盒”

这是日常业务开发中最重要、但最容易被忽略的防线。它的目的是:让一个包(Package)内只有唯一一个类代表对外出口,其他辅助类全部隐姓埋名。

💻 业务场景

com.shop.order.service包下,我们有核心的订单服务类、一个复杂的金额计算类、一个安全校验类。我们只希望外部知道“订单服务”,不想让他们看到计算和校验的细节。

📝 代码实现

📂 目录结构

src/com/shop/order/service/ ├── OrderService.java <-- 🎯 唯一公开出口 (public) ├── AmountCalculator.java <-- 🔒 包级私有 (不加 public) └── OrderValidator.java <-- 🔒 包级私有 (不加 public)

1. 公开出口类

packagecom.shop.order.service;importcom.shop.order.domain.Order;publicclassOrderService{// 📢 注意:有 publicpublicvoidprocessOrder(Orderorder){// ✅ 同一个包内,可以自由调用没有 public 的类OrderValidatorvalidator=newOrderValidator();AmountCalculatorcalculator=newAmountCalculator();validator.validate(order);calculator.calculateDiscount(order);System.out.println("订单处理成功!");}}

2. 隐藏的内部零件(金额计算类)

packagecom.shop.order.service;importcom.shop.order.domain.Order;// 🔒 注意:前面没有写 public!它是包级私有的classAmountCalculator{voidcalculateDiscount(Orderorder){// 方法也没有写 publicSystem.out.println("正在执行复杂的折扣计算逻辑...");}}

❌ 外部包滥用时的效果

如果有别的团队成员在com.shop.user包下尝试调用计算器:

packagecom.shop.user;// import com.shop.order.service.AmountCalculator; // ❌ 编译直接报错:无法导入publicclassUserService{publicvoidtest(){// ❌ 编译期报错:'AmountCalculator' is not public in 'com.shop.order.service'. Cannot be accessed from outside package// AmountCalculator calc = new AmountCalculator();}}
  • 防线效果:外部同学在敲代码时,代码补全提示里只会干净地出现OrderService。内部的AmountCalculator被完美隐藏,你可以随时重构它的代码,哪怕把它删了换个名字,也绝不会影响到外部任何一行代码。

🧱 第三道防线:Java 9+ 模块化系统(JPMS)—— 设立大业务板块的“海关”

当项目变大后,包级权限有个致命漏洞:防君子不防小人。如果别人在别的工程里,故意创建了一个一模一样的包名com.shop.order.service,他就能够强行 new 你的AmountCalculator。为了彻底锁死大边界,我们需要 JPMS。

💻 业务场景

我们将整个订单系统打包成一个独立的模块com.shop.order.module。我们要求:即使有些类在包里必须声明为public,但只要没有经过我“海关”的允许,外界连碰都别想碰。

📝 代码与配置实现

📂 目录结构

order-module/ └── src/ └── main/ └── java/ ├── module-info.java <-- 🎯 模块海关配置文件 └── com/shop/order/ ├── domain/Order.java └── internal/CoreEngine.java <-- 🔒 核心引擎(必须跨包,但不想对外暴露)

1. 编写海关配置module-info.java

modulecom.shop.order.module{// 📢 明确放行:只把外部需要的域模型包导出去exportscom.shop.order.domain;// ❌ 不写 exports com.shop.order.internal;// 意味着 internal 包下的所有 public 类,出了这个模块全变废纸!}

2. 模块内的核心引擎(在 internal 包下)

packagecom.shop.order.internal;publicclassCoreEngine{// 📢 注意:虽然它是 publicpublicvoidexecuteSecretAlgorithm(){System.out.println("订单系统的核心机密算法正在运行...");}}

❌ 外部模块(如支付模块)调用时的效果

如果外部的com.shop.payment模块尝试强行调用:

packagecom.shop.payment;// 即使 CoreEngine 是 public 的,因为所在的包没被 exportsimportcom.shop.order.internal.CoreEngine;// ❌ 编译期直接被 JVM 海关拦下!报错:Package 'com.shop.order.internal' is not exported by module 'com.shop.order.module'publicclassPaymentService{publicvoidpay(){// CoreEngine engine = new CoreEngine(); // ❌ 绝对无法通过编译!}}
  • 防线效果:建立了极度安全的底层防御。即便黑客通过反射(Reflection)强行获取,在没有opens关键字的情况下,运行时也会直接抛出InaccessibleObjectException

🛑 第四道防线:多模块物理拆分(Build Tools)—— 彻底切断源头依赖

这是企业级大厂最普遍采用、最铁血的手段。它的逻辑是:在你的构建工具(Maven/Gradle)里,根本不给你引入对方的 Jar 包。巧妇难为无米之炊,没引入 Jar 包,你连对方的名字都打不出来。

💻 业务场景

在电商系统里,我们严禁支付模块(Payment Module)反向调用订单模块(Order Module)(因为这会导致可怕的循环依赖:订单调支付,支付又调订单,项目直接无法编译)。

📝 Maven 配置实现

我们把项目物理拆分为两个独立的文件夹(两个独立的 Maven Project):

1. 订单模块的pom.xmlorder-system

订单系统需要调用支付,所以它声明了依赖:

<groupId>com.shop</groupId><artifactId>order-system</artifactId><version>1.0.0</version><dependencies><!-- 引入支付系统的 Jar 包 --><dependency><groupId>com.shop</groupId><artifactId>payment-system</artifactId><version>1.0.0</version></dependency></dependencies>

2. 支付模块的pom.xmlpayment-system

支付系统是独立、高冷的。它的pom.xml里绝对不写任何关于order-system的依赖。

<groupId>com.shop</groupId><artifactId>payment-system</artifactId><version>1.0.0</version><dependencies><!-- 🛑 绝对不加 order-system 的依赖! --></dependencies>

❌ 铁血效果

如果某个新人在编写PaymentService.java时,脑子一热想去调用订单:

packagecom.shop.payment;publicclassPaymentService{publicvoidprocessPayment(){// ❌ 敲下这句话的瞬间,IDEA 直接飘红,代码彻底报废// 因为 payment-system 的类路径(Classpath)里压根没有订单的类!com.shop.order.service.OrderServiceservice;}}
  • 防线效果:从物理层面切断了错误的架构设计。除非去改pom.xml,否则程序员无论动用什么黑科技(不管是反射、冒充包名还是改修饰符),在编译期都绝对无法调用订单模块。

💡 终极战术总结:架构师如何打出这套组合拳?

在实际的大型项目研发中,顶级的技术架构师绝不依赖单一防线,而是将它们层层嵌套:

  1. 宏观物理隔离(第四道):先用 Maven/Gradle 拆分大业务。payment不能依赖order,从源头掐断不良设计的苗头。
  2. 大边界海关(第三道):在被允许依赖的路径上(例如order依赖payment),通过 JPMS (module-info.java) 规定哪些是公共 SDK 包,哪些是内部包,防止底层的CoreEngine被乱用。
  3. 日常代码黑盒(第二道):在同一个子模块内部写业务代码时,大量使用包级访问权限(不加public)。每个功能包只留一个核心类作为对外的public窗口,其余小零件全部在包内消化。
  4. 属性微观保护(第一道):在具体的实体类里,把核心数据用private锁死,只通过有业务含义的方法暴露修改入口。

通过这四道防线的层层围剿,你的 Java 系统就能在面对几百人团队协作时,依然保持教科书级的、清爽完美的架构结构!


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询