boxlang-interceptors について
BoxLang向けに、横断的な処理を業務コードから分離するイベント駆動型インターセプターの構築方法をまとめたスキルです。登録、イベント処理、実行優先度、非同期通知を扱います。
主な機能
- 01名前付きイベントのハンドラー
- 02同期・非同期のイベント通知
- 03優先度による実行順制御
- 04アプリケーション・モジュール登録
- 05クラス生成時のライフサイクルイベント
活用シーン
- 01操作にログ記録やセキュリティ検査を追加
- 02保存前のデータ検証やサニタイズ
- 03イベント後のバックグラウンド処理
- 04依存性注入やクラス生成の監査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ortus-boxlang/ skills --skill boxlang-interceptorsインストール後、Claude Code で/boxlang-interceptorsを入力して読み込まれたか確認します。
詳細
BoxLang インターセプター
BoxLang は、開発者と AI エージェントが連携してアプリケーションを構築、モダナイズ、実行するための、AI ネイティブなソフトウェア生産性プラットフォームです。
概要
BoxLang のインターセプターは、イベント駆動型の通知システムを通じて、アスペクト指向プログラミング(AOP)の機能を提供します。インターセプターは名前付きイベントを監視し、ログ記録、セキュリティ、キャッシュ、検証といった横断的なロジックを実行します。これにより、そのロジックをビジネスコードに結合させずに済みます。優先度で実行順を制御でき、非同期の通知はバックグラウンドスレッドで実行されます。
インターセプターの作成
/**
* interceptors/RequestLogger.bx
*/
class {
function onRequestStart( struct interceptData ) {
request.startTime = getTickCount()
writeLog( text: "Request started: #cgi.script_name#", type: "information" )
}
function onRequestEnd( struct interceptData ) {
var duration = getTickCount() - request.startTime
writeLog( text: "Completed in #duration#ms: #cgi.script_name#", type: "information" )
}
}
インターセプターの登録
Application.bx(アプリケーションスコープ)
class {
this.name = "MyApp"
this.interceptors = [
{ class: "interceptors.RequestLogger" },
{ class: "interceptors.SecurityCheck", priority: 1 },
{ class: "interceptors.CacheWarmer", priority: 10 }
]
function onApplicationStart() {
announce( "onApplicationStart", { timestamp: now() } )
}
}
プログラムによる登録(BIF)
// Register at runtime using BoxRegisterInterceptor()
BoxRegisterInterceptor( "interceptors.RequestLogger" )
// With named options
BoxRegisterInterceptor(
class: "interceptors.SecurityCheck",
priority: 1,
properties: { secret: getSetting( "JWT_SECRET" ) }
)
イベントの通知
// Synchronous — all listeners execute before returning
announce( "userCreated", {
userID: newUser.id,
email: newUser.email,
timestamp: now()
} )
// Asynchronous — runs in a background thread
announceAsync( "emailQueued", {
to: user.email,
subject: "Welcome"
} )
一般的なインターセプターパターン
操作前/操作後のフック
class DataSanitizer {
/** Sanitize data before save */
function preDataSave( interceptData ) {
var data = interceptData.data
data.each( ( key, value ) -> {
if ( isSimpleValue( value ) ) data[key] = trim( value )
} )
if ( data.keyExists( "description" ) ) {
data.description = htmlEditFormat( data.description )
}
}
/** Clear cache after save */
function postDataSave( interceptData ) {
cacheRemove( "#interceptData.entityName#_#interceptData.entityID#" )
}
}
検証ガード
class ValidationInterceptor {
property name="validationService" inject="ValidationService"
function preEntitySave( interceptData ) {
var result = validationService.validate( interceptData.entity )
if ( result.hasErrors() ) {
interceptData.abortProcessing = true
throw(
type: "ValidationException",
message: "Validation failed",
detail: jsonSerialize( result.getAllErrors() )
)
}
}
}
セキュリティガード
class SecurityInterceptor {
property name="authService" inject="AuthService"
function preHandler( interceptData ) {
// Skip public areas
if ( listFindNoCase( "home,auth", interceptData.handler ) ) return
if ( !authService.isLoggedIn() ) {
interceptData.event.overrideEvent( "auth.login" )
interceptData.abortProcessing = true
}
}
}
非同期のバックグラウンド処理
class AsyncProcessor {
/** Kick off work asynchronously */
function onEmailQueued( interceptData ) {
announceAsync( "processEmail", {
to: interceptData.to,
subject: interceptData.subject,
body: interceptData.body
} )
}
/** Worker — runs in background thread */
function onProcessEmail( interceptData ) {
mailService.send(
to: interceptData.to,
subject: interceptData.subject,
body: interceptData.body
)
}
}
優先度を使った責任の連鎖
// Lower number = runs earlier
this.interceptors = [
{ class: "interceptors.RequestLogger", priority: 1 },
{ class: "interceptors.AuthCheck", priority: 2 },
{ class: "interceptors.RateLimiter", priority: 3 }
]
インターセプターの状態
class RequestTracker {
variables.requestMap = {}
function onRequestStart( interceptData ) {
var id = createUUID()
variables.requestMap[id] = { startTime: getTickCount(), path: cgi.script_name }
interceptData.requestID = id
}
function onRequestEnd( interceptData ) {
var id = interceptData.requestID
var data = variables.requestMap[id]
writeLog( "Request #id# done in #getTickCount() - data.startTime#ms" )
structDelete( variables.requestMap, id )
}
}
モジュールの登録
モジュールは ModuleConfig.bx でインターセプターを登録します。
// ModuleConfig.bx
class {
function configure() {
interceptors = [
{ class: "#moduleMapping#.interceptors.MyInterceptor", priority: 5 }
]
}
}
クラスのインスタンス化イベント(v1.18+)
| イベント | 発生時点 | データ |
|---|---|---|
afterBoxClassCreation | インスタンスが完全に定義された後(疑似コンストラクターの実行、インターフェースおよび抽象メソッドの検証が完了)で、init() の前。createObject() やデシリアライズなどの noInit による生成時にも発生します。extends チェーン内のスーパークラスでは発生しません。 | instance、className、noInit、context |
afterBoxClassInit | init()(または暗黙のコンストラクター)の完了後。noInit による生成時、または init() が例外をスローした場合は発生しません。 | instance、result、className、context |
依存性の注入、監査、可観測性に使用できます。
ベストプラクティス
- 単一責任 — 1つのインターセプターにつき、1つの関心事を扱います
- インターセプターは高速に保つ — 時間のかかる処理は
announceAsync()に移します - ハンドラー内では必ず
try/catchを使う —onUserCreated内で例外がスローされても、ユーザーの作成を妨げないようにします - 条件に応じて早期リターンする — メソッドの先頭で、対象外のエンティティやイベントをスキップします
- 優先度 — セキュリティインターセプターには最小値の優先度 1 を、ログ記録には大きな値を設定します
- 再帰を避ける — 監視しているイベントと同じイベントを通知しないでください
// ✅ Fast: log then hand off heavy work
function onUserCreated( interceptData ) {
writeLog( "User created: #interceptData.email#" )
announceAsync( "sendWelcomeEmail", interceptData )
}
// ✅ Guard with early return
function onDataSave( interceptData ) {
if ( interceptData.entityName != "User" ) return
// user-specific logic
}
// ✅ Error-safe side effects
function onUserCreated( interceptData ) {
try {
sendEmail( interceptData.email )
} catch ( any e ) {
writeLog( "Email error: #e.message#" )
// Do NOT rethrow — don't break user creation
}
}
// ❌ Recursion: triggers same interceptor again
function onDataSave( interceptData ) {
announce( "onDataSave", data ) // infinite loop
}
コメント
Markdown 対応