有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java从jar文件写入$HOME

我正试图写入$HOME目录中的文件。写入该文件的代码已打包到jar文件中。当我运行单元测试来打包jar文件时,一切都按照预期工作——即填充文件并可以再次读取

当我试图从另一个应用程序运行这段代码时,jar文件包含在lib目录中,它失败了。文件已创建,但从未写入。当应用程序读取文件时,由于文件为空,因此无法对其进行解析

以下是写入文件的代码:

  logger.warn("TestNet wallet does not exist creating one now in the directory: " + walletPath)
  testNetFileName.createNewFile()
  logger.warn("Wallet file name: " + testNetFileName.getAbsolutePath)
  logger.warn("Can write: "+ testNetFileName.canWrite())
  logger.warn("Can read: " + testNetFileName.canRead)
  val w = Wallet.fromWatchingKey(TestNet3Params.get(), testNetSeed)
  w.autosaveToFile(testNetFileName, savingInterval, TimeUnit.MILLISECONDS, null)
  w
}

以下是与上述方法相关的日志:

2015-12-30 15:11:46,416 - [WARN] - from class com.suredbits.core.wallet.ColdStorageWallet$ in play-akka.actor.default-dispatcher-9 
TestNet wallet exists, reading in the one from disk

2015-12-30 15:11:46,416 - [WARN] - from class com.suredbits.core.wallet.ColdStorageWallet$ in play-akka.actor.default-dispatcher-9 
Wallet file name: /home/chris/testnet-cold-storage.wallet

然后它爆炸了

以下是autoSaveToFile的定义

public WalletFiles autosaveToFile(File f, long delayTime, TimeUnit timeUnit,
                                  @Nullable WalletFiles.Listener eventListener) {
    lock.lock();
    try {
        checkState(vFileManager == null, "Already auto saving this wallet.");
        WalletFiles manager = new WalletFiles(this, f, delayTime, timeUnit);
        if (eventListener != null)
            manager.setListener(eventListener);
        vFileManager = manager;
        return manager;
    } finally {
        lock.unlock();
    }
}

以及WalletFiles的定义

https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/wallet/WalletFiles.java#L68

public WalletFiles(final Wallet wallet, File file, long delay, TimeUnit delayTimeUnit) {
    // An executor that starts up threads when needed and shuts them down later.
    this.executor = new ScheduledThreadPoolExecutor(1, new ContextPropagatingThreadFactory("Wallet autosave thread", Thread.MIN_PRIORITY));
    this.executor.setKeepAliveTime(5, TimeUnit.SECONDS);
    this.executor.allowCoreThreadTimeOut(true);
    this.executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
    this.wallet = checkNotNull(wallet);
    // File must only be accessed from the auto-save executor from now on, to avoid simultaneous access.
    this.file = checkNotNull(file);
    this.savePending = new AtomicBoolean();
    this.delay = delay;
    this.delayTimeUnit = checkNotNull(delayTimeUnit);

    this.saver = new Callable<Void>() {
        @Override public Void call() throws Exception {
            // Runs in an auto save thread.
            if (!savePending.getAndSet(false)) {
                // Some other scheduled request already beat us to it.
                return null;
            }
            log.info("Background saving wallet, last seen block is {}/{}", wallet.getLastBlockSeenHeight(), wallet.getLastBlockSeenHash());
            saveNowInternal();
            return null;
        }
    };
}

我猜这是某种权限问题,但我似乎无法理解

编辑:这一切都是在完全相同的Ubuntu14.04机器上运行的——不同操作系统没有增加复杂性


共 (2) 个答案

  1. # 1 楼答案

    通常不能依赖$HOME的存在或可写性。实际上,只有两种可移植的方法可以识别(即提供指向)外部文件

    1. 使用调用命令行上的属性集或环境中提供的属性提供显式路径,或
    2. 在配置属性文件中提供路径,其位置本身在命令行或环境中作为属性提供

    使用$HOME的问题是,您无法知道应用程序运行时使用的用户ID。用户可能有或可能没有主目录,即使用户有,该目录也可能不可写。在您的特定情况下,您的进程可能能够创建文件(对目录本身的写访问),但对文件的写访问可能受到umask和/或acl(在Windows上)或selinux(在Linux上)的限制

    换句话说,库的安装程序/用户必须显式地为应用程序提供一个已知的可写路径

    另一种思考方式是,您正在编写可能在完全未知的环境中使用的库代码。除了你和用户之间的明确合同之外,你不能对外部环境做任何假设。您可以在接口规范中声明$HOME必须是可写的,但对于某些环境不具有$HOME可写性的用户来说,这可能非常不方便

    一个更好、更便携的解决方案是

    specify -Dcom.xyz.workdir=[path] on the command line to indicate the work path to be used

    或者

    The xyz library will look for its work directory in the path specified by the XYZ_WORK environment variable

    理想情况下,您可以同时执行这两项操作,以便为用户提供一定的灵活性

  2. # 2 楼答案

    savePending总是错误的。在call的开头,检查它是否为false,然后返回null。实际的保存代码永远不会执行。我猜你是想检查它是否在true那里,并将其设置为true,而不是false。然后,最后还需要将其重置为false

    现在,这在单元测试中起作用的原因就不同了。测试必须执行不同的代码