在Java中实现分层架构

时间:2015-01-16 13:19:05

标签: java eclipse maven architecture dependencies

给定一个用Java编写的软件系统,包括三层,A - > B - > C,即A层使用B层,B使用C层。

我想确保一个层的类只能访问同一层的类或其直接依赖,即B应该能够访问C但不能访问A.另外A应该能够访问B但是不是C.

是否有一种简单的方法可以执行此类限制?理想情况下,如果有人试图访问错误图层的类,我希望eclipse立即投诉。

该软件目前使用maven。因此,我尝试将A,B和C放入不同的maven模块并正确声明依赖关系。这样可以防止B访问A,但不会阻止A访问C.

接下来我尝试将C从依赖项排除到B.这现在也阻止了从A到C的访问。但是现在我不再能够使用copy-dependencies来收集运行时所需的所有传递依赖项。

是否有一种很好的方法可以让我清晰地分离图层,还可以让我收集所有需要的运行时依赖项?

17 个答案:

答案 0 :(得分:2)

嗯 - 很有趣。我以前肯定遇到过这个问题,但从未尝试过实施解决方案。我想知道你是否可以将接口作为抽象层引入 - 类似于Facade模式,然后声明对它的依赖。

例如,对于图层B和C,创建新的maven项目,只包含这些图层中的接口,让我们调用这些项目B'和C'。然后,您将仅将依赖关系声明为接口层,而不是实现层。

所以A依赖于B' (只要)。 B取决于B' (因为它将实现在那里声明的接口)和C'。那么C将取决于C'。这会阻止" A使用C"问题,但你将无法获得运行时依赖项。

从那里,您需要使用maven范围标记来获取运行时依赖项(http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)。这是我真正无法探索的部分,但我认为你可以使用'运行时'范围以添加依赖项。因此,您需要添加A取决于B(具有运行时范围),类似地,B依赖于C(具有运行时范围)。使用运行时作用域不会引入编译时依赖性,因此应避免重新引入" A使用C"问题。但是,我不确定这是否会提供您正在寻找的完整传递依赖关闭。

我很想知道你是否能提出一个有效的解决方案。

答案 1 :(得分:2)

可能这不是您正在寻找的解决方案,我没有尝试过,但也许您可以尝试使用checkstyle。

想象一下,模块C中的包被称为“ org.project.modulec ... ”,模块B中的包“ org.project.moduleb .... “和模块A中的包” org.project.modulea .... “。

您可以在每个模块中配置maven-checkstyle-plugin并查找非法包名称。即在模块A中配置为非法导入名为org.project.modulec的包。 查看http://checkstyle.sourceforge.net/config_imports.html(IllegalImport)

您可以配置maven-checkstyle-plugin,每次编译检查非法导入并使编译失败。

答案 2 :(得分:2)

也许你可以在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>

这可以帮到你吗?

答案 3 :(得分:2)

我会建议一些我从未真正尝试过的东西 - 使用JDepend编写单元测试来验证架构依赖性。 JDepend documentation给出了一个例子,作为&#34;依赖性约束测试&#34;。两个主要的警告是

  1. 我还没有看到社区采用这种做法,
  2. JDepend项目似乎已被放弃。

答案 4 :(得分:2)

我所知道的最佳解决方案是Structure101 software。它允许您定义有关代码依赖性的规则,并在IDE或构建期间检查它们。

答案 5 :(得分:2)

如果我是你,我会做以下步骤:

  • 为每个图层创建两个模块。一个用于实现的另一个接口。
  • 做一个正确的maven依赖,避免传递依赖。
  • 安装Sonargraph-Architect plugin in eclipse。它可以让您配置图层规则。

答案 6 :(得分:2)

在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>

答案 7 :(得分:1)

我会从模块B中提取接口,即你将有B和B-Impl

在这种情况下,您将获得以下依赖项:

  • A取决于B
  • B-Impl取决于B和C

为了组装部署工件,您可以创建一个单独的模块,而不包含任何依赖于A和B-Impl的代码

答案 8 :(得分:1)

有一个名为archunit的项目。

我以前从未使用过它,但是您可以编写JUnit测试来验证您的体系结构。

您只需要添加以下依赖项,就可以开始编写测试。

<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit</artifactId>
    <version>0.13.1</version>
    <scope>test</scope>
</dependency>

您将遇到测试错误,但没有编译时警告,但这并不取决于IDE。

答案 9 :(得分:1)

您可以使用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,并在发生规则违规时使构建失败。

答案 10 :(得分:1)

对于软件结构,您需要利用最佳编码实践和设计模式。我在下面概述了几点肯定会有所帮助。

  
      
  1. 只能在专门的工厂类
  2. 中创建对象   
  3. 您应该编写代码并仅公开必要的&#34;接口&#34;层之间
  4.   
  5. 您应该利用包范围(默认值)类可见性。
  6.   
  7. 如果需要,您应该将代码拆分为单独的子项目,并且(如果需要)创建单独的jar以确保适当的层间   依赖性。
  8.   

拥有良好的系统设计将完成并超越您的目标。

答案 11 :(得分:1)

如果你想这样做,你需要一个只能在 A Layer 中定义的对象,它是 Layer B所需的 key Layer C 也是如此:只能通过提供(一个对象)来访问它,只能从 Layer B

这是我刚刚创建的代码,它向您展示如何使用 3个类实现此构思:

A类

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();
    }
}

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");
    }
}

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");
    }
}

每一层都是如此。

答案 12 :(得分:1)

您可以通过制作强制执行此类图层的JAR工件OSGI捆绑包来实现此目的。通过使用OSGI指令或使用工具支持手工制作您的JAR-MANIFEST(也可以通过Maven)。如果你使用Maven,你可以选择各种maven插件来实现这一目标。同样对于像Eclipse这样的IDE,您可以在其中选择不同的Eclipse插件,如PDEbndtools

构建时设计层控制的替代工具是Macker。还有一个maven plugin

答案 13 :(得分:1)

看起来你正在尝试做一些maven开箱即用的事情。

如果模块A依赖于带有排除C子句的B,则在没有明确依赖于C的情况下,A类中不能访问C类。但它们适用于B,因为B直接依赖于它们。

然后,当您打包解决方案时,您可以在模块R上运行程序集或其他任何内容,这是A,B和C的父级,并且可以毫不费力地收集它们的依赖项。

答案 14 :(得分:1)

您可以在Eclipse中为类路径工件定义访问规则。访问规则可用于映射模式,例如&#34;。com。示例*&#34;解决方案,例如&#34;禁止&#34 ;.当定义了对受限位置的导入时,这会导致编译器警告。

虽然这对小代码集非常有效,但在较大的项目中定义访问规则可能非常繁琐。请记住,这是一个专有的Eclipse功能,因此访问规则存储在特定于Eclpise的项目配置中。

要定义访问规则,请遵循以下Clickpath: 项目属性&gt; Java构建路径&gt;图书馆&gt; [您的图书馆或Maven模块]&gt;访问规则&gt;点击&#34;编辑&#34;

访问规则也可以在“设置”菜单中全局定义。

答案 15 :(得分:0)

为什么不简单地为每一层使用不同的项目?您可以将它们放入工作区并根据需要管理构建依赖项。

答案 16 :(得分:0)

如果您经常使用Spring框架,则可以使用https://github.com/odrotbohm/moduliths来查看强制模式。Oliver对此主题也有一些不错的视频演示。使用Java本机访问修饰符(公共,私有)也有很大帮助。