如何在模块化单体Spring Boot应用中限制Gradle模块间的可见性
如何在模块化单体Spring Boot应用中限制Gradle模块间的可见性
我刚好在几个大型Spring Boot模块化单体项目里落地过类似的领域封装方案,这种把领域拆成api/impl子模块的思路已经抓住了核心,接下来咱们从Gradle配置、Spring扫描、代码权限三个层面把封装彻底落地,绝对能把内部逻辑牢牢锁在各自领域里。
1. 先把api和impl的职责划死
这是一切的基础,必须把两个子模块的边界焊死:
- api模块:只放对外暴露的内容——领域公共接口、DTO/VO、枚举、全局异常类这些,绝对不能放任何业务实现类、内部工具类,它是当前领域对外的唯一交互窗口。
- impl模块:承载所有核心业务逻辑,包括Service实现、Repository、内部工具类、事务配置等,所有不想被其他领域访问的代码全塞这里。
2. 用Gradle的java-library插件+依赖规则锁死访问
Gradle的普通java插件不够精细,换成java-library插件,它区分了api和implementation依赖,还能帮我们做更严格的依赖校验:
(1)基础依赖配置
每个领域的impl模块只能依赖自己的api模块,示例配置:
// user-management/impl/build.gradle plugins { id 'java-library' id 'org.springframework.boot' apply false } dependencies { // 内部依赖自己的api模块 implementation project(':user-management:api') // Spring相关依赖用implementation,不对外暴露 implementation 'org.springframework.boot:spring-boot-starter-data-jpa' }
其他领域如果需要和user-management交互,只能依赖它的api模块:
// notification-handling/api/build.gradle dependencies { api project(':user-management:api') }
(2)禁止跨领域依赖impl模块
在根项目的build.gradle里加全局依赖规则,一旦检测到跨领域直接依赖impl模块,直接让构建失败:
// 根项目build.gradle subprojects { plugins.withId('java-library') { configurations.all { resolutionStrategy.eachDependency { details -> def requestedPath = details.requested.path // 检查是否是impl模块,且不是当前领域下的impl if (requestedPath.contains(':impl') && !requestedPath.startsWith("${project.path.split(':')[0]}:")) { throw new GradleException("非法依赖:禁止跨领域直接访问impl模块 ${requestedPath},请依赖对应的api模块") } } } } }
这样只要有人想在notification-handling里直接依赖:user-management:impl,Gradle构建时就会抛出异常,从构建层面阻断违规依赖。
3. 限制Spring组件扫描范围,避免内部组件泄露
Spring Boot默认的组件扫描会扫所有@Component标注的类,很容易把impl里的组件泄露给其他领域,所以必须精准控制:
- 在每个impl模块的配置类里,只扫描当前impl的包:
// user-management/impl/src/main/java/com/example/usermanagement/impl/UserManagementImplConfig.java @Configuration @ComponentScan(basePackages = "com.example.usermanagement.impl") @EnableJpaRepositories(basePackages = "com.example.usermanagement.impl.repository") public class UserManagementImplConfig { }
- api模块里不要放任何带
@Component、@Service、@Repository的类,只放纯Java类和接口,避免被其他模块的组件扫描误捞。 - 对外暴露的服务必须通过api模块的接口定义,impl模块实现该接口,其他领域只能注入api接口,不能直接注入impl的实现类:
// api模块的接口 public interface UserNotificationService { void sendUserCreatedNotification(Long userId); } // impl模块的实现类,仅当前模块可见(包级私有) class UserNotificationServiceImpl implements UserNotificationService { // 内部业务逻辑 }
4. 代码层面加访问权限防护
从Java语法层面再补一层防护:
- impl模块里的非对外实现类、工具类,都用包级私有(不写访问修饰符),或者
protected,这样其他包的代码根本无法直接访问。 - 即使是对外暴露的实现类,也不要给它加
public修饰符,除非必须(但最好还是通过api接口交互)。
最后补个CI校验
可以在CI流程里加一个自定义Gradle任务,自动遍历所有模块的依赖,检查是否有违规的跨领域impl依赖,确保代码审查没覆盖到的地方也能被拦截。
内容来源于stack exchange
相关产品推荐
相关产品推荐

