如何解决ArchTest中Config层被Service层访问的违规问题
问题
项目违反了架构测试规则,搞不懂为啥Config层不能被其他层访问。用@Value直接从application.properties拿配置虽然能行,但明显不规范。现在项目里已经有一大堆@Configuration文件集中在config包,还对应不同的*.YML文件,想保留这个模式,求解决违规问题的同时,让Service等其他层能正常获取Config层的配置数据。
相关代码示例
架构测试代码
@AnalyzeClasses(packagesOf = MySecretApp.class, importOptions = DoNotIncludeTests.class) class TechnicalStructureTest { // prettier-ignore @ArchTest static final ArchRule respectsTechnicalArchitectureLayers = layeredArchitecture() .layer("Config").definedBy("..config..") .layer("Web").definedBy("..web..") .optionalLayer("Service").definedBy("..service..") .layer("Security").definedBy("..security..") .layer("Persistence").definedBy("..repository..") .layer("Domain").definedBy("..domain..") .whereLayer("Config").mayNotBeAccessedByAnyLayer() .whereLayer("Web").mayOnlyBeAccessedByLayers("Config") .whereLayer("Service").mayOnlyBeAccessedByLayers("Web", "Config") .whereLayer("Security").mayOnlyBeAccessedByLayers("Config", "Service", "Web") .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service", "Security", "Web", "Config") .whereLayer("Domain").mayOnlyBeAccessedByLayers("Persistence", "Service", "Security", "Web", "Config") .ignoreDependency(belongToAnyOf(MySecretApp.class), alwaysTrue()) .ignoreDependency(alwaysTrue(), belongToAnyOf( dev.spectre.dominance.config.Constants.class, dev.spectre.dominance.config.ApplicationProperties.class )); }
报错示例
Constructor <dev.spectre.dominance.service.PrintService.<init>(dev.spectre.dominance.config.PrintServiceProperties)> has parameter of type <dev.spectre.dominance.PrintServiceProperties> in (PrintService.java:0)
Service类代码
@Service public class PrintService { private final PrintServiceProperties printServiceProperties; public PrintService(PrintServiceProperties printServiceProperties) { this.printServiceProperties = printServiceProperties; } @Async public void sendPrintjob(PrintJob job) { log.debug("Will send print job to Kafka with content: {} ", job.toString()); RestTemplate template = new RestTemplate(); template.postForEntity(printServiceProperties.getUrl(), job, PrintJobAnswer.class); } }
Config类代码
@Configuration @ConfigurationProperties(prefix = "spring.printer") public class PrintServiceProperties { private String url; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
解决方案
方案1:扩展架构测试的忽略规则(最快最省心)
既然测试里已经有忽略ApplicationProperties的先例,直接把PrintServiceProperties加入忽略列表就行,完全保留现有结构:
修改架构测试中的ignoreDependency部分,新增一行:
.ignoreDependency(alwaysTrue(), belongToAnyOf( dev.spectre.dominance.config.Constants.class, dev.spectre.dominance.config.ApplicationProperties.class, dev.spectre.dominance.config.PrintServiceProperties.class // 新增这行 ));
优势:改动极小,不用调整任何业务代码,立刻解决违规问题,完全保留现有配置模式。
方案2:拆分配置类(严格遵循架构分层)
如果想严格遵守Config层不对外暴露的规则,可以把配置数据和配置注册职责拆分:
- 新建
dev.spectre.dominance.config.props包,把PrintServiceProperties移过去,去掉@Configuration注解:
@ConfigurationProperties(prefix = "spring.printer") public class PrintServiceProperties { private String url; // getter/setter 保持不变 }
- 在原Config层新增一个注册类,负责把配置类注入Spring容器:
@Configuration @EnableConfigurationProperties(PrintServiceProperties.class) public class PrintServiceConfig { // 无需额外代码,仅负责启用配置属性 }
- 修改架构测试规则,允许Service层访问新的
ConfigProps层:
// 新增层定义 .layer("ConfigProps").definedBy("..config.props..") // 更新Service层的访问规则 .whereLayer("Service").mayOnlyBeAccessedByLayers("Web", "Config", "ConfigProps")
优势:严格符合分层原则,配置数据和配置注册解耦,后续扩展更清晰。
方案3:封装配置服务(隐藏Config层细节)
不想动测试或包结构的话,可以在Config层封装一个配置服务,让Service依赖这个服务而非直接依赖配置类:
- 在Config层创建配置服务类:
@Component public class PrintConfigService { private final PrintServiceProperties properties; public PrintConfigService(PrintServiceProperties properties) { this.properties = properties; } public String getPrinterUrl() { return properties.getUrl(); } }
- 修改
PrintService,依赖PrintConfigService:
@Service public class PrintService { private final PrintConfigService printConfigService; public PrintService(PrintConfigService printConfigService) { this.printConfigService = printConfigService; } @Async public void sendPrintjob(PrintJob job) { // 改用封装后的方法获取配置 template.postForEntity(printConfigService.getPrinterUrl(), job, PrintJobAnswer.class); } }
优势:隐藏Config层内部细节,Service层只依赖业务化的配置服务,符合依赖倒置原则。
内容的提问来源于stack exchange,提问作者Semo
相关产品推荐
相关产品推荐

