はじめに
前回記事では、SIcore というフレームワークを作るに至った背景と、初心者向け設計が AI(GitHub Copilot など)にも相性が良いかもしれないという話を書きました。
今回は、その設計の中から 「URLがそのまま実行クラス名」 のルールと実装について、概要〜コードレベルまでまとめます。
この記事で書くこと
- どんなURLマッピング仕様にしているか(ルール)
- 設定で何を変えられるか
- 実装はどこで、どう動いているか(Javaコード)
- この方式のメリット/注意点
仕様:URLがそのまま実行クラス名
SIcoreでは、ブラウザから指定されたURLと実行クラス(Webサービス)のマッピングを、単純な文字列置換(+ルートパッケージ付与)で行っています。
URL: http://localhost:8000/services/exmodule/ExampleListSearch
↓ マッピング
実行クラス: com.example.app.service.exmodule.ExampleListSearch
ポイントは3つです。
- URL の
/services/は Webサービスのコンテキストパス(設定で変更可能)。 -
/services/以降のパスを.区切りに文字列置換して、パッケージ・クラス名に使用する。 - パッケージの先頭に Webサービスのルートパッケージ(設定で変更可能)を付与して、実行クラス名とする。
これにより「ルーティング定義ファイル」「アノテーションスキャン」「コントローラ登録」といった仕組みがなくても、URL だけで実行クラスが決定します。
設定:コンテキストとルートパッケージ
設定は config/web.properties にあります。
# JSONサービスコンテキストパス
json.service.context=services
# JSONサービスルートパッケージ
json.service.package=com.example.app.service
これらの設定を変更することで、以下をカスタマイズできます。
- URL の
/services部分を別のコンテキストパスに変更する - 実行クラスを配置するベースとなるルートパッケージを変更する
実装の全体像:どこでURLを受けているか
Webサーバーは、JDK標準の com.sun.net.httpserver.HttpServer で立てています。
src/com/onepg/web/StandaloneServer.java で設定を読み込み、コンテキスト /services に JsonServiceHandler を登録しています。
// JSONサービスハンドラー
final String jsonServiceContext = propMap.getString("json.service.context");
final String jsonServicePackage = propMap.getString("json.service.package");
this.server.createContext("/" + jsonServiceContext,
new JsonServiceHandler(jsonServiceContext, jsonServicePackage));
実装の詳細:URL → 実行クラス名の生成
URLから実行クラス名を組み立てているのは、src/com/onepg/web/JsonServiceHandler.java のこのメソッドです。
private String buildClsNameByReq(final String reqPath) {
return this.svcClsPackage + "."
+ reqPath.replace("/" + this.contextPath + "/", "").replace("/", ".");
}
例えば reqPath が /services/exmodule/ExampleListSearch の場合、
-
replace("/services/", "")でexmodule/ExampleListSearch -
replace("/", ".")でexmodule.ExampleListSearch - 先頭に
com.example.app.service.を付与
最終的に com.example.app.service.exmodule.ExampleListSearch という完全修飾クラス名が生成されます。
実装の詳細:リフレクションで実行
クラス名が決まったら、リフレクションでインスタンス化して実行します。
final Class<?> cls = Class.forName(clsName);
final Object clsObj = cls.getDeclaredConstructor().newInstance();
if (!(clsObj instanceof AbstractWebService)) {
throw new RuntimeException(
"Classes not inheriting from web service base class (AbstractWebService) cannot be executed. ");
}
((AbstractWebService) clsObj).execute(io);
ここで AbstractWebService(ベースクラス)を継承していることをチェックしているので、
少なくとも「関係ないクラスを誤って実行してしまう」状態にはならないようにしています。
リクエストの扱い:GETはクエリ、POSTはJSON
フレームワークは、HTTPメソッドに応じてパラメーターを異なる方法で処理します。
if ("GET".equals(reqMethod)) {
final String query = exchange.getRequestURI().getQuery();
io.putAllByUrlParam(query);
} else if ("POST".equals(reqMethod)) {
final String body = ServerUtil.getRequestBody(exchange);
io.putAllByJson(body);
}
ブラウザ側(JavaScript)では、HttpUtil.callJsonService('/services/...', req) で POST、HttpUtil.movePage('/services/...', req) で GET という形で使い分けます。
※ GET の場合は、req はURLパラメータ(クエリ文字列)として付与される想定です(サーバー側は exchange.getRequestURI().getQuery() を読むため)。
この方式のメリット
- ルーティング定義が不要 → 設定/アノテーションが減り、作る側も読む側も迷いにくい
- URLを見るだけで実行クラスが分かる → 調査・保守がしやすい
- 実行フローが単純 → 初心者が追いやすい
- 規約が明確 → AI(GitHub Copilot など)も生成時に迷いにくい
注意点(今後の改善候補)
- リフレクションを使うため、アクセス頻度が高い箇所を性能チューニングしたい場合は工夫が必要(例:クラス解決やコンストラクタ取得のキャッシュなど)
- URLで到達できる範囲=公開APIになりやすいので、パッケージ設計と公開/非公開の線引きが重要
おわりに
今回は「URLがそのまま実行クラス名」というルールの中身と、実装(設定→コンテキスト登録→クラス解決→実行)までを書きました。
次回は「JSON限定」の設計について書く予定です。
関連記事リンク
他の記事もぜひご覧ください!
- 001 SIer向けフレームワークを自作した動機
- 002 直結型URLマッピング(本記事)
- 003 JSON限定
- 004 静的HTML
SIcoreフレームワーク リンク
実装コードと資料はすべてこちらで公開しています。
- GitHub: https://github.com/sugaiketadao/sicore-ja
- サンプル画面の確認方法: https://github.com/sugaiketadao/sicore-ja#%EF%B8%8F-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E7%94%BB%E9%9D%A2%E3%81%AE%E7%A2%BA%E8%AA%8D%E6%96%B9%E6%B3%95---vs-code
- AI開発の始め方: https://github.com/sugaiketadao/sicore-ja#-ai%E9%96%8B%E7%99%BA%E3%81%AE%E5%A7%8B%E3%82%81%E6%96%B9
読んでいただきありがとうございました!
❤いいね!をしていただけると励みになります。