通过java -jar来执行war包

最近将mysql改为了h2(仅仅是测试,应该还有部分sql语句待调整),加上缩略图服务亦可不依赖GraphicsMagkic,这样在装了jre8和sevlet容器的环境中就可以运行,为了更加的方便本地项目的启动,准备做成通过jar命令即可执行的war包,Google了下后发现,http://hudson-ci.org/ 提供的hudson.war可以达到这种效果 下载了hudson.war之后解压一看是这个样子的:

QQ截图20161129140043.png

除了基本的文件之外发现org和libs文件夹不是一个web项目所必须的,打开libs之后可以发下,里面放着jetty的jar包还有hudson-jetty-war-executable.jar这个可执行的jar包。反编译之后看了下源码,发现了它的运行原理:首先通过org文件夹下org.eclipse.hudson.war.Execuator获取war包的路径:

ProtectionDomain protectionDomain = Executable.class.getProtectionDomain();
URL warUrl = protectionDomain.getCodeSource().getLocation();

获取到war路径之后,通过JarFile来获取libs下文件夹下的jar包,将它们拷贝到系统临时文件夹中:

 private List<URL> extractJettyJarsFromWar(String warPath)
    throws IOException
  {
    JarFile jarFile = new JarFile(warPath);

    List jarUrls = new ArrayList();

    InputStream inStream = null;
    try
    {
      for (String entryPath : this.jettyJars)
      {
        try
        {
          tmpFile = File.createTempFile(entryPath.replaceAll("/", "_"), "hudson");
        }
        catch (IOException e)
        {
          File tmpFile;
          String tmpdir = System.getProperty("java.io.tmpdir");
          throw new IOException("Failed to extract " + entryPath + " to " + tmpdir, e);
        }
        File tmpFile;
        JarEntry jarEntry = jarFile.getJarEntry(entryPath);
        inStream = jarFile.getInputStream(jarEntry);

        OutputStream outStream = new FileOutputStream(tmpFile);
        try {
          byte[] buffer = new byte[8192];
          int readLength;
          while ((readLength = inStream.read(buffer)) > 0)
            outStream.write(buffer, 0, readLength);
        }
        catch (Exception exc) {
          exc.printStackTrace();
        } finally {
          outStream.close();
        }

        tmpFile.deleteOnExit();

        jarUrls.add(tmpFile.toURI().toURL());
      }
    }
    catch (Exception exc) {
      exc.printStackTrace();
    } finally {
      if (inStream != null) {
        inStream.close();
      }
    }

    return jarUrls;
  }
}

拷贝完之后通过UrlClassLoader来寻找Jetty的启动类,然后通过反射调用Jetty启动类的start方法来启动Jetty:

 List jarUrls = extractJettyJarsFromWar(warUrl.getPath());
 ClassLoader urlClassLoader = new URLClassLoader((URL[])jarUrls.toArray(new URL[jarUrls.size()]));
 Thread.currentThread().setContextClassLoader(urlClassLoader);
 Class jettyUtil = urlClassLoader.loadClass("org.eclipse.hudson.jetty.JettyLauncher");
 Method mainMethod = jettyUtil.getMethod("start", new Class[] { [Ljava.lang.String.class, URL.class });
 mainMethod.invoke(null, new Object[] { this.arguments.toArray(new String[this.arguments.size()]), warUrl });

Jetty启动就很简单,只要将war包的路径告诉它,由于hudson的启动类中还有一些其他的配置,这里给出最简单的启动:

Server server = new Server(port);
WebAppContext context = new WebAppContext();
context.setContextPath(appPros.getProperty("app.contextPath", ""));
context.setDescriptor(warUrl.toExternalForm() + "/WEB-INF/web.xml");
context.setServer(server);
context.setWar(warUrl.toExternalForm());
server.setHandler(context);
server.setStopAtShutdown(true);
server.start();
try {
//启动浏览器,打开默认地址
Desktop.getDesktop()
.browse(new URI(appPros.getProperty("app.schema") + "://" + appPros.getProperty("app.domain")
+ (port.intValue() == 80 ? "" : ":" + port + appPros.getProperty("app.contextPath"))));
} catch (Exception e) {
}
server.join();

最后,通过修改META-INF/MANIFEST.MF文件来配置war包的Main-Class,我的文件是这样的:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: Administrator
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_77
Main-Class: Starter

最后将libs和MainClass放入到war包根目录,通过执行java -jar name.war包即可启动。