Aviator を使ったルールエンジン

Aviator は、Dianping(大众点评)が公開したJava式計算エンジンです。このエンジンは、文字列形式の式(例:`"a + b > 10 ? 'pass' : 'fail'"`)を実行時に動的に解析し、評価します。Aviator は、ルールエンジン、リスク管理、動的ロジック、レポート計算などさまざまな場面で活用されています。

QLExpress と似ていますが、Aviator は設計がシンプルで、特に数値計算において優れた性能を発揮します。また、関数プログラミングスタイルに対応しています。

1. 基本仕組み

Aviator は以下の3つのフェーズを経て式を評価します。

[文字列表式]
     ↓ (トークン解析)
[Tokens:変数、演算子、数値、括弧...]
     ↓ (構文解析 + 編作文字列)
[AviatorFunction(内部表現)]
     ↓ (実行 / JIT 最適化)
[評価結果]
  • **コンパイルフェーズ**:式を `AviatorFunction` オブジェクトにコンパイル(キャッシュ可能)
  • **実行フェーズ**:変数コンテキスト(Map)を渡して `execute()` を呼び出し、結果を得る
  • **反射なし**:予め定義された演算子や関数を用いるため、実行時の反射コストを抑えます

**Aviator の特徴**:

  • 式を一度だけコンパイルし、複数回実行しても効率が良い
  • **型推論**:int/long/double 等の数値型を自動的に処理
  • **安全な評価**:デフォルトではJavaメソッドへのアクセスを制限
特徴 説明
**高性能** 単一式のQPSは50万以上(Groovy/JSR223を大幅に上回る)
**軽量** JARサイズは約300KB、外部依存なし
**多様な文法** 算術、論理、三項演算、正規表現、コレクション操作をサポート
**カスタム関数** Java Lambdaまたはメソッドを関数として登録可能
**安全な制御** デフォルトで反射を禁止、ホワイトリスト関数のみ許可
**組み込み関数ライブラリ** `string`, `math`, `regex`, `date` 等の便利な関数を提供

2. 初心者向け例

2.1 Maven 依存関係の追加

<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>5.4.1</version> <!-- 最新バージョン推奨 -->
</dependency>

GitHubリポジトリ:https://github.com/killme2008/aviator

2.2 基本的な式計算

import com.googlecode.aviator.AviatorEvaluator;

public class BasicExample {
    public static void main(String[] args) {
        // 即時評価(生産環境では使用しない)
        Object result = AviatorEvaluator.execute("1 + 2 * 3");
        System.out.println(result); // 7

        // コンパイルして再利用(推奨!)
        Expression exp = AviatorEvaluator.compile("a + b * 2");
        Map<String, Object> env = new HashMap<>();
        env.put("a", 10);
        env.put("b", 20);
        Object res = exp.execute(env);
        System.out.println(res); // 50
    }
}

**注意**:生産環境では `compile()` + `execute(env)` を使用してください。

2.3 条件判断と三項演算

Expression exp = AviatorEvaluator.compile("score >= 90 ? '優秀' : (score >= 60 ? '合格' : '不合格')");
Map<String, Object> env = Map.of("score", 85);
System.out.println(exp.execute(env)); // 合格

2.4 カスタム関数の登録(重要な機能)

方式1:Lambda を使って関数登録(推奨)

// max(a, b) 関数の登録
AviatorEvaluator.addFunction(new AbstractFunction() {
    @Override
    public String getName() { return "max"; }
    @Override
    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
        Number a = FunctionUtils.getNumberValue(arg1, env);
        Number b = FunctionUtils.getNumberValue(arg2, env);
        return AviatorNumber.valueOf(Math.max(a.doubleValue(), b.doubleValue()));
    }
});

// 使用例
Expression exp = AviatorEvaluator.compile("max(x, y)");
Map<String, Object> env = Map.of("x", 10, "y", 20);
System.out.println(exp.execute(env)); // 20.0

方式2:静态メソッドの登録(Aviator 5.0+)

// ツールクラスの定義
public class MathUtils {
    @AviatorFunction("str_len") // 関数名
    public static long strLen(String s) {
        return s == null ? 0 : s.length();
    }
}

// 関数の自動スキャン(有効にする必要があります)
AviatorEvaluator.setOption(Options.FUNCTION_SCAN_PACKAGE, "com.yourpkg");
AviatorEvaluator.loadFunctions(); // @AviatorFunction を持つクラスをスキャン

// 使用例
System.out.println(AviatorEvaluator.execute("str_len('hello')")); // 5

2.5 複雑なオブジェクト(POJO)の操作

public class User {
    private String name;
    private int age;
    // getter メソッドはpublicにする必要があります!
    public String getName() { return name; }
    public int getAge() { return age; }
}

// 使用例
User user = new User();
user.name = "Alice";
user.age = 25;

Map<String, Object> env = Map.of("user", user);
Object res = AviatorEvaluator.execute("user.age > 18 && user.name == 'Alice'", env);
System.out.println(res); // true

Aviator はgetter メソッドを通じてオブジェクトのプロパティにアクセスします(例:`user.age` → `user.getAge()`)

