exports to 是 Java 9+ 模块系统中实现定向包可见性的机制,需同时满足提供方 exports to、消费方 requires、包内类为 public 三条件;它区别于普通 exports 的广播式开放,支持最小权限控制与零信任实践。

“exports to”不是通用语法,而是 Java 9+ 模块系统(JPMS)中 module-info.java 的一项精确控制机制:它允许你声明“只把某个包导出给指定的消费模块”,而非对所有模块开放。这是实现“定向可见”的核心手段,能显著缩小攻击面、防止误用,也符合零信任中“最小权限访问”原则。
Java 中 exports to 的正确写法与前提条件
要使用 exports to,必须同时满足三个条件:
- 提供方模块的
module-info.java中明确写出exports com.example.api to com.consumer.app; - 消费方模块(
com.consumer.app)在自己的module-info.java中声明requires com.example.lib; - 被导出的包(如
com.example.api)内类本身仍是public的——但仅靠public不足以跨模块访问,必须配合exports to
若遗漏 requires 或写错模块名,编译时会报错:module not found 或 package not exported to this module。
为什么不能只用普通 exports?
普通 exports com.example.api; 是“广播式”开放:任何模块只要 requires 你,就能访问该包。这容易导致:
- 测试模块(如
com.example.test)意外依赖内部 API - 第三方插件绕过设计约束,直接调用本应隔离的逻辑
- 重构时无法安全删除旧接口,因不知谁在用
而 exports com.example.api to com.consumer.app, com.admin.tool; 明确列出白名单,其他模块即使强行 requires 也无法解析该包中的类型。
实战中常踩的坑与规避方式
常见错误不是语法写错,而是语义理解偏差:
-
误以为 opens to 和 exports to 可互换:二者完全独立。
opens专为反射(如序列化、DI 容器注入)开放私有成员,不解决编译期类型可见性;exports to解决的是 public 类型能否被其他模块引用 -
忘记导出嵌套包:如果导出
com.example.api,子包com.example.api.v2默认不包含在内,需单独写exports com.example.api.v2 to ... -
混淆模块名和包名:模块名是
module-info.java中module xxx.yyy;声明的名称,与包名无关。确保exports to后写的模块名与消费方实际声明的模块名严格一致(含大小写)
结合 opens to 实现更精细的运行时控制
当框架需反射访问(比如 Spring 加载 @Configuration 类、Jackson 反序列化 DTO),仅 exports to 不够。此时需补充:
-
opens com.example.dto to com.fasterxml.jackson.databind;—— 允许 Jackson 在运行时读写该包下类的私有字段 - 注意:
opens to的目标模块名必须是实际执行反射的模块(如 Jackson 的模块名是com.fasterxml.jackson.databind),不是你的应用模块 - 若同时需要编译期引用 + 运行时反射,就写两行:
exports com.example.dto to com.consumer.app;和opens com.example.dto to com.fasterxml.jackson.databind;

















