Spring MVCの深く理解と簡易実装: 仕組みからコードまで

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の処理フロー

  1. ユーザーがHTTPリクエストをDispatcherServletに送信。
  2. HandlerMappingがURLに基づいてHandlerを検索。
  3. HandlerAdapterがHandlerを適応。
  4. Handlerが処理を実行しModelAndViewを返す。
  5. ViewResolverがビューを解決。
  6. ビューがレンダリングされ、結果がユーザーに返される。

簡易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

タグ: spring-mvc java-annotations dependency-injection request-handling custom-framework

7月31日 02:13 投稿