🐢 꼬부기 LV.1 | 개념•기초/💧물대포(핵심개념)

스프링의 구조를 이해해 보기

서화 2026. 1. 16. 17:50

📌스프링의 구조를 이해하기 전 먼저 프론트 컨트롤러 요청 처리 흐름을 복습하고자한다

클라이언트(브라우저,사용자,뷰) 에서 loginPage.do로 GET 요청을 보내면

해당 요청은 가장 먼저 프론트 컨트롤러(Front Controller) 로 전달된다.
이 프론트 컨트롤러는 전체 구조에서 유일한 서블릿으로, 요청을 받아서 처리하는 역할을 담당한다.

비동기 요청을 처리하는 클래스도 서블릿이지만, 스프링 구조에서 실제로는 POJO로 변경된다.
즉 구조가 경량화된다

✔️ 프론트 컨트롤러 요청 처리 흐름

1. 클라이언트 요청 분석 및 커맨드 생성

프론트 컨트롤러는 가장 먼저 클라이언트로부터 전달된 요청 URL을 분석한다.

이때 요청 URI에서 실제 동작을 구분하기 위한 값(커맨드) 을 추출한다.
예를 들어 /LoginPage.do 와 같은 요청에서 LoginPage 라는 커맨드를 추출하는 방식이다.

2. 커맨드 기반 분기 처리

생성된 커맨드를 기준으로 프론트 컨트롤러는 내부적으로 분기 처리를 수행한다.
이 구조를 흔히 자판기 방식 처리라고 부른다.

요청마다 필요한 DTO를 생성해 사용하고, 처리가 끝나면 버리는 구조를 가진다.

3. ActionFactory와 싱글톤 유지

실제 컨트롤러 객체 생성은 ActionFactory 라는 팩토리 패턴을 통해 실행된다

ActionFactory는 내부에 Map<String, Action> 형태의 멤버 변수를 가지고있고 커맨드와 실행 객체를 연결해 관리한다.

따라서 어떤 프레임워크든 “멤버 변수로 Map을 가지고 요청을 관리한다면
팩토리 패턴일 확률이 높다”라고 이해하면 된다

4. 요청에 맞는 컨트롤러 반환 및 실행

프론트 컨트롤러는 ActionFactory에 커맨드를 전달하고, 이에 맞는 컨트롤러(Action) 를 반환한다.

반환된 컨트롤러는 로직을 수행하고,요청 처리 결과를 정리한다.

이 시점부터 실제 “일을 하는 주체”는 프론트 컨트롤러가 아니라 개별 컨트롤러(Action)가 된다.

5. ActionForward 생성 및 반환

컨트롤러의 처리 결과로 ActionForward 객체가 생성된다.

ActionForward에는 처리 결과를 어떤 페이지로, 어떤 방식으로 보여줄지에 대한 정보가 담긴다.

또한 ActionForward는 객체이기 때문에 구조적으로 무겁다.

스프링에서는 기본적으로 포워드 방식을 사용하고, 컨트롤러는 이동할 뷰 이름(String) 만 반환한다.
이 방식은 객체 생성을 줄여 전체 구조를 더욱 경량화한다.

6. 최종 결과를 클라이언트에게 응답

프론트 컨트롤러는 컨트롤러가 반환한 결과(ActionForward 또는 String)를 기준으로 요청한 정보를 클라이언트(브라우저, 사용자)에게 보여준다


✔️직접 구현으로 스프링 구조 이해하기

🛠️ DispatcherServlet 만들기

스프링에서는 FrontController가 DispatcherServlet으로 변경된다 따라서 이 서블릿의 역할은 FrontController과 똑같다

edit을 눌러서 DispatcherServlet을 *.do로 변경해준다

@WebServlet("*.do")

edit으로 변경한 *.do 요청이 @WebServlet 어노테이션으로 매칭된 것을 확인할 수 있다.
이를 통해 디스패처 서블릿 실행에 필요한 설정을 web.xml 없이, 어노테이션으로 처리할 수 있다.

<Bean> → @Component

<constr...>,<property...> →@Autowired

이렇게 어노테이션을 설정할수 있다

원래 서블릿 컨테이너에서는 동작을 위해 톰캣을 사용했는데 스프링은 내장 톰캣을 가지고 있다

🛠️ DispatcherServlet에 doAction 만들기

doAction 메서드를 만들고 위에 복습했던 프론트 컨트롤러 실행순서 1~5번까지를 코드로 작성한다

1. 클라이언트 요청 분석 및 커맨드 생성

private void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	String uri = request.getRequestURI(); // 클라언트가 요청한 url주소
	String command = uri.substring(uri.lastIndexOf("/")); // url에서 마지막/ 다음 문자열을 추출해서 커맨드 생성
	System.out.println(command); //생성한 커맨드 확인용 출력

2. 커맨드 기반 분기 처리

public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private HandlerMapping hm;// 커맨드와 컨트롤러를 매핑하여 요청에 맞는 컨트롤러를 반환하는 역할
    //디스패쳐에 사용하기 위해서는 멤버변수로 지정해주어야한다
Controller ctrl = hm.getController(command); //커맨드에 해당하는 컨트롤러 조회

기존에는 ActionFactory를 사용했지만 스프링에서는 HandlerMapping으로 변경되었다 따라서 변수명도 교체해줘야하며

