Gradle実践シリーズその6です〜
今回はExecutableJar、所謂実行可能jarを生成する方法についてです。
簡単な実行可能jarの作り方としては以下のコードがよくサンプルとして挙がります。
jar { from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } manifest.mainAttributes("Main-Class" : "tree.s2.batch.SampleBatch") }
これで実行可能jarは作成できました!やったぜ。
・・・
・・・・・
・・・・・・・
全然やってない
全然ダメです。
何がダメかというと、jarの中身がダメなのです。
ちょっとこのjarを解凍してみます。
・・・中略・・・ creating: META-INF/maven/org.seasar.mayaa/ creating: META-INF/maven/org.seasar.mayaa/mayaa/ inflating: META-INF/maven/org.seasar.mayaa/mayaa/pom.properties inflating: META-INF/maven/org.seasar.mayaa/mayaa/pom.xml creating: META-INF/resource/ inflating: META-INF/resource/JAXEN-LICENSE.txt inflating: META-INF/resource/LICENSE-2.0.htm inflating: META-INF/resource/RHINO-NOTICE.txt inflating: META-INF/resource/acknowledgment.txt inflating: META-INF/resource/mayaa_banner.gif inflating: META-INF/resource/release.txt creating: org/ creating: org/seasar/ creating: org/seasar/mayaa/ inflating: org/seasar/mayaa/ContextAware.class inflating: org/seasar/mayaa/FactoryFactory.class inflating: org/seasar/mayaa/ParameterAware.class inflating: org/seasar/mayaa/PositionAware.class ・・・中略・・・
なんじゃこりゃあ!!
私が想像していたのはこう↓ですが・・・
lib ├── 1.jar ├── 2.jar └── 3.jar
しかし実際はjarが解凍された状態で入ってるのです。
この一々解凍する処理が非常に重く、時間がかかる原因なのです。
何故わざわざjarを解凍してから再度jarにする必要があるのか・・・
あまり仕様を把握していませんが、原因は zipTree(it) と思われます。
jarが解凍されてしまうことで、resources系が全部表に出てきてしまい、同名のファイル名があった場合、上書きされてしまいます。これは使えませんね。
jarを解凍せず高速に実行可能jarを生成
前述のコードでダメな点は以下であると解りました。
- classpath上のjarが解凍されてしまう。
- zipTreeで解凍される処理の負荷が高く時間がかかる。
- resourcesが上書きされてしまう。
ではこれを解消しましょう。
jar { copy { from configurations.compile into "distribution/lib" } def manifestClasspath = configurations.compile.collect{ 'lib/' + it.getName() }.join(' ') manifest { attributes "Main-Class" : "tree.s2.batch.SampleBatch" attributes 'Class-Path': manifestClasspath } from (configurations.compile.resolve().collect { it.isDirectory() ? it : fileTree(it) }) { exclude 'META-INF/MANIFEST.MF' exclude 'META-INF/*.SF' exclude 'META-INF/*.DSA' exclude 'META-INF/*.RSA' exclude '**/*.jar' } destinationDir = file("distribution") archiveName = 'batch.jar' }
1個づつ解説します。
copy
最初にコンパイルに必要なjarをコピーしておきます。これでjarが解凍される心配もありません。
注意点として、${buildDir}にコピーすると、cleanが影響するためか、コピーがうまくできません。
従って${buildDir}以外にコピーしています。
manifestClasspath
このままだと実行可能jarがコピーしたjarを認識できません。
なので、MANIFESTの属性である「Class-Path」にjarを列挙します。
ttree-macpro:distribution tree$ cat META-INF/MANIFEST.MF Manifest-Version: 1.0 Main-Class: tree.s2.batch.SampleBatch Class-Path: lib/mayaa-1.1.18.jar lib/antlr-2.7.7.jar lib/aopalliance-1 .0.jar lib/guava-14.0.1.jar lib/commons-beanutils-1.8.3.jar lib/commo ns-beanutils-core-1.8.3.jar lib/commons-collections-3.2.1.jar lib/com mons-configuration-1.9.jar lib/commons-digester-2.1.jar lib/commons-e l-1.0.jar lib/commons-fileupload-1.3.jar lib/commons-io-2.4.jar lib/c ・・・略・・・
半角スペース区切りでjarを列挙します。
jdbc.dicon等も含めれば、DB等の接続先も変える事ができそうです。
fileTree(it)
zipTreeだとjarファイルが解凍されてしまうので、fileTreeに変更します。
zipTreeと違って解凍処理が入らないので非常に高速です。
exclude '**/*.jar'
今回はfat.jarではないので、実行可能jar内からjarを除外し、libs/*.jarをMANIFESTのClass-Pathを参照しています。
最終的な構造とjarの中身
構造は以下の通りです。
distribution/ ├── batch.jar └── lib ├── activation-1.1.jar ├── antlr-2.7.7.jar ・・・略・・・ ├── xml-apis-1.3.03.jar └── zookeeper-3.4.5.jar
batch.jarの中身は以下の通りです。
tree-macpro:distribution tree$ unzip batch.jar Archive: batch.jar creating: META-INF/ inflating: META-INF/MANIFEST.MF creating: tree/ creating: tree/s2/ creating: tree/s2/batch/ inflating: tree/s2/batch/BaseBatch.class inflating: tree/s2/batch/SampleBatch.class inflating: app.dicon inflating: convention.dicon inflating: creator.dicon inflating: customizer.dicon inflating: logback.xml inflating: s2container.dicon
こうして生成した実行可能jarを実行するには以下のようにします。
tree-macpro:distribution tree$ java -jar batch.jar INFO 2013/06/24 23:07:50:716 [main] (SingletonS2ContainerFactory.log:234) - s2-frameworkのバージョンは2.4.45です。 INFO 2013/06/24 23:07:50:720 [main] (SingletonS2ContainerFactory.log:234) - s2-extensionのバージョンは2.4.45です。 INFO 2013/06/24 23:07:50:721 [main] (SingletonS2ContainerFactory.log:234) - s2-tigerのバージョンは2.4.45です。 INFO 2013/06/24 23:07:51:545 [main] (HttpClientUtil.createClient:103) - Creating new http client, config:maxConnections=128&maxConnectionsPerHost=32&followRedirects=false INFO 2013/06/24 23:07:51:571 [main] (SingletonS2ContainerFactory.info:137) - Running on [ENV]ut, [DEPLOY MODE]Warm Deploy WARN 2013/06/24 23:07:51:628 [main] (AopProxy.log:237) - tree.s2.batch.SampleBatchのメソッド(main)にはアスペクトを適用できない修飾子が指定されています WARN 2013/06/24 23:07:51:629 [main] (AopProxy.log:237) - tree.s2.batch.SampleBatchのメソッド(execute)にはアスペクトを適用できない修飾子が指定されています INFO 2013/06/24 23:07:51:630 [main] (BaseBatch.execute:94) - [サンプルバッチ] 開始。 INFO 2013/06/24 23:07:51:630 [main] (SampleBatch.run:22) - サンプル INFO 2013/06/24 23:07:51:630 [main] (BaseBatch.execute:102) - [サンプルバッチ] 正常終了。 INFO 2013/06/24 23:07:51:631 [main] (BaseBatch.execute:115) - [サンプルバッチ] 実行時間=0秒。
この実行可能jarを生成するのは大分苦労しました。
微妙な点も多いかと思いますが、参考にはなるかと思います。
javaのビルドをgradleで行う:調査編1:maven・SBTとの違い - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:調査編2:依存から1行で特定jarを除外! - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:調査編3:seasar系の依存の注意点 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:調査編4:Groovy Librariesの罠 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編1:jsとcssのcombineとminify - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編2:静的ファイルの圧縮 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編3:jspの文字列置換(ReplaceTokens) - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編4:あれどうやるの?の疑問と自己解決 - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編5:マルチプロジェクト時のprovidedCompileについて - 文系プログラマによるTIPSブログ
javaのビルドをgradleで行う:実践編6:高速に実用的な実行可能jarを生成する - 文系プログラマによるTIPSブログ