You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决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层不对外暴露的规则,可以把配置数据和配置注册职责拆分:

  1. 新建dev.spectre.dominance.config.props包,把PrintServiceProperties移过去,去掉@Configuration注解:
@ConfigurationProperties(prefix = "spring.printer")
public class PrintServiceProperties {
    private String url;
    // getter/setter 保持不变
}
  1. 在原Config层新增一个注册类,负责把配置类注入Spring容器:
@Configuration
@EnableConfigurationProperties(PrintServiceProperties.class)
public class PrintServiceConfig {
    // 无需额外代码,仅负责启用配置属性
}
  1. 修改架构测试规则,允许Service层访问新的ConfigProps层:
// 新增层定义
.layer("ConfigProps").definedBy("..config.props..")
// 更新Service层的访问规则
.whereLayer("Service").mayOnlyBeAccessedByLayers("Web", "Config", "ConfigProps")

优势:严格符合分层原则,配置数据和配置注册解耦,后续扩展更清晰。

方案3:封装配置服务(隐藏Config层细节)

不想动测试或包结构的话,可以在Config层封装一个配置服务,让Service依赖这个服务而非直接依赖配置类:

  1. 在Config层创建配置服务类:
@Component
public class PrintConfigService {
    private final PrintServiceProperties properties;

    public PrintConfigService(PrintServiceProperties properties) {
        this.properties = properties;
    }

    public String getPrinterUrl() {
        return properties.getUrl();
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 04:50:22