Introduction
In the previous article, I wrote about the background of creating the SIcore framework and how beginner-friendly design might also be AI-friendly (for tools like GitHub Copilot).
This time, I'll dive into one specific design choice: "URL maps directly to execution class name" — covering the concept through to the code-level implementation.
What This Article Covers
- The URL mapping specification (rules)
- What can be customized via configuration
- Where and how the implementation works (Java code)
- Benefits and considerations of this approach
Specification: URL Maps Directly to Class Name
In SIcore, we map URLs to execution classes (web services) through simple string replacement (plus root package prepending).
URL: http://localhost:8000/services/exmodule/ExampleListSearch
↓ Mapping
Execution class: com.example.app.service.exmodule.ExampleListSearch
There are three key points:
-
/services/in the URL is the web service context path (configurable). - The path after
/services/is converted to package and class names by replacing/with. - A root package for web services (configurable) is prepended to form the fully qualified class name.
This eliminates the need for "routing configuration files," "annotation scanning," or "controller registration" — the URL alone determines the execution class.
Configuration: Context and Root Package
The configuration is in config/web.properties:
# JSON service context path
json.service.context=services
# JSON service root package
json.service.package=com.example.app.service
By changing these settings, you can customize:
- Change the
/servicespart of the URL to a different context path - Change the base root package where execution classes are located
Implementation Overview: Where URLs Are Received
The web server uses JDK's standard com.sun.net.httpserver.HttpServer.
In src/com/onepg/web/StandaloneServer.java, we load the configuration and register JsonServiceHandler for the /services context.
// JSON service handler
final String jsonServiceContext = propMap.getString("json.service.context");
final String jsonServicePackage = propMap.getString("json.service.package");
this.server.createContext("/" + jsonServiceContext,
new JsonServiceHandler(jsonServiceContext, jsonServicePackage));
Implementation Details: URL → Class Name Generation
The method that builds the execution class name from the URL is in src/com/onepg/web/JsonServiceHandler.java:
private String buildClsNameByReq(final String reqPath) {
return this.svcClsPackage + "."
+ reqPath.replace("/" + this.contextPath + "/", "").replace("/", ".");
}
For example, when reqPath is /services/exmodule/ExampleListSearch:
-
replace("/services/", "")→exmodule/ExampleListSearch -
replace("/", ".")→exmodule.ExampleListSearch - Prepend
com.example.app.service.
This generates the fully qualified class name: com.example.app.service.exmodule.ExampleListSearch
Implementation Details: Execution via Reflection
Once the class name is determined, we instantiate and execute it via reflection:
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);
By checking inheritance of AbstractWebService (base class), we ensure that "unrelated classes cannot be accidentally executed."
Request Handling: GET Uses Query, POST Uses JSON
The framework processes parameters differently based on the HTTP method:
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);
}
On the browser side (JavaScript), we use HttpUtil.callJsonService('/services/...', req) for POST and HttpUtil.movePage('/services/...', req) for GET.
Note: For GET, req is appended as URL parameters (query string) because the server reads exchange.getRequestURI().getQuery().
Benefits of This Approach
- No routing configuration needed → Fewer settings/annotations, easier for both writers and readers
- URL alone reveals the execution class → Faster investigation and maintenance
- Simple execution flow → Easier for beginners to trace
- Clear conventions → Less confusion for AI (like GitHub Copilot) during code generation
Considerations (Future Improvements)
- Reflection usage means optimization work is needed for high-frequency access paths (e.g., caching class resolution and constructor retrieval)
- URL-accessible range ≈ public API, so careful package design and public/private boundaries are important
Conclusion
This article covered the internals of the "URL maps directly to class name" rule and its implementation (configuration → context registration → class resolution → execution).
Next time, I'll write about "JSON-centric design."
Links
- GitHub: https://github.com/sugaiketadao/sicore
- How to verify sample screens (VS Code): https://github.com/sugaiketadao/sicore#%EF%B8%8F-how-to-verify-sample-screens---vs-code
- Getting started with AI development: https://github.com/sugaiketadao/sicore#-getting-started-with-ai-development
Thank you for reading!
I'd appreciate it if you could give it a ❤️!
Top comments (0)