핸들러매핑의 역할은 액션팩토리와 같다

3. HandlerMapping으로 싱글톤유지

public class HandlerMapping {
	private Map<String, Controller> mappings; //핸들러 매핑의 멤버변수로 맵을 지정
	
	public HandlerMapping() {//기본생성자를 이용하여 멤버변수 초기화
		mappings = new HashMap<String, Controller>();
		
		mappings.put("/login.do", new LoginController());// 이동할 페이지와 실행할 컨트롤러 지정
	}
	
	public Controller getController(String command) {
		return mappings.get(command);//요청에 맞는 커맨드 반환
	}
}

4.요청에 맞는 컨트롤러 반환 및 실행

기존에 사용한 ActionForward 가 Controller라는 이름의 인터페이스로 변경된다 스프링은 포워드 방식을 기본적으로 사용하고 있기때문에 포워드를 주고받는 객체형식이 아니라 문자열을 주고 받는 방식으로 변경된다

public interface Controller {
	String execute(HttpServletRequest request, HttpServletResponse response);
}

String excute로 변경 되면서 인자에 디스패쳐서블릿의 doAction 메서드의 인자를 가져와서 넣어준다

String viewName = ctrl.execute(request, response); 
//요청 처리 후 이동할 대상(view)을 문자열로 반환한다

 

로그인 기능을 실행하기 위한 컨트롤러 만들어서 컨트롤러 인터페이스 상속 받기

public class LoginController implements Controller {

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) {
		if(true) {//로그인 성공하면 메인페이지로 이동
			return "main.do";
		}
		return "login"; // 나중에 VR가 처리할 예정
        //실패시 로그인 페이지로 이동
	}

}

기존에서는 이동할 페이지에 login.jsp 이렇게 뒤에 .jsp를 붙였는데 뷰 리졸버라는것을 사용해 이 .jsp를 처리해줄것이기 때문에 .do는 붙이고 .jsp는 떼고 작성하였다

또한 이렇게 인터페이스를 만듬으로서 어디로 갈지만 반환하기 때문에 경량화 되었다

즉 디스패쳐 서블릿이 할일을 핸들러 매핑해주기 때문에 없으면 작동을 안하는데 현재 핸들러 매핑을 디스패쳐 서블릿에 만들지 않았다 따라서 핸들러 매핑에 의존성을 주입해줘야한다

5. 최종 결과를 클라이언트에게 응답

String view = viewName; // 이동할 뷰 이름
		if(!view.contains(".do")) { // .do가 아니라면
			view = vr.getView(viewName); // VR가 처리함
           // 반환된 뷰 이름을 ViewResolver를 통해 실제 JSP로 바꾼다
		}
		response.sendRedirect(view);//리다이렉트로 이동해라
	}

✔️디스패쳐 서블릿에 의존성 주입하기

디스패쳐 서블릿에서 핸들러 매핑과 뷰 리졸버를 사용하기 위해서는 먼저 멤버변수로 선언해줘야한다

public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private HandlerMapping hm;
	private ViewResolver vr;

의존성 주입에는 이전에 배운 생성자주입,세터주입,오토와이어드 사용 말고 한가지 방법이 더 있는데

바로 인잇 메서드 사용이다

디스패쳐 서블릿은 스프링에서만 사용하기 때문에 인잇 메서드를 사용하여 변수명을 지정해서 객체를 새로 만들어주면 의존성주입이 끝난다

 public void init() {
    	hm = new HandlerMapping();

이제 뷰 리졸버를 만들어야한다

뷰 리졸버는 디스패처 서블릿이 화면을 찾을 때 사용하는 도우미 역할을 한다.
prefix와 suffix를 통해 뷰 이름 앞뒤에 경로를 붙여 최종적으로 이동할 화면 경로를 만들어준다.

public class ViewResolver {
	private String prefix;// 경로의 앞을 담당 = 디스패쳐 서블릿이 처리하는 커맨드 = /login
	private String suffix;// 경로의 뒤를 담당 = 이동할 페이지 = login.jsp
    
	//세터 사용해서 의존성 주입하기
	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}
	public void setSuffix(String suffix) {
		this.suffix = suffix;
	}
	
	public String getView(String viewName) {
    // 뷰 이름(viewName)에 prefix와 suffix를 합쳐서
    // 실제 이동할 뷰 경로(JSP 경로 등)를 만든다
    return prefix + viewName + suffix;
}

	}
}

뷰 리졸버는 4가지 방법의 의존성 주입중에 세터주입을 사용했다 따라서 디스패쳐 서블릿에 인잇메서드에 뷰 리졸버 객체를 만들고 프리픽스와 서픽스를 지정한다

 public void init() {
    	hm = new HandlerMapping();
    	vr = new ViewResolver();
    	vr.setPrefix("./");
    	vr.setSuffix(".jsp");
    }

이렇게 뷰 리졸버를 사용함으로서 이제 .do가 붙은 요청은 디스패쳐 서블릿이처리하고 .do가 없는 페이지 이동이라면 뷰 리졸버가 처리한다

✔️스프링 구조의 핵심 네가지

스프링이후의 나온 프레임 워크들은 이 핵심네가지를 모두 사용한다 따라서 이 구조를 알고 있으면 좋다