如何确定Java应用程序的主类?

时间:2018-04-30 21:47:13

标签: java spock

我们正在开发一个平台,许多开发人员将编写自己的ETL应用程序,这些应用程序使用供应商的API,然后将其提交到平台上执行。我们希望在编写Main类(通常只使用供应商的API)时将开发人员从他们自己的东西限制,以便促进一些强烈持有的约定。 (大)组织有一种人们做自己的事情的文化,多年来导致一些非常讨厌的架构,所以我们想强加一些可以通过CI / CD实施的最佳实践惯例,这将有助于培养代码共享。另一种选择是对所有人免费回归规范,我们迫切希望避免这种规范。

我们如何确定应用程序的主要类是什么?我们怎样才能测试呢?我们想要定义开发人员使用的抽象类或接口,它将定义开发人员必须遵守的一些前期承诺(否则测试将失败)。我们无法修改供应商的代码。

因此,更具体地说,目前我们有:

public class MyNastyFreeForAll {
    public static void main(String[] ) {
        //...
    }
}

有没有办法检测/执行类似的内容:

public class MyConventionEnforcingClass implements/extends MyConventions {
    public static void main(String[] ) {
        //...
    }
}

即。测试应用程序的主类使用从MyConventions派生的东西?

理想情况下,我想使用Spock运行测试。

或者,是否有更好的方法来实现这一目标?我担心,在一个没有中央控制/层级的大量独立团队中这样规模的组织中的代码审查不会削减它。

编辑反思意见中的输入:

从本质上讲,这是一个人的问题。然而,1000多岁的人口和文化变迁不会在一夜之间发生。它不会仅仅通过教育,记录和影响,从而希望人们做正确的事情。我正在寻找一种技术解决方案,可以轻轻地引导我们的开发人员做正确的事情 - 如果他们愿意,他们总是可以颠覆这一点,但我想要他们如果他们想要这样做,他们会尽力去做。这是因为我正在寻找一个我在SO上发布的技术解决方案,而不是寻求如何在另一个网站上推动文化变革的指导。

编辑提供MCVE:

这是使用抽象类的示例。验证(在编译或测试时)主类派生自MyConventions会很好。如果一个用户想要主动颠覆这一点,那么就这样吧 - 你可以把马带到水中 - 但是我试图让最终用户更容易做正确的事而不是做正确的事情。简单地给他们一个为他们做样板的课程是不够的,因为这些用户喜欢做他们自己的事情并且可能会忽略你,所以应该有某种形式的轻触技术执行。没有尝试在doProcessing()上强加惯例,但可以使用相同的原则来添加前后方法等来实现这一目标。

如果有另一种实现这一目标的方式,那么我对这些想法非常感兴趣。

// MyNastyFreeForAll.java
// written by end-user
public class MyNastyFreeForAll {
    public static void main(String[] args) {
        MyNastyFreeForAll entrypoint = new MyNastyFreeForAll();
        entrypoint.doBoilerplate();
        entrypoint.doProcessing();
    }

    private void doBoilerplate() {
        // lot of setup stuff here, where the user can go astray
        // would like to provide this in a class, perhaps
        // but we need to be able to enforce that the user uses this class
        // and doesn't simply try to roll their own.
        System.out.println("Doing boilerplate my own way");

    }

    private void doProcessing() {
        // more things that the user can misuse
        System.out.println("Doing doProcessing my way");

    }
}

// MyConventions.java
// written by team that knows how to set things up well/correctly
public abstract class MyConventions {

    public void doBoilerplate() {
      System.out.println("Doing boilerplate the correct way");
    }

    public abstract void doProcessing();

}

// MyConventionsImpl.java
// written by end-user
public class MyConventionsImpl extends MyConventions {

  public static void main(String[] args) {
    MyConventions entrypoint = new MyConventionsImpl();
    entrypoint.doBoilerplate();
    entrypoint.doProcessing();
  }

  public void doProcessing() {
    System.out.println("Doing doProcessing my way");

  }
}

1 个答案:

答案 0 :(得分:1)

您和其他部门可以使用AspectJ编译器编译所有代码,可以从命令行手动,通过批处理文件,通过为AspectJ配置的IDE(例如Eclipse,IDEA)或通过Maven编译。我在GitHub上为您创建了Maven设置,just clone the project。对不起,它没有使用你的MCVE课程,因为我看到它们太晚了,不想重新开始。

接口方法

现在让我们假设所有符合要求的应用程序都需要实现接口

package de.scrum_master.base;

