Javassist-如何将方法注入JAR中的类

Javassist-如何将方法注入JAR中的类,java,jar,Java,Jar,我正在从JAR文件中读取一个类并注入一个函数。如何将其写回JAR文件 // Load the class representation ClassPool pool = ClassPool.getDefault(); pool.insertClassPath( "c:/Test.jar" ); CtClass cc = pool.get("com.test.TestFunction"); CtMethod m = CtNewMethod.make("publ

我正在从
JAR
文件中读取一个类并注入一个函数。如何将其写回JAR文件

     // Load the class representation

   ClassPool pool = ClassPool.getDefault();
   pool.insertClassPath( "c:/Test.jar" );
   CtClass cc = pool.get("com.test.TestFunction"); 
   CtMethod m = CtNewMethod.make("public void test2() { System.out.println(\"test2\"); }", cc);
   cc.addMethod(m); 
   CtMethod cm = cc.getDeclaredMethod("test1", new CtClass[0]);
   cm.insertBefore("{ test2();}");
   cc.writeFile("c:/Test.jar");  // Fails here

线程“main”
java.io.FileNotFoundException中出现异常
:c:\Test.jar\com\Test\TestFunction.class(系统找不到指定的路径)

我想参考StackOverflow Q&A中的以下内容:

当您阅读这些注释时,您会发现无法回写到JAR。这也由抛出的IOException(FNFE)表示。或者一个愚蠢的wisenheimer回答:如果我们将JavaDoc读入这个问题,我们会看到该方法需要一个directoryName

我建议解压JAR,进行操作,然后重新构建(压缩)JAR


请随时告诉我您的想法。

我想Javassist中没有简单的方法可以更新JAR并用新类替换更新的类。所以我创建了一个JarHandler类,它只接收参数

这是执行注入的主类

  public static void main(String args[]){
  ClassPool pool = ClassPool.getDefault();
  pool.insertClassPath( "c:/Test.jar" );
  CtClass cc = pool.get("com.test.TestFunction"); 
  CtMethod m = CtNewMethod.make("public void test2() { System.out.println(\"test2\"); }", cc);
 cc.addMethod(m); 
 CtMethod cm = cc.getDeclaredMethod("test1", new CtClass[0]);
 cm.insertBefore("{ test2();}");

 byte[] b = cc.toBytecode(); // convert the new class to bytecode.
 pool.removeClassPath(cp);   // need to remove the classpath to release connection to JAR file so we can update it.
 JarHandler jarHandler = new JarHandler();
 jarHandler.replaceJarFile("C:/Test.jar", b, "com/test/TestFunction.class");

 }
这是JarHandler类

 public class JarHandler{
   public void replaceJarFile(String jarPathAndName,byte[] fileByteCode,String fileName) throws IOException {
      File jarFile = new File(jarPathAndName);
      File tempJarFile = new File(jarPathAndName + ".tmp");
      JarFile jar = new JarFile(jarFile);
      boolean jarWasUpdated = false;

      try {
=
         JarOutputStream tempJar =
            new JarOutputStream(new FileOutputStream(tempJarFile));

         // Allocate a buffer for reading entry data.

         byte[] buffer = new byte[1024];
         int bytesRead;

         try {
            // Open the given file.

            try {
               // Create a jar entry and add it to the temp jar.

               JarEntry entry = new JarEntry(fileName);
               tempJar.putNextEntry(entry);
               tempJar.write(fileByteCode);

            } catch (Exception ex) {
                   System.out.println(ex);

                   // Add a stub entry here, so that the jar will close without an
                   // exception.

                   tempJar.putNextEntry(new JarEntry("stub"));
                }


            // Loop through the jar entries and add them to the temp jar,
            // skipping the entry that was added to the temp jar already.
            InputStream entryStream = null;
            for (Enumeration entries = jar.entries(); entries.hasMoreElements(); ) {
               // Get the next entry.

               JarEntry entry = (JarEntry) entries.nextElement();

               // If the entry has not been added already, so add it.

               if (! entry.getName().equals(fileName)) {
                  // Get an input stream for the entry.

                  entryStream = jar.getInputStream(entry);
                  tempJar.putNextEntry(entry);

                  while ((bytesRead = entryStream.read(buffer)) != -1) {
                     tempJar.write(buffer, 0, bytesRead);
                  }
               }else
                   System.out.println("Does Equal");
            }
            if(entryStream!=null)
                entryStream.close();
            jarWasUpdated = true;
         }
         catch (Exception ex) {
            System.out.println(ex);

            // IMportant so the jar will close without an
            // exception.

            tempJar.putNextEntry(new JarEntry("stub"));
         }
         finally {
            tempJar.close();
         }
      }
      finally {

         jar.close();

         if (! jarWasUpdated) {
            tempJarFile.delete();
         }
      }


      if (jarWasUpdated) {            
         if(jarFile.delete()){
           tempJarFile.renameTo(jarFile);
           System.out.println(jarPathAndName + " updated.");
         }else
             System.out.println("Could Not Delete JAR File");
      }
   }
这个函数只是通过向其写入新的类字节码来创建一个临时JAR。 然后is遍历当前JAR的所有条目,并将其所有条目写入临时JAR
JAR文件,但正在更新的条目除外(上面已经写入了字节码)。然后它删除当前JAR,并用使用相同JAR名称的临时JAR替换它。

是的,可以看出writeFile()需要一个目录名作为参数。我一辈子都无法理解为什么可以从JAR中检索类并用几个方法对其进行操作,而没有任何简单的方法将其写回JAR。我甚至找不到任何这样做的例子。我认为许多开发人员将处理JAR而不仅仅是类路径。我同意这种用法。我认为该包的作者严格遵循了OO原则“紧密内聚但松散耦合”(或者说是懒惰的):操作类的所有功能都可用(紧密),但JAR操作(耦合)缺失。-您介意发布您的解决方案以帮助他人更快地进步吗?我不太确定我是否同意紧密内聚/松散耦合的评论。这与类之间的接口以及它们如何交互有关。在这种情况下,我认为操作字节码有3个主要功能领域。1.)定位要操作的字节码/查看2.)实际操作/查看3.)编写新字节码。当涉及到向jar写入时,该包没有实现第三个功能。这个包完成了类文件方法的编写,我自己也可以轻松地完成。很好,编程/编码已经从一个基于事实的主题转变为一种哲学:)为什么要这样做?类通常部署在JAR中。因此,如果我想在指定的关键点将性能入口/出口代码注入到应用程序中,这就是如何对已部署的应用程序执行此操作。这是一个非常糟糕的主意。你不应该改变你正在测试的东西,即使是为了性能。如果您的perf monitor提取代码中存在错误,并且会对您的性能产生负面影响,那该怎么办?实现这一点的正确方法是对jvm进行测试,jvm有一个用于这类事情的api。看看jprofiler()。我同意,做一些盲目的事情是个坏主意。但我将做的不仅仅是性能监控,更多的是“开箱即用”功能。其中一些功能将使用更亲密/直接的方法来实现,例如使用代理。但是,对于我正在处理的环境来说,这是不可能的。我只是在探索阶段寻找其他方法。谢谢你的建议。你能提供一个可编译的例子吗?