2012年5月21日

自ZIP或JAR file中載入class執行

對於從事多年的應用系統開發,通常會選擇web application方式來進行;多半因為可以用http方式來取代自行設計有關socket的程式。 但,每每專案中或多或少會做些有用的utility供其他程式使用,在系統不大的情形下,少有將這些utility做成JAR file(放在WEB-INF/lib目錄)的需要;所以,就試想「如果可以像JAR file那樣,直接拿WAR file來使用就好了,如:
java -jar myapp.war

可是,大家都知道WAR file與JAR file結構不同,要達到這個目的就得先知道MANIFEST的結構;例如,指定一個main class:
Manifest-Version: ...
Ant-Version: ...
Created-By: ...
Main-Class: Main

但WAR file中,class不是在WEB-INF/classes中,就是在WEB-INF/lib中;所以還需指定 classpath:
...
Class-Path: WEB-INF/classes
...

多半按上述方法完成後,如同這篇說明一樣,測試的結果會是:
java.lang.NoClassDefFoundError

顯然,Class-Path不是這樣子用的。
不過,該說明的解答中提到,可以模仿Hudson.war一般:自製main class。另外,因為WAR file無異於其他的ZIP file,於是請教了谷歌大神之後,找到這篇教如何自ZIP或JAR file中載入class。
最後就完成了我的需求「即是WAR file,又可以執行某一class」:
java -jar myapp.war "some-parameter-for-launching-SomeUtil"

程式如下:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import my.util.SomeUtil;

public class Main extends ClassLoader {
 private static final String DELEGATE = "my.util.SomeUtil";
 private final ZipFile file;

 public Main(String filename) throws IOException {
  this.file = new ZipFile(filename);
 }
 
 @Override
 public Class findClass(String name) throws ClassNotFoundException {
  ZipEntry entry = this.file.getEntry(
    (this.file.getName().endsWith("war") ? "WEB-INF/classes/" : "") + 
    name.replace(".", "/") + ".class"
    );
  if (entry == null) {
   throw new ClassNotFoundException(name);
  }

  try {
   byte[] array = new byte[1024];
   InputStream ins = this.file.getInputStream(entry);
   ByteArrayOutputStream out = new ByteArrayOutputStream(array.length);
   int length = ins.read(array);

   while (length > 0) {
    out.write(array, 0, length);
    length = ins.read(array);
   }
   return defineClass(name, out.toByteArray(), 0, out.size());

  } catch (IOException exception) {
   throw new ClassNotFoundException(name, exception);
  }
 }


 /**
  * @param args
  */
 public static void main(String[] args) {
  if(args != null) {
   try {
    Main classLoader = new Main(args[0]);
    classLoader.findClass(DELEGATE);
    System.out.println(SomeUtil.doSomething(args[1]));

   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 }
}

不過,這個Main class記得要包在WAR file中的根目錄下。

沒有留言: