Spring MVCの基本コンセプト
Java Web開発において、Spring MVCは柔軟なアーキテクチャと強力な機能により広く採用されています。しかし多くの開発者は日常的な使用に留まり、内部の仕組みや実装ロジックを深く理解していません。ここでは、Spring MVCの核となる概念から出発し、簡易なSpring MVCの実装を通じてその動作原理を解説します。
1. MVCパターン
MVC(Model-View-Controller)は、データ処理・画面表示・ユーザーインタラクションロジックを分離する設計パターンです。
- Model:データとビジネスロジックを保持するJavaBean。
- View:JSPやHTML、Vueコンポーネントなどのデータ可視化。
- Controller:リクエストの受け手として、ModelとViewを調整し、パラメータ検証やビジネスロジックの呼び出しを行う。
2. Spring MVCの主要コンポーネント
- DispatcherServlet:中央コントローラーとしてリクエストを処理。
- HandlerMapping:URLに基づいてHandlerをマッピング。
- HandlerAdapter:Handlerの実行を適応。
- Handler:具体的な処理ロジック。
- ViewResolver:論理ビュー名から実際のビューを解決。
3. Spring MVCの処理フロー
- ユーザーがHTTPリクエストをDispatcherServletに送信。
- HandlerMappingがURLに基づいてHandlerを検索。
- HandlerAdapterがHandlerを適応。
- Handlerが処理を実行しModelAndViewを返す。
- ViewResolverがビューを解決。
- ビューがレンダリングされ、結果がユーザーに返される。
簡易Spring MVCの実装
1. カスタムアノテーションの定義
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
}
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface UrlMapping {
String path() default "";
}
2. 核心クラスの実装
import java.lang.reflect.Method;
import java.util.*;
import java.io.File;
public class MvcFramework {
private static Map> routeMap = new HashMap<>();
private static Map instanceMap = new HashMap<>();
}
private static List<String> findClassFiles(String baseDir) {
File root = new File(baseDir);
List<String> files = new ArrayList<>();
if (root.exists()) {
LinkedList<File> queue = new LinkedList<>();
queue.addAll(Arrays.asList(root.listFiles()));
while (!queue.isEmpty()) {
File current = queue.poll();
if (current.isDirectory()) {
queue.addAll(Arrays.asList(current.listFiles()));
} else if (current.getName().endsWith(".class")) {
files.add(current.getAbsolutePath());
}
}
}
return files;
}
public static void scan(String packagePath, String baseDir) {
List<String> classFiles = findClassFiles(baseDir);
for (String file : classFiles) {
try {
String relativePath = file.substring(baseDir.length() - 1);
String className = packagePath + "." + relativePath
.replace(File.separator, ".")
.replace(".class", "");
Class> clazz = ClassLoader.getSystemClassLoader().loadClass(className);
if (isComponent(clazz)) {
if (hasUrlMapping(clazz)) {
String classPath = getUrlMapping(clazz).path();
if (routeMap.containsKey(classPath)) {
throw new RuntimeException("Duplicate class path: " + classPath);
}
routeMap.put(classPath, new HashMap<>());
instanceMap.put(classPath, clazz.newInstance());
for (Method method : clazz.getDeclaredMethods()) {
if (hasUrlMapping(method)) {
String methodPath = getUrlMapping(method).path();
if (routeMap.get(classPath).containsKey(methodPath)) {
throw new RuntimeException("Duplicate method path: " + classPath + "/" + methodPath);
}
routeMap.get(classPath).put(methodPath, method);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. コントローラーの例
@Component
@UrlMapping(path = "/")
public class HomeHandler {
@UrlMapping(path = "/")
public void showHome() {
System.out.println("HomeHandler -> showHome");
}
}
@Component
@UrlMapping(path = "/sample")
public class SampleHandler {
@UrlMapping(path = "/")
public String display() {
System.out.println("SampleHandler -> display");
return "";
}
@UrlMapping(path = "/detail")
public String showDetail() {
System.out.println("SampleHandler -> showDetail");
return "";
}
}
4. 実行結果
HomeHandler -> showHome
SampleHandler -> showDetail
SampleHandler -> display
SampleHandler -> showDetail