ソースコードが紛失している、あるいは開発環境が整っていない状況で、運用中のJARやWARファイルに対して緊急の修正が必要になることがあります。このような場合、バイナリ(.classファイル)を直接操作し、再パッケージ化することで対応が可能です。
1. アーカイブの展開と構成確認
まず、対象となるJARまたはWARファイルを展開し、内部構造を確認します。Java標準のjarコマンドを使用して、アーカイブを解凍します。
# WARファイルの展開
jar xvf application.war
# JARファイルの展開
jar xvf service-module.jar
展開後、修正対象となるクラスファイルのパスを特定します。設定ファイルやプロパティファイルなどの非Javaファイルであれば、テキストエディタで直接修正が可能です。
2. Javassistを用いたクラスファイルの書き換え
コンパイル済みの.classファイルを修正する場合、デコンパイルして再コンパイルする手法は依存関係の解決が難しいため、Javassistライブラリを使用してバイトコードを直接操作する方法が効率的です。
2.1 開発環境の準備
Mavenプロジェクトを作成し、以下の依存関係をpom.xmlに追加します。
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.0-GA</version>
</dependency>
2.2 ロジックの修正プログラム実装
以下のコード例は、特定のメソッドの戻り値を強制的に変更するプログラムです。修正対象のJARファイルをクラスパスに追加してから操作を行います。
import javassist.*;
public class BytecodeModifier {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
// 修正対象のJARをパスに追加
pool.insertClassPath("target-app.jar");
// 修正対象のクラスを取得
CtClass ctClass = pool.get("com.example.logic.AuthManager");
// 修正対象のメソッドを取得
CtMethod method = ctClass.getDeclaredMethod("isValidLicense");
// メソッド本体を書き換え
method.setBody("{ return true; }");
// 修正したクラスファイルをカレントディレクトリに出力
ctClass.writeFile();
ctClass.detach();
}
}
このプログラムを実行すると、パッケージ構造に基づいたディレクトリ(例: com/example/logic/AuthManager.class)が生成されます。
3. アーカイブへの書き戻しと再パッケージ化
生成された新しい.classファイルを、元のアーカイブ構造に反映させます。jar -uvfコマンドを使用して、特定のファイルのみを更新します。
# 単一クラスの更新
jar -uvf application.jar com/example/logic/AuthManager.class
# ディレクトリ単位の更新
jar -uvf application.jar WEB-INF/classes/
4. ネストされたJARファイルに関する注意点
Spring Bootなどで採用されている、JARの中にさらにJARが含まれる(Executable JAR)形式の場合、再パッケージ化の際に注意が必要です。内部のライブラリ(BOOT-INF/lib/*.jarなど)を修正して差し戻す場合、圧縮アルゴリズムの不整合により実行時にjava.lang.IllegalStateExceptionが発生することがあります。
このエラーを回避するには、以下の点に留意してください。
- ネストされたJARを更新する際は、圧縮をかけない「無圧縮(ストアモード)」でアーカイブに追加する必要があります。
- GUIの圧縮ツール(WinRARなど)を使用する場合は、圧縮メソッドを「圧縮しない」または「ストア」に設定して、ファイルをドラッグ&ドロップで置換してください。
正常に更新が完了すれば、ソースコードなしでアプリケーションのロジック変更が反映されます。