给定一个用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来收集运行时所需的所有传递依赖项。
是否有一种很好的方法可以让我清晰地分离图层,还可以让我收集所有需要的运行时依赖项?
答案 0 :(得分:2)
例如,对于图层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;。两个主要的警告是
答案 4 :(得分:2)
我所知道的最佳解决方案是Structure101 software。它允许您定义有关代码依赖性的规则,并在IDE或构建期间检查它们。
答案 5 :(得分:2)
如果我是你,我会做以下步骤:
答案 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-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)
对于软件结构,您需要利用最佳编码实践和设计模式。我在下面概述了几点肯定会有所帮助。
- 只能在专门的工厂类
中创建对象- 您应该编写代码并仅公开必要的&#34;接口&#34;层之间
- 您应该利用包范围(默认值)类可见性。
- 如果需要,您应该将代码拆分为单独的子项目,并且(如果需要)创建单独的jar以确保适当的层间 依赖性。
醇>
拥有良好的系统设计将完成并超越您的目标。
答案 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插件,如PDE或bndtools。
构建时设计层控制的替代工具是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本机访问修饰符(公共,私有)也有很大帮助。