为什么 javafx 忽略来自 main 的 return 并仍然启动应用程序?
why javafx ignore return from main and still launches an application?
我有以下代码。
public static void main(String[] args)
{
if (!ArgumentsHandler.handle(args))
{
return;
}
Storage.getInstance().load();
if (!Storage.getInstance().isLoadSuccessful())
{
launch(args);
}
else
{
System.err.println("Unable to load configurations.");
}
}
我特别反转 if
语句中的条件使其失败,我可以在调试器中明确看到它不执行 launch
方法,但应用程序 window 仍在显示中。
我还注意到在 main
方法中使用 return
语句没有效果 - 应用程序仍然继续执行。它只响应 System.exit(0)
.
为什么会这样?
更新:
根据您的要求,这是 ArgumentsHandler 的一个片段。我在这里没有使用线程(至少是有意的)。
public static boolean handle(String[] args)
{
//handle args
if (args.length > 0)
{
switch (args[0])
{
//createRepository
case "-c":
configure(args);
break;
case "-r":
case "--repository":
repository(args);
break;
default:
help();
break;
}
return false;
}
return true;
}
private static void configure(String[] args)
{
if (args.length > 1)
{
boolean isRandom = false;
switch (args[1])
{
case "true":
case "1":
isRandom = true;
break;
case "false":
case "0":
//valid input, ignored
break;
default:
System.err.println("Invalid arguments. Possible values: [--configuration] [1/0].");
return;
}
Storage.configure(isRandom); //creates a bunch of json files (uses NIO).
return;
}
else
{
System.err.println("Invalid arguments. Possible values: -c [1/0].");
}
}
存储空间
public void load()
{
isLoadSuccessful = false;
//load configuration
app = loadConfiguration(appFilePath);
if (app == null)
{
System.err.println("Unable to load app configuration.");
return;
}
//load company
company = loadCompany(app.getCompanyFilePath());
if (company == null)
{
System.err.println("Unable to load company configuration.");
return;
}
repository = loadRepository(app.getRepositoryFilePath());
if (repository == null)
{
System.err.println("Unable to load repository configuration.");
return;
}
isLoadSuccessful = true;
}
private static App loadConfiguration(String filePath)
{
return (App) Utility.load(filePath, App.class);
}
loadConfiguration
、loadCompany
和loadRepository
其实是一样的。将来,他们不会读取简单的 json 文件,而是会访问复杂的档案,这就是为什么我已经创建了几个几乎相同的方法。
Utility.load
public static Object load(String path, Type type)
{
try
{
JsonReader reader = new JsonReader(new FileReader(path));
Gson gson = new Gson();
Object obj = gson.fromJson(reader, type);
reader.close();
return obj;
}
catch (IOException ex)
{
ex.printStackTrace();
return null;
}
}
只是反序列化文件中的对象。
从你调用 launch(args)
的方式来看,我和你后来 假设 main
方法在 class 的子 class 中=16=]。我相信这就是您遇到问题的原因。
正如您所注意到的,有很多看似 JavaFX 特定的线程 运行。具体来说,非守护程序“JavaFX Application Thread”是运行(至少,它在Java 10 中是非守护程序)。即使 main
线程退出,该线程也会导致 JVM 保持活动状态。这是 Java:
的正常行为
java.lang.Thread
When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main
of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:
- The
exit
method of class Runtime
has been called and the security manager has permitted the exit operation to take place.
- All threads that are not daemon threads have died, either by returning from the call to the
run
method or by throwing an exception that propagates beyond the run
method.
但是,为什么在您故意不调用 Application.launch
时却启动了“JavaFX 应用程序线程”?我只是在猜测,但这可能与 JavaFX 应用程序收到的特殊待遇有关。因为至少 Java 8 你不必在 Application
1 的 subclass 中声明一个 main
方法。如果主 class 是 Application
的子 class Java 会自动处理启动。
import javafx.application.Application;
import javafx.stage.Stage;
public class MyApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// create scene and show stage...
}
}
如果您有上述内容并调用 java MyApp
,应用程序将启动并调用 start
。但是,如果您有以下情况:
import javafx.application.Application;
import javafx.stage.Stage;
public class MyApp extends Application {
public static void main(String[] args) {}
@Override
public void start(Stage primaryStage) throws Exception {
// create scene and show stage...
}
}
然后调用 main
方法,但 start
是 而不是 。基本上,显式声明 main
会覆盖启动 JavaFX 应用程序 的默认行为,但不会阻止 JavaFX 运行时被初始化 。也许这种行为是设计好的,或者可能是一种疏忽。但这里重要的是,只有当 main class 有一个 main
方法并且是一个 Application
subclass 时才会发生这种情况。如果将这两者分开:
public class MyApp extends Application {
// implement ...
}
public class Main {
public static void main(String[] args) {
// Perform pre-checks, return if necessary
Application.launch(MyApp.class, args);
}
}
那么你就不会再有这个问题了。
否则你可以继续使用System.exit()
或切换到Platform.exit()
。
还有另一种可能更合适的方法来处理这个问题。您似乎在调用 Application.launch
之前在 main
方法中执行初始化。如果在此初始化过程中出现问题,您希望中止启动 JavaFX 应用程序。好吧,JavaFX 提供了自己执行此操作的方法:Application.init()
.
The application initialization method. This method is called immediately after the Application class is loaded and constructed. An application may override this method to perform initialization prior to the actual starting of the application.
The implementation of this method provided by the Application class does nothing.
NOTE: This method is not called on the JavaFX Application Thread. An application must not construct a Scene or a Stage in this method. An application may construct other JavaFX objects in this method.
将您的初始化代码移至此方法。如果你打电话给 Platform.exit()
then the application will exit and Application.start
won't be invoked. An alternative is to throw an exception inside init
. You can also get the application arguments by using Application.getParameters()
which returns an instance of Application.Parameters
.
public class MyApp extends Application {
@Override
public void init() throws Exception {
if (!ArgumentsHandler.handle(getParameters()) {
Platform.exit(); // or throw an exception
} else {
Storage storage = Storage.getInstance();
storage.load();
if (!storage.isLoadSuccessful()) {
Platform.exit(); // or throw an exception
}
}
}
@Override
public void start(Stage primaryStage) throws Exception {
// Create Scene and show the primary Stage
}
@Override
public void stop() throws Exception {
/*
* Called when the JavaFX application is exiting, such as from
* a call to Platform.exit(). Note, however, that in my experience
* this method is *not* called when Platform.exit() is called inside
* the "init" method. It is called if Platform.exit() is called from
* inside the "start" method or anywhere else in the application once
* it is properly started.
*
* This is where you could perform any necessary cleanup.
*/
}
}
1. JavaFX 在版本 8 中包含在 Java SE 中。请注意,此行为可能会在 Java 11 中更改,因为 JavaFX 将再次与 Java SE 分开.
我有以下代码。
public static void main(String[] args)
{
if (!ArgumentsHandler.handle(args))
{
return;
}
Storage.getInstance().load();
if (!Storage.getInstance().isLoadSuccessful())
{
launch(args);
}
else
{
System.err.println("Unable to load configurations.");
}
}
我特别反转 if
语句中的条件使其失败,我可以在调试器中明确看到它不执行 launch
方法,但应用程序 window 仍在显示中。
我还注意到在 main
方法中使用 return
语句没有效果 - 应用程序仍然继续执行。它只响应 System.exit(0)
.
为什么会这样?
更新:
根据您的要求,这是 ArgumentsHandler 的一个片段。我在这里没有使用线程(至少是有意的)。
public static boolean handle(String[] args)
{
//handle args
if (args.length > 0)
{
switch (args[0])
{
//createRepository
case "-c":
configure(args);
break;
case "-r":
case "--repository":
repository(args);
break;
default:
help();
break;
}
return false;
}
return true;
}
private static void configure(String[] args)
{
if (args.length > 1)
{
boolean isRandom = false;
switch (args[1])
{
case "true":
case "1":
isRandom = true;
break;
case "false":
case "0":
//valid input, ignored
break;
default:
System.err.println("Invalid arguments. Possible values: [--configuration] [1/0].");
return;
}
Storage.configure(isRandom); //creates a bunch of json files (uses NIO).
return;
}
else
{
System.err.println("Invalid arguments. Possible values: -c [1/0].");
}
}
存储空间
public void load()
{
isLoadSuccessful = false;
//load configuration
app = loadConfiguration(appFilePath);
if (app == null)
{
System.err.println("Unable to load app configuration.");
return;
}
//load company
company = loadCompany(app.getCompanyFilePath());
if (company == null)
{
System.err.println("Unable to load company configuration.");
return;
}
repository = loadRepository(app.getRepositoryFilePath());
if (repository == null)
{
System.err.println("Unable to load repository configuration.");
return;
}
isLoadSuccessful = true;
}
private static App loadConfiguration(String filePath)
{
return (App) Utility.load(filePath, App.class);
}
loadConfiguration
、loadCompany
和loadRepository
其实是一样的。将来,他们不会读取简单的 json 文件,而是会访问复杂的档案,这就是为什么我已经创建了几个几乎相同的方法。
Utility.load
public static Object load(String path, Type type)
{
try
{
JsonReader reader = new JsonReader(new FileReader(path));
Gson gson = new Gson();
Object obj = gson.fromJson(reader, type);
reader.close();
return obj;
}
catch (IOException ex)
{
ex.printStackTrace();
return null;
}
}
只是反序列化文件中的对象。
从你调用 launch(args)
的方式来看,我和你后来 main
方法在 class 的子 class 中=16=]。我相信这就是您遇到问题的原因。
正如您所注意到的,有很多看似 JavaFX 特定的线程 运行。具体来说,非守护程序“JavaFX Application Thread”是运行(至少,它在Java 10 中是非守护程序)。即使 main
线程退出,该线程也会导致 JVM 保持活动状态。这是 Java:
java.lang.Thread
When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named
main
of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:
- The
exit
method of classRuntime
has been called and the security manager has permitted the exit operation to take place.- All threads that are not daemon threads have died, either by returning from the call to the
run
method or by throwing an exception that propagates beyond therun
method.
但是,为什么在您故意不调用 Application.launch
时却启动了“JavaFX 应用程序线程”?我只是在猜测,但这可能与 JavaFX 应用程序收到的特殊待遇有关。因为至少 Java 8 你不必在 Application
1 的 subclass 中声明一个 main
方法。如果主 class 是 Application
的子 class Java 会自动处理启动。
import javafx.application.Application;
import javafx.stage.Stage;
public class MyApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// create scene and show stage...
}
}
如果您有上述内容并调用 java MyApp
,应用程序将启动并调用 start
。但是,如果您有以下情况:
import javafx.application.Application;
import javafx.stage.Stage;
public class MyApp extends Application {
public static void main(String[] args) {}
@Override
public void start(Stage primaryStage) throws Exception {
// create scene and show stage...
}
}
然后调用 main
方法,但 start
是 而不是 。基本上,显式声明 main
会覆盖启动 JavaFX 应用程序 的默认行为,但不会阻止 JavaFX 运行时被初始化 。也许这种行为是设计好的,或者可能是一种疏忽。但这里重要的是,只有当 main class 有一个 main
方法并且是一个 Application
subclass 时才会发生这种情况。如果将这两者分开:
public class MyApp extends Application {
// implement ...
}
public class Main {
public static void main(String[] args) {
// Perform pre-checks, return if necessary
Application.launch(MyApp.class, args);
}
}
那么你就不会再有这个问题了。
否则你可以继续使用System.exit()
或切换到Platform.exit()
。
还有另一种可能更合适的方法来处理这个问题。您似乎在调用 Application.launch
之前在 main
方法中执行初始化。如果在此初始化过程中出现问题,您希望中止启动 JavaFX 应用程序。好吧,JavaFX 提供了自己执行此操作的方法:Application.init()
.
The application initialization method. This method is called immediately after the Application class is loaded and constructed. An application may override this method to perform initialization prior to the actual starting of the application.
The implementation of this method provided by the Application class does nothing.
NOTE: This method is not called on the JavaFX Application Thread. An application must not construct a Scene or a Stage in this method. An application may construct other JavaFX objects in this method.
将您的初始化代码移至此方法。如果你打电话给 Platform.exit()
then the application will exit and Application.start
won't be invoked. An alternative is to throw an exception inside init
. You can also get the application arguments by using Application.getParameters()
which returns an instance of Application.Parameters
.
public class MyApp extends Application {
@Override
public void init() throws Exception {
if (!ArgumentsHandler.handle(getParameters()) {
Platform.exit(); // or throw an exception
} else {
Storage storage = Storage.getInstance();
storage.load();
if (!storage.isLoadSuccessful()) {
Platform.exit(); // or throw an exception
}
}
}
@Override
public void start(Stage primaryStage) throws Exception {
// Create Scene and show the primary Stage
}
@Override
public void stop() throws Exception {
/*
* Called when the JavaFX application is exiting, such as from
* a call to Platform.exit(). Note, however, that in my experience
* this method is *not* called when Platform.exit() is called inside
* the "init" method. It is called if Platform.exit() is called from
* inside the "start" method or anywhere else in the application once
* it is properly started.
*
* This is where you could perform any necessary cleanup.
*/
}
}
1. JavaFX 在版本 8 中包含在 Java SE 中。请注意,此行为可能会在 Java 11 中更改,因为 JavaFX 将再次与 Java SE 分开.