【问题标题】:Is it possible to create an URL pointing to an in-memory object?是否可以创建指向内存对象的 URL?
【发布时间】:2013-06-29 21:17:11
【问题描述】:

我正在尝试扩展我的库以集成SwingJPA,方法是尽可能地使JPA 配置为自动(和可移植),这意味着以编程方式添加<class> 元素。 (我知道它可以通过 Hibernate 的 AnnotationConfiguration 或 EclipseLInk 的 ServerSession 来完成,但是 - 可移植性)。我还想避免将Spring 仅用于这个单一目的。

我可以即时创建一个persistence.xml,并用指定包中的<class> 元素填充它(通过反射库)。当我尝试将此persistence.xml 提供给JPA 提供程序时,问题就开始了。我能想到的唯一方法是设置URLClassLoader,但我想不出一种方法不会让我首先将文件写入磁盘的某个地方,因为只有能够获得有效的URL。设置一个套接字以通过URL(localhost:xxxx) 提供文件似乎......我不知道,邪恶?

有人知道我该如何解决这个问题吗?我知道避免使用一个库听起来需要做很多工作,但我想知道是否可以这样做。

编辑(尝试更清晰):

动态生成的XML 保存在String 对象中。我不知道如何将它提供给持久性提供者另外,我想避免将文件写入磁盘。

就我的问题而言,持久性提供程序只是一个扫描类路径以查找META-INF/persistence.xml 的类。一些实现可以接受动态创建XML,但没有通用接口(特别是对于文件的关键部分,<class> 标签)。

我的想法是设置一个自定义 ClassLoader - 如果您有任何其他的,我将不胜感激,我没有设置这个。

我能找到的唯一一个易于扩展/可配置的是URLClassLoader。它适用于URL 对象,我不知道是否可以在不先将 XML 实际写入磁盘的情况下创建一个。

这就是我设置的方式,但它通过将persistenceXmlFile = new File("META-INF/persistence.xml") 写入磁盘来工作:

Thread.currentThread().setContextClassLoader(
    new URLResourceClassLoader(
        new URL[] { persistenceXmlFile.toURI().toURL() },
        Thread.currentThread().getContextClassLoader()
    )
);

URLResourceClassLoaderURLCLassLoader 的子类,它允许通过覆盖public Enumeration<URL> findResources(String name) 来查找资源和类。

【问题讨论】:

  • 您的意思是"memory mapped",还是“仅存在于内存中的对象”?
  • 我也想知道这是否可能,因为内存被认为是每个进程私有的......
  • 您使用的代码是什么?为什么它必须是 URL?如果您要显示代码,我们可能会提出一个很好地接受一些通用Resource 或类似的解决方案。
  • @MattBall 抱歉,我的意思是“仅存在于内存中的对象”。如果可能的话,我只是不想写入磁盘。
  • @yair 我不必是 URL。我正在寻找一种方法来提供一个使用文件扫描类路径的类 - 而无需实际将文件写入磁盘。

标签: java jpa classloader


【解决方案1】:

可能有点晚(4 年后),但对于正在寻找类似解决方案的其他人,您也许可以使用我创建的 URL 工厂:

public class InMemoryURLFactory {

    public static void main(String... args) throws Exception {
        URL url = InMemoryURLFactory.getInstance().build("/this/is/a/test.txt", "This is a test!");
        byte[] data = IOUtils.toByteArray(url.openConnection().getInputStream());
        // Prints out: This is a test!
        System.out.println(new String(data));
    }

    private final Map<URL, byte[]> contents = new WeakHashMap<>();
    private final URLStreamHandler handler = new InMemoryStreamHandler();

    private static InMemoryURLFactory instance = null;

    public static synchronized InMemoryURLFactory getInstance() {
        if(instance == null)
            instance = new InMemoryURLFactory();
        return instance;
    }

    private InMemoryURLFactory() {

    }

    public URL build(String path, String data) {
        try {
            return build(path, data.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException ex) {
            throw new RuntimeException(ex);
        }
    }

    public URL build(String path, byte[] data) {
        try {
            URL url = new URL("memory", "", -1, path, handler);
            contents.put(url, data);
            return url;
        } catch (MalformedURLException ex) {
            throw new RuntimeException(ex);
        }
    }

    private class InMemoryStreamHandler extends URLStreamHandler {

        @Override
        protected URLConnection openConnection(URL u) throws IOException {
            if(!u.getProtocol().equals("memory")) {
                throw new IOException("Cannot handle protocol: " + u.getProtocol());
            }
            return new URLConnection(u) {

                private byte[] data = null;

                @Override
                public void connect() throws IOException {
                    initDataIfNeeded();
                    checkDataAvailability();
                    // Protected field from superclass
                    connected = true;
                }

                @Override
                public long getContentLengthLong() {
                    initDataIfNeeded();
                    if(data == null)
                        return 0;
                    return data.length;
                }

                @Override
                public InputStream getInputStream() throws IOException {
                    initDataIfNeeded();
                    checkDataAvailability();
                    return new ByteArrayInputStream(data);
                }

                private void initDataIfNeeded() {
                    if(data == null)
                        data = contents.get(u);
                }

                private void checkDataAvailability() throws IOException {
                    if(data == null)
                        throw new IOException("In-memory data cannot be found for: " + u.getPath());
                }

            };
        }

    }
}

【讨论】:

  • byte[] data = IOUtils.toByteArray(url.openConnection().getInputStream()); IOUtils是如何获取的? Eclipse IDE 编译器说:类型 sun.security.util.IOUtils 不可访问
  • 谢谢你,一直在寻找类似的东西来存储转换为标准 CSS 文件的 LESS 文件,然后根据 URL 加载它们。此外,如果您不想添加 Apache Commons 依赖项,请使用自 Java 9 起可用的url.openConnection().getInputStream().readAllBytes()
  • 感谢这个即用型工作示例,包括防止内存泄漏的 WeakHashmap...
【解决方案2】:

我们可以为此使用Jimfs google 库。

首先,我们需要将maven dependency 添加到我们的项目中:

<dependency>
  <groupId>com.google.jimfs</groupId>
  <artifactId>jimfs</artifactId>
  <version>1.2</version>
</dependency>

之后,我们需要配置我们的文件系统行为,并将我们的字符串内容写入内存文件,如下所示:

public static final String INPUT =
      "\n"
          + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
          + "<note>\n"
          + "  <to>Tove</to>\n"
          + "  <from>Jani</from>\n"
          + "  <heading>Reminder</heading>\n"
          + "  <body>Don't forget me this weekend!</body>\n"
          + "</note>";

@Test
void usingJIMFS() throws IOException {
  try (var fs = Jimfs.newFileSystem(Configuration.unix())) {
    var path = fs.getPath(UUID.randomUUID().toString());
    Files.writeString(path, INPUT);
    var url = path.toUri().toURL();

    assertThat(url.getProtocol()).isEqualTo("jimfs");
    assertThat(Resources.asCharSource(url, UTF_8).read()).isEqualTo(INPUT);
  }
}

我们可以在official repository中找到更多示例。

如果我们查看jimfs source code 内部,我们会发现实现类似于@NSV 答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多