【问题标题】:Is it possible to copy all resource files (without naming them specifically) at runtime?是否可以在运行时复制所有资源文件(不具体命名)?
【发布时间】:2015-04-22 07:46:21
【问题描述】:

我有许多资源文件想作为方法调用复制到另一个位置。有各种方法和指南,以及关于如何从 JAR 读取/复制文件的问题。但是,如果我没有完全关闭,与在开发/调试时从 Eclipse 运行代码相比,当代码从已部署的 JAR 运行时,standard ways of getting a resource 将查看不同的位置。所以适用于一种情况的解决方案不适用于另一种情况..

我当前且极其冗长的解决方案(见下文)有效,但维护起来很丑陋、冗长且烦人(添加或删除附加文件)。 我有什么方法可以优化这个方法,(可能使用 NIO 包中的新巫术),例如只需循环 /res 文件夹中的文件并将它们全部复制到新位置。我'不确定如何避免为 getResource() 指定文件名和路径在这种情况下是否可行。

private void loadRes() throws IOException{
    // get styling resources from the jar file 
    File htmlRes = new File(topfolder, "res");
    if(!htmlRes.exists() || !htmlRes.isDirectory())
            htmlRes.mkdir();

    File cssfile = new File(htmlRes, "style.css"),
         bullet_file = new File(htmlRes,"i.gif"), 
         banner_file = new File(htmlRes, "banner.png"),
         bg_file = new File(htmlRes,"body_bg.png"),
         img_bg = new File(htmlRes,"button-bg.png"),
         hov_bg = new File(htmlRes,"button-hover-bg.png"),
         visu_img = new File(htmlRes,"visu.png"),
         pc_img = new File(htmlRes,"pc.png"),
         asc_gif = new File(htmlRes,"asc.gif"),
         desc_gif = new File(htmlRes,"desc.gif"),
         bg_gif = new File(htmlRes,"bg.gif");

    URL css_url = ExportUtils.class.getResource("/res/style.css");
    URL bullet_url = ExportUtils.class.getResource("/res/i.gif");
    URL banner_url = ExportUtils.class.getResource("/res/banner.png");
    URL bg_url = ExportUtils.class.getResource("/res/body_bg.png");
    URL image1_url = ExportUtils.class.getResource("/res/button-bg.png");
    URL image2_url = ExportUtils.class.getResource("/res/button-hover-bg.png");
    URL visu_url = ExportUtils.class.getResource("/res/visu.png");
    URL pc_url = ExportUtils.class.getResource("/res/pc.png");
    URL asc_url = ExportUtils.class.getResource("/res/asc.gif");
    URL desc_url = ExportUtils.class.getResource("/res/desc.gif");
    URL bggif_url = ExportUtils.class.getResource("/res/bg.gif");

    FileOutputStream fos = new FileOutputStream(cssfile);
    Resources.copy(css_url, fos);
    fos = new FileOutputStream(bullet_file);
    Resources.copy(bullet_url, fos);
    fos = new FileOutputStream(banner_file);
    Resources.copy(banner_url, fos);
    fos = new FileOutputStream(bg_file);
    Resources.copy(bg_url, fos);
    fos = new FileOutputStream(img_bg);
    Resources.copy(image1_url, fos);
    fos = new FileOutputStream(hov_bg);
    Resources.copy(image2_url, fos);
    fos = new FileOutputStream(visu_img);
    Resources.copy(visu_url, fos);
    fos = new FileOutputStream(pc_img);
    Resources.copy(pc_url, fos);
    fos = new FileOutputStream(asc_gif);
    Resources.copy(asc_url, fos);
    fos = new FileOutputStream(desc_gif);
    Resources.copy(desc_url, fos);
    fos = new FileOutputStream(bg_gif);
    Resources.copy(bggif_url, fos);

    // scripts and finish head
    URL sort_script = ExportUtils.class.getResource("/res/scripts.js");
    URL func_script = ExportUtils.class.getResource("/res/jquery.tablesorter.min.js");
    URL scinot_parser = ExportUtils.class.getResource("/res/jquery.tablesorter.scinot.js");

    File div_js = new File(htmlRes,"scripts.js");
    File jquery_sorttable = new File(htmlRes,"jquery.tablesorter.min.js");
    File jquery_st_scinot = new File(htmlRes, "jquery.tablesorter.scinot.js");

    fos = new FileOutputStream(div_js);
    Resources.copy(sort_script, fos);

    fos = new FileOutputStream(jquery_sorttable);
    Resources.copy(func_script, fos);

    fos = new FileOutputStream(jquery_st_scinot);
    Resources.copy(scinot_parser, fos);
 }