public interface BasicInterface {
  void doSomething(String name);
  String convert(int number);
}
package de.scrum_master.app;

import de.scrum_master.base.BasicInterface;

public class ApplicationOne implements BasicInterface {
  @Override
  public void doSomething(String name) {
    System.out.println("Doing something with " + name);
  }

  @Override
  public String convert(int number) {
    return new Integer(number).toString();
  }

  public static void main(String[] args) {
    System.out.println("BasicInterface implementation");
    ApplicationOne application = new ApplicationOne();
    application.doSomething("Joe");
    System.out.println("Converted number = " + application.convert(11));
  }
}

基类方法

或者,有一个基类应用程序必须扩展:

package de.scrum_master.base;

public abstract class ApplicationBase {
  public abstract void doSomething(String name);

  public String convert(int number) {
    return ((Integer) number).toString();
  }
}
package de.scrum_master.app;

import de.scrum_master.base.ApplicationBase;

public class ApplicationTwo extends ApplicationBase {
  @Override
  public void doSomething(String name) {
    System.out.println("Doing something with " + name);
  }

  public static void main(String[] args) {
    System.out.println("ApplicationBase subclass");
    ApplicationTwo application = new ApplicationTwo();
    application.doSomething("Joe");
    System.out.println("Converted number = " + application.convert(11));
  }
}

不需要的申请

现在我们有一个应用程序可以自己做,既不实现接口也不扩展基类:

package de.scrum_master.app;

public class UnwantedApplication {
  public void sayHello(String name) {
    System.out.println("Hello " + name);
  }

  public String transform(int number) {
    return new Integer(number).toString();
  }

  public static void main(String[] args) {
    System.out.println("Unwanted application");
    UnwantedApplication application = new UnwantedApplication();
    application.sayHello("Joe");
    System.out.println("Transformed number = " + application.transform(11));
  }
}

合同执行方面

现在让我们编写一个AspectJ方面,它通过declare error产生编译错误(也可以通过declare warning发出警告,但这不会强制执行任何操作,只报告问题)。 / p>

package de.scrum_master.aspect;

import de.scrum_master.base.BasicInterface;
import de.scrum_master.base.ApplicationBase;

public aspect ApplicationContractEnforcer {
  declare error :
    within(de.scrum_master..*) &&
    execution(public static void main(String[])) &&
    !within(BasicInterface+) &&
    !within(ApplicationBase+)
  : "Applications with main methods have to implement BasicInterface or extend ApplicationBase";
}

此代码的含义是:在de.scrum_master或任何子包中查找包含主要方法的所有类,但不实现BasicInterface而不是扩展ApplicationBase。实际上,您当然只会选择后两个标准中的一个。我在这里做两件事给你一个选择。如果找到任何此类,则会显示带有指定错误消息的编译器错误。

无论出于何种原因,有些人不喜欢精彩表达的AspectJ本地语言(Java语法的超集),但更喜欢编写丑陋的注释风格方面,将所有方面切入点打包成字符串常量。这是相同的方面,只是在另一种语法中。选择任何。 (在GitHub项目中,我通过搜索不存在的包xde.scrum_master来停用原生方面,以避免双重编译错误。)

package de.scrum_master.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareError;

@Aspect
public class ApplicationContractEnforcer2 {
  @DeclareError(
    "within(de.scrum_master..*) && " +
    "execution(public static void main(String[])) && " +
    "!within(de.scrum_master.base.BasicInterface+) && " +
    "!within(de.scrum_master.base.ApplicationBase+)"
  )
  static final String errorMessage =
    "Applications with main methods have to implement BasicInterface or extend ApplicationBase";
}

使用Maven编译

运行mvn clean compile时(参见POM的GitHub项目),您将看到此输出(稍微缩短):

[INFO] ------------------------------------------------------------------------
[INFO] Building AspectJ sample with declare error 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- aspectj-maven-plugin:1.10:compile (default) @ aspectj-application-contract-enforcer ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[ERROR] "Applications with main methods have to implement BasicInterface or extend ApplicationBase"
    C:\Users\alexa\Documents\java-src\SO_AJ_EnforceMainClassImplementingInterface\src\main\java\de\scrum_master\app\UnwantedApplication.java:12
public static void main(String[] args) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

Eclipse中的错误视图

在Eclipse中使用AJDT(AspectJ开发工具),它看起来像这样:

Eclipse showing custom AspectJ compiler error

只需将main中的UnwantedApplication方法重命名为mainX之类的其他内容即可,错误就会消失。