[스프링부트] 11. MVC 프레임워크

백하림's avatar
Mar 19, 2025
[스프링부트] 11. MVC 프레임워크

MVC 프레임워크

DispatcherServlet 코드

💡

1. DispatcherServlet (요청을 처리하는 컨트롤러)

  • .do로 끝나는 모든 요청을 받음.
  • init()에서 컴포넌트 스캔을 실행해 컨트롤러 객체들을 모아둠.
  • doGet()에서 path 파라미터를 받아 적절한 컨트롤러 메서드를 실행함.
package org.example.demo10; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.example.demo10.core.ComponentScan; import org.example.demo10.core.RequestMapping; import org.example.demo10.core.ViewResolver; import java.io.IOException; import java.lang.reflect.Method; import java.util.Set; @WebServlet("*.do") public class DispatcherServlet extends HttpServlet { private Set<Object> controllers; @Override public void init(ServletConfig config) throws ServletException { // 1. 컴포넌트 스캔 ComponentScan componentScan = new ComponentScan(config.getServletContext()); controllers = componentScan.scanClass("org.example.demo10.controller"); //System.out.println(controllers.size()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // localhost:8080/user.do?path=join String path = req.getParameter("path"); // 2. 라우팅 String templatePath = route(path); // 3. 이동 if (templatePath == null) { resp.setStatus(404); resp.getWriter().println("<h1>404 Not Found</h1>"); } else { req.getRequestDispatcher(ViewResolver.viewName(templatePath)).forward(req, resp); } } private String route(String path) { for (Object instance : controllers) { Method[] methods = instance.getClass().getMethods(); for (Method method : methods) { RequestMapping rm = method.getAnnotation(RequestMapping.class); if (rm == null) continue; // 다음 for문으로 바로 넘어감 if (rm.value().equals(path)) { try { return (String) method.invoke(instance); } catch (Exception e) { throw new RuntimeException(e); } } } } return null; } }

ViewResolver 코드

💡

3. ViewResolver (뷰의 위치를 반환)

  • viewName("join")을 호출하면 "WEB-INF/views/join.jsp"를 반환해줌.
  • JSP 파일이 보안 폴더(WEB-INF) 안에 있어서 직접 접근이 불가능함 → DispatcherServlet이 찾아서 포워딩함.
package org.example.demo10.core; public class ViewResolver { private static final String prefix = "/WEB-INF/views/"; private static final String suffix = ".jsp"; public static String viewName(String filename) { return prefix + filename + suffix; } }

ComponentScan 코드

💡

2. ComponentScan (컨트롤러를 찾는 역할)

  • org.example.demo10.controller 패키지에서 @Controller가 붙은 클래스를 찾아 객체로 생성.
  • scanClass()에서 Class.forName()으로 클래스를 로딩하고, newInstance()로 객체를 만듦.
package org.example.demo10.core; import jakarta.servlet.ServletContext; import java.io.File; import java.util.HashSet; import java.util.Set; public class ComponentScan { private final ServletContext servletContext; public ComponentScan(ServletContext servletContext) { this.servletContext = servletContext; } // 클래스를 스캔하는 메소드 public Set<Object> scanClass(String pkg) { Set<Object> instances = new HashSet<>(); try { // 톰캣의 webapps 폴더 내 WEB-INF/classes 경로 가져오기 String classPath = servletContext.getRealPath("/WEB-INF/classes/"); // C:\Program Files\Apache Software Foundation\Tomcat 11.0\webapps\ROOT\WEB-INF\classes\ File slashDir = new File(classPath); File dotToSlashDir = new File(slashDir, pkg.replace(".", File.separator)); for (File file : dotToSlashDir.listFiles()) { // System.out.println(file.getName()); String className = pkg + "." + file.getName().replace(".class", ""); // System.out.println(className); try { Class cls = Class.forName(className); if (cls.isAnnotationPresent(Controller.class)) { // System.out.println("Controller 어노테이션"); Object instance = cls.getDeclaredConstructor().newInstance(); instances.add(instance); } } catch (Exception e) { throw new RuntimeException(e); } } return instances; } catch (Exception e) { throw new RuntimeException(e); } } }

어노테이션 코드

package org.example.demo10.core; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Controller { }
package org.example.demo10.core; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { String value(); }

UserController, BoardController 코드

💡

4. UserController (사용자 관련 요청 처리)

  • @Controller로 등록됨.
  • @RequestMapping("join")path=join 요청이 들어오면 "join"을 반환.
  • DispatcherServletjoin.jsp로 포워딩함.
package org.example.demo10.controller; import org.example.demo10.core.Controller; import org.example.demo10.core.RequestMapping; @Controller public class UserController { @RequestMapping("join") public String join() { System.out.println("UserController join"); return "join"; } @RequestMapping("login") public String login() { System.out.println("UserController login"); return "login"; } }
package org.example.demo10.controller; import org.example.demo10.core.Controller; @Controller public class BoardController { }

jsp파일

join

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>join page</h1> </body> </html>

login

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>login page</h1> </body> </html>
💡

즉, 이 코드의 핵심은?

  1. DispatcherServlet이 모든 요청을 가로채서 적절한 컨트롤러를 실행함.
  1. ComponentScan이 컨트롤러들을 찾아 객체로 만듦.
  1. ViewResolver가 JSP 파일의 위치를 정해줌.
  1. UserController가 특정 요청을 처리하고, JSP로 데이터를 넘겨 화면을 보여줌.
Share article

harimmon