3. 高度な使い方

3.1 組み込み関数ライブラリ(登録不要)

// 文字列操作
AviatorEvaluator.execute("string.contains('hello', 'll')"); // true
AviatorEvaluator.execute("string.length('world')");        // 5

// 数学関数
AviatorEvaluator.execute("math.sqrt(16)");                 // 4.0
AviatorEvaluator.execute("math.round(3.7)");               // 4

// 正規表現
AviatorEvaluator.execute("'abc123' =~ /\\d+/");           // true

// コレクション操作
AviatorEvaluator.execute("count([1,2,3])");                // 3
AviatorEvaluator.execute("include([1,2,3], 2)");           // true

全ての組み込み関数のリスト:https://github.com/killme2008/aviator#built-in-functions

3.2 コレクションと配列操作

List<Integer> scores = Arrays.asList(85, 90, 78);
Map<String, Object> env = Map.of("scores", scores);

// 平均点の計算
Object avg = AviatorEvaluator.execute("reduce(scores, 0, (acc, x) -> acc + x) / count(scores)", env);
System.out.println(avg); // 84.333...

// 高得点のフィルタリング
Object highScores = AviatorEvaluator.execute("filter(scores, x -> x > 80)", env);
System.out.println(highScores); // [85, 90]

Aviator 5.0+ ではLambda式をサポートしています。

3.3 性能チューニング:式のキャッシュ

// グローバルキャッシュ(デフォルト有効)
AviatorEvaluator.setOption(Options.EXPRESSION_CACHE_SIZE, 1000);

// 手動キャッシュ
Map<String, Expression> cache = new ConcurrentHashMap<>();
String exprStr = "a + b";
Expression exp = cache.computeIfAbsent(exprStr, AviatorEvaluator::compile);
exp.execute(env);

3.4 安全性制御

// 危険な操作を無効化(デフォルトでは無効)
AviatorEvaluator.setOption(Options.ALLOW_ACCESS_STATIC_FIELD, false);
AviatorEvaluator.setOption(Options.ALLOW_CLASS_LOADER, false);

// ホワイトリスト関数のみ許可(addFunction を使って管理)

4. 代表的な使用例

シナリオ 式例
**リスク管理** `"user.riskLevel == 'HIGH' && order.amount > 10000"`
**動的価格設定** `"basePrice * (1 + discountRate) - couponValue"`
**ゲームスキル計算** `"player.hp -= enemy.attack * 1.5"`
**データフィルタリング** `"include(user.tags, 'VIP') && user.balance > 100"`
**しきい値設定** `"metric.value > config.threshold"`

5. QLExpress との比較

特徴 Aviator QLExpress
**性能** ⭐⭐⭐⭐⭐(数値計算が速い) ⭐⭐⭐⭐
**文法の簡潔性** 数学式に近い if/for 等の制御文をサポート
**関数登録** Lambda / アノテーション 反射を用いた登録
**学習曲線** 平緩 やや急
**コミュニティ活性度** 中程度 Aliyun 言語、活発
**適切な使用例** 頻繁な計算、シンプルなルール 複雑なスクリプトロジック

**選択の参考**:

  • 純粋な式計算(式、条件判断)→ **Aviator**
  • if/for/while 等の制御文が必要 → **QLExpress**

6. 最佳プラクティス

  1. **式をコンパイルするようにする**:`compile()` を使う代わりに `execute(String)` を使わない
  2. **Expression オブジェクトをキャッシュする**:再コンパイルを避ける
  3. **組み込み関数を優先する**:`math.sqrt()` を使う代わりにカスタム関数を作らない
  4. **POJO に getter メソッドを定義する**:プロパティにアクセス可能にする
  5. **例外処理を行う**:`ExpressionRuntimeException` をキャッチする
try {
    exp.execute(env);
} catch (ExpressionRuntimeException e) {
    log.error("式評価エラー: {}", e.getMessage());
}

7. 完整な例:リアルタイムリスク管理

public class RiskEngine {
    private final Map<String, Expression> ruleCache = new ConcurrentHashMap<>();

    public boolean evaluate(String rule, Map<String, Object> context) {
        Expression exp = ruleCache.computeIfAbsent(rule, AviatorEvaluator::compile);
        try {
            Object result = exp.execute(context);
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            log.warn("ルール評価失敗: {}", rule, e);
            return false; // 安全性のためにfalseを返す
        }
    }
}

// 使用例
RiskEngine engine = new RiskEngine();
Map<String, Object> ctx = Map.of(
    "ip", "192.168.1.100",
    "amount", 15000,
    "isVip", true
);
boolean blocked = engine.evaluate("amount > 10000 || !isVip", ctx);

8. 結論

Aviator は高性能、安全、シンプルな文法を備えたJava式エンジンです。特に頻繁に呼ばれるルール計算に適しています。適切なコンパイルキャッシュ、組み込み関数、カスタム関数の利用により、業務ロジックを柔軟に動的に管理できます。

**公式ドキュメント**:https://github.com/killme2008/aviator

タグ: Aviator Java式計算 ルールエンジン

6月14日 00:50 投稿