在 Java 中实施分层架构
Enforcing layered architecture in Java
给定一个用Java编写的软件系统,由三层组成,A -> B -> C,即A层使用B层,B层使用C层。
我想确保一层的 class 只能访问同一层的 classes 或其直接依赖项,即 B 应该能够访问 C 但不能A. 另外 A 应该可以访问 B 但不能访问 C。
有没有一种简单的方法来强制执行这样的限制?理想情况下,如果有人试图访问错误层的 class,我希望 eclipse 立即抱怨。
软件目前使用maven。因此,我尝试将 A、B 和 C 放入不同的 Maven 模块并正确声明依赖项。这可以很好地阻止 B 访问 A,但不会阻止 A 访问 C。
接下来我尝试将 C 从对 B 的依赖中排除。这现在也阻止了从 A 到 C 的访问。但是现在我不再能够使用复制依赖来收集 [=26= 所需的所有传递依赖] 时间。
有没有一种好方法可以让我清楚地分离层,还可以让我收集所有需要的运行时间依赖性?
嗯嗯 - 有趣。我以前确实 运行 遇到过这个问题,但从未尝试过实施解决方案。我想知道您是否可以将接口作为抽象层引入 - 类似于 Facade 模式,然后声明对其的依赖关系。
例如,对于 B 层和 C 层,创建仅包含这些层的接口的新 Maven 项目,我们将这些项目称为 B' 和 C'。然后,您将只声明对接口层的依赖关系,而不是实现层。
所以 A 将依赖于 B'(仅)。 B 将依赖于 B'(因为它将实现在那里声明的接口)和 C'。然后 C 将依赖于 C'。这将防止 "A uses C" 问题,但您将无法获得 运行 时间依赖性。
从那里,您将需要使用 Maven 范围标记来获取 运行 时间依赖项 (http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)。这是我真正没有探索过的部分,但我认为您可以使用 'runtime' 范围来添加依赖项。因此,您需要添加 A 依赖于 B(具有 运行 时间范围),类似地,B 依赖于 C(具有 运行 时间范围)。使用 运行time scope 不会引入编译时依赖,因此应该避免重新引入 "A uses C" 问题。但是,我不确定这是否会提供您正在寻找的完整传递依赖闭包。
我很想知道您是否能提出可行的解决方案。
我将从模块 B 中提取接口,即您将拥有 B 和 B-Impl
在这种情况下,您将获得以下依赖项:
- A依赖B
- B-Impl 依赖于 B 和 C
为了组装部署工件,您可以创建一个单独的模块,无需任何依赖于 A 和 B-Impl 的代码
可能这不是您正在寻找的解决方案,我也没有尝试过,但也许您可以尝试使用 checkstyle。
假设模块 C 中的包称为“org.project.modulec...”,模块 B 中的包称为“org.project.moduleb” ....”和模块 A 中的包“org.project.modulea.....”.
您可以在每个模块中配置 maven-checkstyle-plugin 并查找非法包名。 IE。在模块 A 中,将名为 org.project.modulec 的包的导入配置为非法。
查看 http://checkstyle.sourceforge.net/config_imports.html(非法导入)
你可以配置maven-checkstyle-plugin,每次编译检查非法导入,使编译失败。
您可以在 Eclipse 中定义类路径工件的访问规则。访问规则可用于映射模式,例如"com.example.*" 到一个决议,例如"Forbidden"。当定义到受限位置的导入时,这会导致编译器警告。
虽然这对于小型代码集非常有效,但在大型项目中定义访问规则可能会非常乏味。请记住,这是专有的 Eclipse 功能,因此访问规则存储在 Eclpise 特定项目配置中。
要定义访问规则,请遵循以下点击路径:
项目属性 > Java 构建路径 > 库 > [你的库或 Maven 模块] > 访问规则 > 单击 "Edit"
也可以在“设置”菜单中全局定义访问规则。
也许你可以在 A:
的 pom 中试试这个
<dependency>
<groupId>the.groupId</groupId>
<artifactId>moduleB</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>the.groupId</groupId>
<artifactId>moduleC</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>the.groupId</groupId>
<artifactId>moduleC</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
这对你有帮助吗?
我将建议一些我自己从未实际尝试过的方法 -- 使用 JDepend 编写单元测试以验证体系结构依赖性。 JDepend documentation 给出了一个 "Dependency Constraint Test" 的例子。两个主要警告是
- 我还没有看到社区采用这种做法,
- JDepend 项目似乎被放弃了。
我所知的最佳解决方案是 Structure101 software。它允许您定义有关代码依赖性的规则,并在 IDE 或构建期间检查它们。
看起来你正在尝试做一些 maven 开箱即用的事情。
如果模块 A 依赖于 B 并带有 exclude C 子句,则 C 类 在 A 中无法访问而不显式依赖于 C。但它们存在于 B 中,因为 B 直接依赖于它们。
然后当你打包你的解决方案时,你 运行 组件或模块 R 上的任何东西,它是 A、B 和 C 的父级,并毫不费力地收集它们的依赖关系。
您可以通过制作 JAR 工件来实现此目的 OSGI bundles that enforce such layers. Either by hand-crafting your JAR-MANIFEST (also possible via Maven) using OSGI directives or by using tool-support. If you use Maven, you can choose between a variety of maven plugins to achieve this. Likewise for IDE's like Eclipse, where you can choose between different Eclipse plugins like PDE or bndtools。
构建时设计层控制的替代工具是 Macker. There is also a maven plugin。
如果你想这样做,你需要一个只能在层中定义的对象,它是一个键 B 层 需要。 Layer C 也是如此:它只能通过提供 key(一个对象)来访问,它只能从 B层.
这是我刚刚创建的代码,向您展示了如何使用 3 Classes:
实现这个想法
Class一个:
public class A
{
/* only A can create an instance of AKey */
public final class AKey
{
private AKey() {
}
}
public A() {
B b = new B(new AKey());
b.f();
}
}
Class B:
public class B
{
/* only B can create an instance of BKey */
public final class BKey
{
private BKey() {
}
}
/* B wants an instance of AKey, and only A can create it */
public B(A.AKey key) {
if (key == null)
throw new IllegalArgumentException();
C c = new C(new BKey());
c.g();
}
public void f() {
System.out.println("I'm a method of B");
}
}
Class C:
public class C
{
/* C wants an instance of BKey, and only B can create it */
public C(B.BKey key) {
if (key == null)
throw new IllegalArgumentException();
}
public void g() {
System.out.println("I'm a method of C");
}
}
现在,如果您想将此行为扩展到特定的 层,您可以按如下所示进行操作:
A层:
public abstract class AbstractA
{
/* only SUBCLASSES can create an instance of AKey */
public final class AKey
{
protected AKey() {
}
}
}
public class A extends AbstractA
{
public A() {
B b = new B(new AKey());
b.f();
BB bb = new BB(new AKey());
bb.f();
}
}
public class AA extends AbstractA
{
public AA() {
B b = new B(new AKey());
b.f();
BB bb = new BB(new AKey());
bb.f();
}
}
B层:
public abstract class AbstractB
{
/* only SUBCLASSES can create an instance of BKey */
public final class BKey
{
protected BKey() {
}
}
}
public class B extends AbstractB
{
/* B wants an instance of AKey, and only A Layer can create it */
public B(AbstractA.AKey key) {
if (key == null)
throw new IllegalArgumentException();
C c = new C(new BKey());
c.g();
CC cc = new CC(new BKey());
cc.g();
}
public void f() {
System.out.println("I'm a method of B");
}
}
public class BB extends AbstractB
{
/* BB wants an instance of AKey, and only A Layer can create it */
public BB(AbstractA.AKey key) {
if (key == null)
throw new IllegalArgumentException();
C c = new C(new BKey());
c.g();
CC cc = new CC(new BKey());
cc.g();
}
public void f() {
System.out.println("I'm a method of BB");
}
}
C层:
public class C
{
/* C wants an instance of BKey, and only B Layer can create it */
public C(B.BKey key) {
if (key == null)
throw new IllegalArgumentException();
}
public void g() {
System.out.println("I'm a method of C");
}
}
public class CC
{
/* CC wants an instance of BKey, and only B Layer can create it */
public CC(B.BKey key) {
if (key == null)
throw new IllegalArgumentException();
}
public void g() {
System.out.println("I'm a method of CC");
}
}
每一层依此类推。
对于软件结构,您需要利用最佳编码实践和设计模式。我在下面列出了几点肯定会有所帮助。
- Creation of object(s) should be done only in the specialized Factory class(es)
- You should code-to and expose only the necessary "interfaces" between layers
- You should take advantage of the package scope (default one) class visibility.
- If necessary you should split your code into separate sub-projects and (if needed) create separate jar(s) to assure proper inter-layer
dependency.
拥有良好的系统设计将完成并超越您的目标。
在 maven 中,您可以使用 maven-macker-plugin,如下例所示:
<build>
<plugins>
<plugin>
<groupId>de.andrena.tools.macker</groupId>
<artifactId>macker-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>macker</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
这里是一个 macker-rules.xml 示例文件:(将其放在与您的 pom.xml 相同的级别)
<?xml version="1.0"?>
<macker>
<ruleset name="Layering rules">
<var name="base" value="org.example" />
<pattern name="appl" class="${base}.**" />
<pattern name="common" class="${base}.common.**" />
<pattern name="persistence" class="${base}.persistence.**" />
<pattern name="business" class="${base}.business.**" />
<pattern name="web" class="${base}.web.**" />
<!-- =============================================================== -->
<!-- Common -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf common; von überall gestattet</message>
<deny>
<to pattern="common" />
<allow>
<from>
<include pattern="appl" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Persistence -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf persistence; von web und business gestattet</message>
<deny>
<to pattern="persistence" />
<allow>
<from>
<include pattern="persistence" />
<include pattern="web" />
<include pattern="business" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Business -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf business; nur von web gestattet</message>
<deny>
<to pattern="business" />
<allow>
<from>
<include pattern="business" />
<include pattern="web" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Web -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf web; von nirgends gestattet</message>
<deny>
<to pattern="web" />
<allow>
<from>
<include pattern="web" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Libraries gebunden an ein spezifisches Modul -->
<!-- =============================================================== -->
<access-rule>
<message>nur in web erlaubt</message>
<deny>
<to>
<include class="javax.faces.**" />
<include class="javax.servlet.**" />
<include class="javax.ws.*" />
<include class="javax.enterprise.*" />
</to>
<allow>
<from pattern="web" />
</allow>
</deny>
</access-rule>
<access-rule>
<message>nur in business und persistence erlaubt</message>
<deny>
<to>
<include class="javax.ejb.**" />
<include class="java.sql.**" />
<include class="javax.sql.**" />
<include class="javax.persistence.**" />
</to>
<allow>
<from>
<include pattern="business" />
<include pattern="persistence" />
</from>
</allow>
</deny>
</access-rule>
</ruleset>
</macker>
并且在一个简单的多模块maven项目中只需将macker-rules.xml放在一个中心位置并指向它存储的目录。
那么你需要在你的父级中配置插件 pom.xml
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>de.andrena.tools.macker</groupId>
<artifactId>macker-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>macker</goal>
</goals>
<configuration>
<rulesDirectory>../</rulesDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
如果我是你,我会执行以下步骤:
- 为每一层创建两个模块。一个用于接口,另一个用于实现。
- 做一个适当的 maven 依赖,避免传递依赖。
- 安装Sonargraph-Architect plugin in eclipse。它会让你配置你的图层规则。
您可以使用 Sonargraph's 新 DSL 描述您的架构:
artifact A
{
// Pattern matching classes belonging to A
include "**/a/**"
connect to B
}
artifact B
{
include "**/b/**"
connect to C
}
artifact C
{
include "**/c/**"
}
DSL 在一系列 BLOG articles.
中进行了描述
然后您可以 运行 通过 Maven 或 Gradle 或您的构建中的类似方法 运行 Sonargraph,并在发生违反规则时使构建失败。
为什么不简单地为每一层使用不同的项目?您将它们放入您的工作区并根据需要管理构建依赖项。
有一个名为 archunit 的项目。
我以前从未使用过它,但您可以编写 JUnit 测试来验证您的架构。
只需要添加如下依赖,就可以开始写测试了
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>
你会有测试错误,但不是编译时警告,而是不依赖于IDE。
如果你经常使用 Spring 框架,你可以看看使用 https://github.com/odrotbohm/moduliths 的强制模式 Oliver 也有一些关于这个主题的不错的视频演示。使用 java 本机访问修饰符(public、私有)也有很大帮助。
给定一个用Java编写的软件系统,由三层组成,A -> B -> C,即A层使用B层,B层使用C层。
我想确保一层的 class 只能访问同一层的 classes 或其直接依赖项,即 B 应该能够访问 C 但不能A. 另外 A 应该可以访问 B 但不能访问 C。
有没有一种简单的方法来强制执行这样的限制?理想情况下,如果有人试图访问错误层的 class,我希望 eclipse 立即抱怨。
软件目前使用maven。因此,我尝试将 A、B 和 C 放入不同的 Maven 模块并正确声明依赖项。这可以很好地阻止 B 访问 A,但不会阻止 A 访问 C。
接下来我尝试将 C 从对 B 的依赖中排除。这现在也阻止了从 A 到 C 的访问。但是现在我不再能够使用复制依赖来收集 [=26= 所需的所有传递依赖] 时间。
有没有一种好方法可以让我清楚地分离层,还可以让我收集所有需要的运行时间依赖性?
嗯嗯 - 有趣。我以前确实 运行 遇到过这个问题,但从未尝试过实施解决方案。我想知道您是否可以将接口作为抽象层引入 - 类似于 Facade 模式,然后声明对其的依赖关系。
例如,对于 B 层和 C 层,创建仅包含这些层的接口的新 Maven 项目,我们将这些项目称为 B' 和 C'。然后,您将只声明对接口层的依赖关系,而不是实现层。
所以 A 将依赖于 B'(仅)。 B 将依赖于 B'(因为它将实现在那里声明的接口)和 C'。然后 C 将依赖于 C'。这将防止 "A uses C" 问题,但您将无法获得 运行 时间依赖性。
从那里,您将需要使用 Maven 范围标记来获取 运行 时间依赖项 (http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)。这是我真正没有探索过的部分,但我认为您可以使用 'runtime' 范围来添加依赖项。因此,您需要添加 A 依赖于 B(具有 运行 时间范围),类似地,B 依赖于 C(具有 运行 时间范围)。使用 运行time scope 不会引入编译时依赖,因此应该避免重新引入 "A uses C" 问题。但是,我不确定这是否会提供您正在寻找的完整传递依赖闭包。
我很想知道您是否能提出可行的解决方案。
我将从模块 B 中提取接口,即您将拥有 B 和 B-Impl
在这种情况下,您将获得以下依赖项:
- A依赖B
- B-Impl 依赖于 B 和 C
为了组装部署工件,您可以创建一个单独的模块,无需任何依赖于 A 和 B-Impl 的代码
可能这不是您正在寻找的解决方案,我也没有尝试过,但也许您可以尝试使用 checkstyle。
假设模块 C 中的包称为“org.project.modulec...”,模块 B 中的包称为“org.project.moduleb” ....”和模块 A 中的包“org.project.modulea.....”.
您可以在每个模块中配置 maven-checkstyle-plugin 并查找非法包名。 IE。在模块 A 中,将名为 org.project.modulec 的包的导入配置为非法。 查看 http://checkstyle.sourceforge.net/config_imports.html(非法导入)
你可以配置maven-checkstyle-plugin,每次编译检查非法导入,使编译失败。
您可以在 Eclipse 中定义类路径工件的访问规则。访问规则可用于映射模式,例如"com.example.*" 到一个决议,例如"Forbidden"。当定义到受限位置的导入时,这会导致编译器警告。
虽然这对于小型代码集非常有效,但在大型项目中定义访问规则可能会非常乏味。请记住,这是专有的 Eclipse 功能,因此访问规则存储在 Eclpise 特定项目配置中。
要定义访问规则,请遵循以下点击路径: 项目属性 > Java 构建路径 > 库 > [你的库或 Maven 模块] > 访问规则 > 单击 "Edit"
也可以在“设置”菜单中全局定义访问规则。
也许你可以在 A:
的 pom 中试试这个<dependency>
<groupId>the.groupId</groupId>
<artifactId>moduleB</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>the.groupId</groupId>
<artifactId>moduleC</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>the.groupId</groupId>
<artifactId>moduleC</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
这对你有帮助吗?
我将建议一些我自己从未实际尝试过的方法 -- 使用 JDepend 编写单元测试以验证体系结构依赖性。 JDepend documentation 给出了一个 "Dependency Constraint Test" 的例子。两个主要警告是
- 我还没有看到社区采用这种做法,
- JDepend 项目似乎被放弃了。
我所知的最佳解决方案是 Structure101 software。它允许您定义有关代码依赖性的规则,并在 IDE 或构建期间检查它们。
看起来你正在尝试做一些 maven 开箱即用的事情。
如果模块 A 依赖于 B 并带有 exclude C 子句,则 C 类 在 A 中无法访问而不显式依赖于 C。但它们存在于 B 中,因为 B 直接依赖于它们。
然后当你打包你的解决方案时,你 运行 组件或模块 R 上的任何东西,它是 A、B 和 C 的父级,并毫不费力地收集它们的依赖关系。
您可以通过制作 JAR 工件来实现此目的 OSGI bundles that enforce such layers. Either by hand-crafting your JAR-MANIFEST (also possible via Maven) using OSGI directives or by using tool-support. If you use Maven, you can choose between a variety of maven plugins to achieve this. Likewise for IDE's like Eclipse, where you can choose between different Eclipse plugins like PDE or bndtools。
构建时设计层控制的替代工具是 Macker. There is also a maven plugin。
如果你想这样做,你需要一个只能在层中定义的对象,它是一个键 B 层 需要。 Layer C 也是如此:它只能通过提供 key(一个对象)来访问,它只能从 B层.
这是我刚刚创建的代码,向您展示了如何使用 3 Classes:
实现这个想法Class一个:
public class A
{
/* only A can create an instance of AKey */
public final class AKey
{
private AKey() {
}
}
public A() {
B b = new B(new AKey());
b.f();
}
}
Class B:
public class B
{
/* only B can create an instance of BKey */
public final class BKey
{
private BKey() {
}
}
/* B wants an instance of AKey, and only A can create it */
public B(A.AKey key) {
if (key == null)
throw new IllegalArgumentException();
C c = new C(new BKey());
c.g();
}
public void f() {
System.out.println("I'm a method of B");
}
}
Class C:
public class C
{
/* C wants an instance of BKey, and only B can create it */
public C(B.BKey key) {
if (key == null)
throw new IllegalArgumentException();
}
public void g() {
System.out.println("I'm a method of C");
}
}
现在,如果您想将此行为扩展到特定的 层,您可以按如下所示进行操作:
A层:
public abstract class AbstractA
{
/* only SUBCLASSES can create an instance of AKey */
public final class AKey
{
protected AKey() {
}
}
}
public class A extends AbstractA
{
public A() {
B b = new B(new AKey());
b.f();
BB bb = new BB(new AKey());
bb.f();
}
}
public class AA extends AbstractA
{
public AA() {
B b = new B(new AKey());
b.f();
BB bb = new BB(new AKey());
bb.f();
}
}
B层:
public abstract class AbstractB
{
/* only SUBCLASSES can create an instance of BKey */
public final class BKey
{
protected BKey() {
}
}
}
public class B extends AbstractB
{
/* B wants an instance of AKey, and only A Layer can create it */
public B(AbstractA.AKey key) {
if (key == null)
throw new IllegalArgumentException();
C c = new C(new BKey());
c.g();
CC cc = new CC(new BKey());
cc.g();
}
public void f() {
System.out.println("I'm a method of B");
}
}
public class BB extends AbstractB
{
/* BB wants an instance of AKey, and only A Layer can create it */
public BB(AbstractA.AKey key) {
if (key == null)
throw new IllegalArgumentException();
C c = new C(new BKey());
c.g();
CC cc = new CC(new BKey());
cc.g();
}
public void f() {
System.out.println("I'm a method of BB");
}
}
C层:
public class C
{
/* C wants an instance of BKey, and only B Layer can create it */
public C(B.BKey key) {
if (key == null)
throw new IllegalArgumentException();
}
public void g() {
System.out.println("I'm a method of C");
}
}
public class CC
{
/* CC wants an instance of BKey, and only B Layer can create it */
public CC(B.BKey key) {
if (key == null)
throw new IllegalArgumentException();
}
public void g() {
System.out.println("I'm a method of CC");
}
}
每一层依此类推。
对于软件结构,您需要利用最佳编码实践和设计模式。我在下面列出了几点肯定会有所帮助。
- Creation of object(s) should be done only in the specialized Factory class(es)
- You should code-to and expose only the necessary "interfaces" between layers
- You should take advantage of the package scope (default one) class visibility.
- If necessary you should split your code into separate sub-projects and (if needed) create separate jar(s) to assure proper inter-layer dependency.
拥有良好的系统设计将完成并超越您的目标。
在 maven 中,您可以使用 maven-macker-plugin,如下例所示:
<build>
<plugins>
<plugin>
<groupId>de.andrena.tools.macker</groupId>
<artifactId>macker-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>macker</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
这里是一个 macker-rules.xml 示例文件:(将其放在与您的 pom.xml 相同的级别)
<?xml version="1.0"?>
<macker>
<ruleset name="Layering rules">
<var name="base" value="org.example" />
<pattern name="appl" class="${base}.**" />
<pattern name="common" class="${base}.common.**" />
<pattern name="persistence" class="${base}.persistence.**" />
<pattern name="business" class="${base}.business.**" />
<pattern name="web" class="${base}.web.**" />
<!-- =============================================================== -->
<!-- Common -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf common; von überall gestattet</message>
<deny>
<to pattern="common" />
<allow>
<from>
<include pattern="appl" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Persistence -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf persistence; von web und business gestattet</message>
<deny>
<to pattern="persistence" />
<allow>
<from>
<include pattern="persistence" />
<include pattern="web" />
<include pattern="business" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Business -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf business; nur von web gestattet</message>
<deny>
<to pattern="business" />
<allow>
<from>
<include pattern="business" />
<include pattern="web" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Web -->
<!-- =============================================================== -->
<access-rule>
<message>zugriff auf web; von nirgends gestattet</message>
<deny>
<to pattern="web" />
<allow>
<from>
<include pattern="web" />
</from>
</allow>
</deny>
</access-rule>
<!-- =============================================================== -->
<!-- Libraries gebunden an ein spezifisches Modul -->
<!-- =============================================================== -->
<access-rule>
<message>nur in web erlaubt</message>
<deny>
<to>
<include class="javax.faces.**" />
<include class="javax.servlet.**" />
<include class="javax.ws.*" />
<include class="javax.enterprise.*" />
</to>
<allow>
<from pattern="web" />
</allow>
</deny>
</access-rule>
<access-rule>
<message>nur in business und persistence erlaubt</message>
<deny>
<to>
<include class="javax.ejb.**" />
<include class="java.sql.**" />
<include class="javax.sql.**" />
<include class="javax.persistence.**" />
</to>
<allow>
<from>
<include pattern="business" />
<include pattern="persistence" />
</from>
</allow>
</deny>
</access-rule>
</ruleset>
</macker>
并且在一个简单的多模块maven项目中只需将macker-rules.xml放在一个中心位置并指向它存储的目录。 那么你需要在你的父级中配置插件 pom.xml
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>de.andrena.tools.macker</groupId>
<artifactId>macker-maven-plugin</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>macker</goal>
</goals>
<configuration>
<rulesDirectory>../</rulesDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
如果我是你,我会执行以下步骤:
- 为每一层创建两个模块。一个用于接口,另一个用于实现。
- 做一个适当的 maven 依赖,避免传递依赖。
- 安装Sonargraph-Architect plugin in eclipse。它会让你配置你的图层规则。
您可以使用 Sonargraph's 新 DSL 描述您的架构:
artifact A
{
// Pattern matching classes belonging to A
include "**/a/**"
connect to B
}
artifact B
{
include "**/b/**"
connect to C
}
artifact C
{
include "**/c/**"
}
DSL 在一系列 BLOG articles.
中进行了描述然后您可以 运行 通过 Maven 或 Gradle 或您的构建中的类似方法 运行 Sonargraph,并在发生违反规则时使构建失败。
为什么不简单地为每一层使用不同的项目?您将它们放入您的工作区并根据需要管理构建依赖项。
有一个名为 archunit 的项目。
我以前从未使用过它,但您可以编写 JUnit 测试来验证您的架构。
只需要添加如下依赖,就可以开始写测试了
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>
你会有测试错误,但不是编译时警告,而是不依赖于IDE。
如果你经常使用 Spring 框架,你可以看看使用 https://github.com/odrotbohm/moduliths 的强制模式 Oliver 也有一些关于这个主题的不错的视频演示。使用 java 本机访问修饰符(public、私有)也有很大帮助。