1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

直結型URLマッピング - いまさらながら SIer向けフレームワークを自作した(10年ぶり3回目) #002

Last updated at Posted at 2026-01-27

はじめに

前回記事では、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つです。

  1. URL の /services/Webサービスのコンテキストパス(設定で変更可能)。
  2. /services/ 以降のパスを . 区切りに文字列置換して、パッケージ・クラス名に使用する。
  3. パッケージの先頭に 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 で設定を読み込み、コンテキスト /servicesJsonServiceHandler を登録しています。

// 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)POSTHttpUtil.movePage('/services/...', req)GET という形で使い分けます。
GET の場合は、req はURLパラメータ(クエリ文字列)として付与される想定です(サーバー側は exchange.getRequestURI().getQuery() を読むため)。

この方式のメリット

  • ルーティング定義が不要 → 設定/アノテーションが減り、作る側も読む側も迷いにくい
  • URLを見るだけで実行クラスが分かる → 調査・保守がしやすい
  • 実行フローが単純 → 初心者が追いやすい
  • 規約が明確 → AI(GitHub Copilot など)も生成時に迷いにくい

注意点(今後の改善候補)

  • リフレクションを使うため、アクセス頻度が高い箇所を性能チューニングしたい場合は工夫が必要(例:クラス解決やコンストラクタ取得のキャッシュなど)
  • URLで到達できる範囲=公開APIになりやすいので、パッケージ設計と公開/非公開の線引きが重要

おわりに

今回は「URLがそのまま実行クラス名」というルールの中身と、実装(設定→コンテキスト登録→クラス解決→実行)までを書きました。

次回は「JSON限定」の設計について書く予定です。

関連記事リンク

他の記事もぜひご覧ください!

SIcoreフレームワーク リンク

実装コードと資料はすべてこちらで公開しています。


読んでいただきありがとうございました!
❤いいね!をしていただけると励みになります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?