如何确定 Java 应用程序的主要 class?
How do I determine the main class of a Java application?
我们正在开发一个平台,许多开发人员将在其中编写自己的 ETL 应用程序,这些应用程序使用供应商的 API,然后提交到平台上执行。我们希望限制开发人员在编写 Main class(通常只使用供应商的 API)时只做他们自己的事情,以促进一些坚定的约定。 (大型)组织有一种人们做自己的事情的文化,多年来导致了一些非常讨厌的架构,所以我们想强加一些可以通过 CI/CD 强制执行的最佳实践约定,这将帮助促进代码共享。替代方案将是对所有人免费恢复常态,我们极力避免这种情况。
我们如何确定应用程序的主要 class 是什么?我们如何对此进行测试?我们想定义开发人员使用的抽象 class 或接口,这将定义开发人员必须遵守的一些预先承诺(否则测试将失败)。我们无法修改供应商的代码。
因此,更具体地说,目前我们有:
public class MyNastyFreeForAll {
public static void main(String[] ) {
//...
}
}
有没有办法detecting/enforcing像这样:
public class MyConventionEnforcingClass implements/extends MyConventions {
public static void main(String[] ) {
//...
}
}
即测试应用程序的主要 class 使用从 MyConventions
?
派生的东西
理想情况下,我想 运行 使用 Spock 进行测试。
或者,有没有更好的方法来实现这个目标?恐怕在这样规模的组织中,在许多没有中心的独立团队中进行代码审查 control/hierarchy 是行不通的。
编辑以反映来自评论的输入:
从本质上讲,这是一个人的问题。然而,人数在 1000 人左右,文化变革不会在一夜之间发生。它不会仅仅通过教育、记录和影响来实现,从而希望人们做正确的事。我正在寻找一种技术解决方案,可以温和地引导我们的开发人员做正确的事情——如果他们愿意,他们总是可以颠覆它,但我想要求他们在想这样做的时候不惜一切代价去做。这是因为我正在寻求一种技术解决方案,所以我 post 正在使用 SO,而不是寻求有关如何在另一个站点上推动文化变革的指导。
编辑以提供 MCVE:
这是一个使用摘要的示例 class。最好验证(在编译或测试时)主 class 派生自 MyConventions
。如果用户想主动颠覆它,那就这样吧——你可以把马牵到水里等等——但我试图让最终用户更容易做正确的事而不是不做正确的事.简单地给他们一个为他们做样板的 class 是不够的,因为这些用户喜欢做他们自己的事情并且可能会忽略你,所以应该有某种形式的轻触技术强制执行。没有尝试对 doProcessing()
施加约定,但可以使用相同的原则来添加 pre- 和 post- 方法等来实现这一点。
如果有另一种方法可以实现这个目标,那么我会对想法非常感兴趣。
// 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");
}
}
您和其他部门可以使用 AspectJ 编译器编译所有代码,从命令行手动编译,通过批处理文件,通过为 AspectJ 配置的 IDE(例如 Eclipse,IDEA)或通过马文。我在 GitHub、just clone the project 上为您创建了 Maven 安装程序。抱歉,它没有使用您的 MCVE classes,因为我看到它们太晚了,不想重新开始。
界面方法
现在让我们假设有一个接口,所有符合规范的应用程序都需要实现它:
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));
}
}
基础class方法
或者,有一个 base class 应用程序必须扩展:
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));
}
}
不需要的应用程序
现在我们有了一个自己做事的应用程序,既不实现接口也不扩展基础 class:
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
发出警告,但这不会强制执行任何操作,只会报告问题) .
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
或任何子包内有主要方法的class,但没有实现BasicInterface
,也没有扩展ApplicationBase
.当然,实际上您只会选择后两个标准之一。我在这里做这两件事是为了给你一个选择。如果找到任何此类 class,将显示带有指定错误消息的编译器错误。
出于某种原因,有些人不喜欢表现力极佳的 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 中的错误视图
在带有 AJDT(AspectJ 开发工具)的 Eclipse 中,它看起来像这样:
只需将 UnwantedApplication
中的 main
方法重命名为 mainX
之类的其他名称,错误就会消失。
我们正在开发一个平台,许多开发人员将在其中编写自己的 ETL 应用程序,这些应用程序使用供应商的 API,然后提交到平台上执行。我们希望限制开发人员在编写 Main class(通常只使用供应商的 API)时只做他们自己的事情,以促进一些坚定的约定。 (大型)组织有一种人们做自己的事情的文化,多年来导致了一些非常讨厌的架构,所以我们想强加一些可以通过 CI/CD 强制执行的最佳实践约定,这将帮助促进代码共享。替代方案将是对所有人免费恢复常态,我们极力避免这种情况。
我们如何确定应用程序的主要 class 是什么?我们如何对此进行测试?我们想定义开发人员使用的抽象 class 或接口,这将定义开发人员必须遵守的一些预先承诺(否则测试将失败)。我们无法修改供应商的代码。
因此,更具体地说,目前我们有:
public class MyNastyFreeForAll {
public static void main(String[] ) {
//...
}
}
有没有办法detecting/enforcing像这样:
public class MyConventionEnforcingClass implements/extends MyConventions {
public static void main(String[] ) {
//...
}
}
即测试应用程序的主要 class 使用从 MyConventions
?
理想情况下,我想 运行 使用 Spock 进行测试。
或者,有没有更好的方法来实现这个目标?恐怕在这样规模的组织中,在许多没有中心的独立团队中进行代码审查 control/hierarchy 是行不通的。
编辑以反映来自评论的输入:
从本质上讲,这是一个人的问题。然而,人数在 1000 人左右,文化变革不会在一夜之间发生。它不会仅仅通过教育、记录和影响来实现,从而希望人们做正确的事。我正在寻找一种技术解决方案,可以温和地引导我们的开发人员做正确的事情——如果他们愿意,他们总是可以颠覆它,但我想要求他们在想这样做的时候不惜一切代价去做。这是因为我正在寻求一种技术解决方案,所以我 post 正在使用 SO,而不是寻求有关如何在另一个站点上推动文化变革的指导。
编辑以提供 MCVE:
这是一个使用摘要的示例 class。最好验证(在编译或测试时)主 class 派生自 MyConventions
。如果用户想主动颠覆它,那就这样吧——你可以把马牵到水里等等——但我试图让最终用户更容易做正确的事而不是不做正确的事.简单地给他们一个为他们做样板的 class 是不够的,因为这些用户喜欢做他们自己的事情并且可能会忽略你,所以应该有某种形式的轻触技术强制执行。没有尝试对 doProcessing()
施加约定,但可以使用相同的原则来添加 pre- 和 post- 方法等来实现这一点。
如果有另一种方法可以实现这个目标,那么我会对想法非常感兴趣。
// 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");
}
}
您和其他部门可以使用 AspectJ 编译器编译所有代码,从命令行手动编译,通过批处理文件,通过为 AspectJ 配置的 IDE(例如 Eclipse,IDEA)或通过马文。我在 GitHub、just clone the project 上为您创建了 Maven 安装程序。抱歉,它没有使用您的 MCVE classes,因为我看到它们太晚了,不想重新开始。
界面方法
现在让我们假设有一个接口,所有符合规范的应用程序都需要实现它:
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));
}
}
基础class方法
或者,有一个 base class 应用程序必须扩展:
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));
}
}
不需要的应用程序
现在我们有了一个自己做事的应用程序,既不实现接口也不扩展基础 class:
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
发出警告,但这不会强制执行任何操作,只会报告问题) .
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
或任何子包内有主要方法的class,但没有实现BasicInterface
,也没有扩展ApplicationBase
.当然,实际上您只会选择后两个标准之一。我在这里做这两件事是为了给你一个选择。如果找到任何此类 class,将显示带有指定错误消息的编译器错误。
出于某种原因,有些人不喜欢表现力极佳的 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 中的错误视图
在带有 AJDT(AspectJ 开发工具)的 Eclipse 中,它看起来像这样:
只需将 UnwantedApplication
中的 main
方法重命名为 mainX
之类的其他名称,错误就会消失。