【问题讨论】:

    标签: java deployment io nio


    【解决方案1】:

    您可以通过

    获取资源的位置
    ExportUtils.class.getProtectionDomain().getCodeSource().getLocation()
    

    如果您在 Eclipse 中运行,这应该会为您提供一个指向项目输出文件夹的 file: URL。如果从 Jar 运行,它应该指向 Jar。然后,您可以从文件夹或 JarInputStream 遍历资源目录中的所有文件。

    但是,由于这种方法在很大程度上依赖于执行环境(例如类加载器和安全管理器),因此这可能会也可能不会奏效。因此,我不建议在生产中使用它。相反,我会使用您的方法,可能会通过在顶部定义资源列表然后在循环中处理它们来稍微整理一下,从而减少代码重复。

    【讨论】:

      【解决方案2】:

      我通常使用类文件的目录并扫描它们。

      File directory = MyClass.class.getResource("/."); // or "." dont remember it
      

      现在你可以使用了

      for(File file : directory.listFiles()) { if(file.isDirectory()) scanDirectory(file); else { //check for resources and copy them } }

      因此,您最终可以从类路径条目开始遍历目录树。但检查像罐子等包。在罐子里可能会变得更加棘手。

      PS:scanDirectory是for循环所在的方法。

      【讨论】:

      • 这看起来更像是一个 web 部署和 tomcat 和群组通常解压缩战争文件(可以关闭)。这就是为什么我写了“在罐子里可能会变得更加棘手。”
      【解决方案3】:
      private void loadRes() throws IOException{
          DataInputStream dis  = null;
          FileOutputStream fos  = null;
          try{
              File htmlRes = new File(".", "res");
          if(!htmlRes.exists() || !htmlRes.isDirectory())
              htmlRes.mkdir();
      
              File outfile = new File(htmlRes, "/res/style.css");
              fos = new FileOutputStream(outfile);
              InputStream is =  Test.class.getResourceAsStream("/res/style.css");
              dis = new DataInputStream(is);
              while (true){
                  fos.write(dis.readByte());
              }
      //repeat for rest of the files
      // or set up an array of file names and iterate this in a loop 
      
          }catch(NullPointerException npe){
              System.err.println("NullPointerException : " + npe.getMessage();
      }catch(EOFException eofe){
              try{
                      //This ensures the streams are closed properly
                      dis.close();
                      fos.close();
              }catch(IOException ioe){
                      System.err.println("IOException: " + ioe.getMessage());
              }
          }catch(MalformedURLException murle){
                  System.err.println("MalformedURLException: " + murle.getMessage());
          }catch(IOException ioe){
                  System.err.println("IOException: " + ioe.getMessage());
          }
      
      }
      

      【讨论】:

      • 感谢您的输入,但这比上面的解决方案更简洁或优雅吗?
      【解决方案4】:

      重新编辑

      您可以通过创建 Jar 文件系统并找到其根目录来浏览 jar 文件。然后可以使用 FileVistor 模式来获取 jar 文件中的每个文件。对于另一种情况,根目录就是您的文件所在的目录。

      使用 getResource() 来告诉您该类是从 jar 文件还是从目录加载的。必须将输出处理成以后的方法可以使用的形式。

      public class JarFs extends SimpleFileVisitor<Path> {
          public static void main(String[] args) throws IOException {
      
              URL resource = JarFs.class.getResource("JarFs.class");
              System.out.println(resource);
              boolean isJar = false;
              String jarFile;
              if( resource.toString().startsWith("jar")) {
                  isJar = true;
                  jarFile = resource.toString().replaceAll("!.*", "").replace("file:", "file://");
              } else {
                  isJar = false;
                  jarFile = resource.toString().replace("file:", "").replace("JarFs.class", "");
              }
              JarFs j = new JarFs(jarFile, isJar);
              j.browse();
          }
          Path root;
          JarFs(String jar, boolean isJar)  throws IOException {
              if(isJar) {
                  //If the class is loaded from a jar file, create a Jar filesystem.
                  String path = jar;
                  System.err.println("Create " + path);
                  URI uri = URI.create(path);
                  Map<String, ?> env = Collections.emptyMap();
                  FileSystem fs = FileSystems.newFileSystem(uri, env);
                  Iterable<Path> roots = fs.getRootDirectories();
                  for (Path p : roots) {
                      root = p;
                      break;
                  }
              } else {
                  //while if the class is loaded from a file, use the directory it is loaded from 
      
                  root = FileSystems.getDefault().getPath(jar);
              }
          }
      

      设置完成后,您可以像这样遍历文件系统或目录

          void browse() throws IOException {
              Files.walkFileTree(root, this);
          }
      

      收益在于 visitFile() 方法

          @Override
          public FileVisitResult visitFile(Path file,
                                     BasicFileAttributes attr) {
              if (attr.isRegularFile()) {
                  // Do the business here
              }
              return FileVisitResult.CONTINUE;
          }
      }
      

      【讨论】:

      • 我可能在这里误解了一些东西,但看起来你的 JarFS 是用来读取任意 jar 文件的。我在考虑部署代码本身的 jar。另外,根据项目的启动方式,您对如何在运行时自动从常规文件遍历和建议的 Jar-traversal 切换有什么建议?
      • 您可以使用 getResource() 找出类的加载位置,如我编辑的答案中所示。
      【解决方案5】:

      要获取所有文件的列表,我使用以下实现。 这在 eclipse 中的开发过程中以及在 jar 文件中也有效。

      (代码使用 Lombok,因此您必须将 val 替换为“真实”类型,而不是最后的 @Cleanup close() JarFile

      public final class ResourceUtils {
      
        public static List<String> listFiles(Class<?> clazz, String path) throws IOException {
          val files = new ArrayList<String>();
      
          val url = clazz.getResource(path);
          if (url == null) throw new IllegalArgumentException("list files failed for path '" + path + "'.");
      
          if ("file".equals(url.getProtocol())) {
            try {
              val rootDir = new File(url.toURI());
              findFilesRecursive(files, rootDir, rootDir);
              return files;
            }
            catch (URISyntaxException e) {
              throw new AssertionError(e); // should never happen
            }
          }
      
          if ("jar".equals(url.getProtocol())) {
            if (path.startsWith("/")) path = path.substring(1);
            val jarFile = url.getPath().substring(5, url.getPath().indexOf("!"));
            @Cleanup val jar = new JarFile(jarFile);
            val entries = jar.entries();
            while (entries.hasMoreElements()) {
              val entry = entries.nextElement();
              if (!entry.isDirectory()) {
                val name = entry.getName();
                if (name.startsWith(path)) files.add(name.substring(path.length() + 1));
              }
            }
      
            return files;
          }
      
          throw new IllegalArgumentException("Unexpected protocol: " + url.getProtocol());
        }
      
        private static void findFilesRecursive(List<String> files, File rootDir, File currPath) {
          if (currPath.isDirectory()) {
            for (File path : currPath.listFiles()) {
              findFilesRecursive(files, rootDir, path);
            }
          }
          else {
            files.add(currPath.getAbsolutePath().substring(rootDir.getAbsolutePath().length() + 1));
          }
        }
      }
      

      这样称呼它:

      ResourceUtils.listFiles(MyClass.class, "/res");
      

      【讨论】:

        【解决方案6】:

        您可以从环境中 grep class paths。这将允许您在定义的位置解压缩 jar 文件并过滤 zip 文件条目,除了.class 之外的所有内容。

        【讨论】:

          【解决方案7】:

          我建议你试试Google Reflections。这是一个旨在简化 Java 反射使用的库,但事实证明,在类路径中查找资源同样好。 为了解决您的具体问题,您可以首先使用以下代码获取 /res 中的所有资源:

          Reflections reflections = new Reflections("res");
          Set<String> resources = reflections.getResources(Pattern.compile(".*\\.*"));
          

          之后,您需要将 resources 中每个资源的内容作为 InputStream 获取:

          InputStream resourceInputSteam =  ClassLoader.class.getResourceAsStream(aResource);
          

          最后将资源放入一个文件夹中,您可以使用 Apache commons-io 的 IOUtils#copy 轻松完成此操作:

          IOUtils.copy(resourceInputSteam, destinationFileOutputStream);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-06-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多