别再什么类都写 public 了!教你用 4 道防线锁死 Java 代码边界

别再什么类都写 public 了!教你用 4 道防线锁死 Java 代码边界
️ Java 访问控制全景指南从代码微观到物理宏观的四道防线 故事背景我们正在开发一个电商的订单模块Order Module。这个模块的核心任务是接收订单数据。校验金额与库存。计算折扣并保存到数据库。调用外部的支付模块Payment Module进行扣款。如果缺乏控制团队成员可能会直接去 new 别人的内部校验类、甚至直接调用底层的数据库连接导致架构瞬间崩塌。下面我们用四道防线把这个系统治理得井井有条。️ 第一道防线成员级别Class Member Level—— 守护类内部的“神经末梢”这是最细粒度的控制。它的目的是隐藏类内部的私有状态防止外部通过“乱改属性”导致程序崩溃。 业务场景在订单实体类Order中订单金额totalAmount和订单状态status是非常敏感的数据绝不能让外部直接用赋值。 代码实现packagecom.shop.order.domain;publicclassOrder{// private对外部绝对隐藏只有类内部的方法能改privatedoubletotalAmount;privateStringstatusPENDING;publicOrder(doubletotalAmount){if(totalAmount0){thrownewIllegalArgumentException(订单金额必须大于0);}this.totalAmounttotalAmount;}// 只暴露只读窗口publicdoublegetTotalAmount(){returnthis.totalAmount;}// 外部只能通过业务方法修改状态无法直接 order.status SUCCESSpublicvoidmarkAsPaid(){if(!PENDING.equals(this.status)){thrownewIllegalStateException(当前状态无法支付);}this.statusPAID;}}防线效果外部代码只能调用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 的类OrderValidatorvalidatornewOrderValidator();AmountCalculatorcalculatornewAmountCalculator();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.javamodulecom.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.modulepublicclassPaymentService{publicvoidpay(){// CoreEngine engine new CoreEngine(); // ❌ 绝对无法通过编译}}防线效果建立了极度安全的底层防御。即便黑客通过反射Reflection强行获取在没有opens关键字的情况下运行时也会直接抛出InaccessibleObjectException。 第四道防线多模块物理拆分Build Tools—— 彻底切断源头依赖这是企业级大厂最普遍采用、最铁血的手段。它的逻辑是在你的构建工具Maven/Gradle里根本不给你引入对方的 Jar 包。巧妇难为无米之炊没引入 Jar 包你连对方的名字都打不出来。 业务场景在电商系统里我们严禁支付模块Payment Module反向调用订单模块Order Module因为这会导致可怕的循环依赖订单调支付支付又调订单项目直接无法编译。 Maven 配置实现我们把项目物理拆分为两个独立的文件夹两个独立的 Maven Project1. 订单模块的pom.xmlorder-system订单系统需要调用支付所以它声明了依赖groupIdcom.shop/groupIdartifactIdorder-system/artifactIdversion1.0.0/versiondependencies!-- 引入支付系统的 Jar 包 --dependencygroupIdcom.shop/groupIdartifactIdpayment-system/artifactIdversion1.0.0/version/dependency/dependencies2. 支付模块的pom.xmlpayment-system支付系统是独立、高冷的。它的pom.xml里绝对不写任何关于order-system的依赖。groupIdcom.shop/groupIdartifactIdpayment-system/artifactIdversion1.0.0/versiondependencies!-- 绝对不加 order-system 的依赖 --/dependencies❌ 铁血效果如果某个新人在编写PaymentService.java时脑子一热想去调用订单packagecom.shop.payment;publicclassPaymentService{publicvoidprocessPayment(){// ❌ 敲下这句话的瞬间IDEA 直接飘红代码彻底报废// 因为 payment-system 的类路径Classpath里压根没有订单的类com.shop.order.service.OrderServiceservice;}}防线效果从物理层面切断了错误的架构设计。除非去改pom.xml否则程序员无论动用什么黑科技不管是反射、冒充包名还是改修饰符在编译期都绝对无法调用订单模块。 终极战术总结架构师如何打出这套组合拳在实际的大型项目研发中顶级的技术架构师绝不依赖单一防线而是将它们层层嵌套宏观物理隔离第四道先用 Maven/Gradle 拆分大业务。payment不能依赖order从源头掐断不良设计的苗头。大边界海关第三道在被允许依赖的路径上例如order依赖payment通过 JPMS (module-info.java) 规定哪些是公共 SDK 包哪些是内部包防止底层的CoreEngine被乱用。日常代码黑盒第二道在同一个子模块内部写业务代码时大量使用包级访问权限不加public。每个功能包只留一个核心类作为对外的public窗口其余小零件全部在包内消化。属性微观保护第一道在具体的实体类里把核心数据用private锁死只通过有业务含义的方法暴露修改入口。通过这四道防线的层层围剿你的 Java 系统就能在面对几百人团队协作时依然保持教科书级的、清爽完美的架构结构