스프링 MVC - Dispatcher Servlet을 직접 구현해보자.

ehdrms2034·2020년 8월 27일
0

본 글은 스프링 MVC에 대해 지식을 정리하고 나중에 헷갈릴 때 다시 보기 위한 글입니다 👀

본 게시글은 Spring MVC Quick Start를 참조하여 정리한 글입니다. 📖 👀

본 게시글은 Spring MVC Documentation를 참조하여 정리한 글입니다 📚 👀

Servlet의 등장

기존의 모델1 아키텍쳐 구조가 클라이언트와 JSP + JavaBeans를 이용한 아키텍쳐 구조였다면, 이를 개선한 모델2가 등장한다. 모델1은 시스템의 규모가 크고 기능이 복잡한 시스템 개발에 어려움을 보였는데 이유는 자바 로직과 화면 디자인이 통합되어 유지보수가 어려웠기 때문이다.

모델 1은 즉 뷰와 컨트롤러의 기능이 합쳐져있는 상태였기 때문에 유지보수에 부적합했다.

이를 해결하기 위해 나온 모델이 MVC 모델이다.

모델2 등장에서 가장 중요한 특징은 Controller의 등장이다.
따라서 사용자의 요청을 받고 요청에 맞는 형태의 뷰를 띄워주는 것을 Controller(Servlet)이 담당하게 되었다.

간단한 DispatcherServlet의 구현

Servlet 클래스는 다음과 같이 정의할 수 있따.

public class DispatcherServlet extends HttpServlet{

	@Override
	protected void doGet(HttpServletRequest req,HttpServletResponse res){
    		...
    	}

	@Override
	protected void doPost(HttpServletRequest req,HttpServletResponse res){
		...
	}

}

위의 서블릿의 사용자의 요청을 받아서 응답을 처리해줄 수 있는 자바 객체가 Servlet이라면
사용자의 Http 요청을 제일 먼저 받아서 어떤 처리를 할지 판단내릴수 있는것이 DispatcherServlet이다.

그렇다면 Spring에서는 어떤 구조로 돌아가고 있는지 보도록 하겠다.

각각의 역할은 다음과 같다.

|클래스| 기능|
|----|----|
|DispatcherServlet|유일한 서블릿 클래스로서 모든 클라이언트의 요청을 가장 먼저 처리하는 Front Controller|
|HandlerMapping| 클라이언트의 요청을 처리할 Controller를 매핑|
|Controller| 실질적인 클라이언트의 요청 처리|
|ViewResolver| Controller가 리턴한 View 이름으로 실행될 경로 완성|

Controller Interface

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

Controller를 구성하는 요소 중에서 DispatcherServlet은 클라이언트의 요청을 가장 먼저 받아들이는 Front Controller다. 하지만 클라이언트의 요청을 처리하기 위해서 DispatcherServlet은 실질적으로 요청을 담당할 Controller의 Mapping을 담당하고 실질적인 처리는 Controller 가 담당한다.

실질적으로 Controller는 ModelAndView를 가지고 있지만 DispatcherServlet의 직접구현을 위해 위와 같이 처리했다.

Controller 의 작성

예를들어 사용자의 요청을 받아 로그인 처리를 하는 컨트롤러를 만들어 만든다 해보자.

public class LoginController implements Controller{
    @Override
    public String handleRequest(HttpServletRequest request,HttpServletResponse response){
        System.out,println("로그인 처리");
        
        String id = request.getParameter("id");
        String password = request.getParameter("password");
    
        UserVO vo = new UserVO();
        vo.setId(id);
        vo.setPassword(password); //JavaBeans 패턴
        
        UserDao userDao = new UserDao();
        UserVo user = userDao.getUser(vo);
                
        if(user!=null){
        	return "getBoardList.do";
        } else {
        	return "login"
        };
    
    }
}

요청이 들어오고 Parameter를 분석해 id 비밀번호가 올바르다면 게시글 화면을 출력하고, 그게 아니라면 다시 로그인 화면으로 돌아가라는 간단한 컨트롤러이다.

HandlerMapping 클래스 작성

Handler Mapping은 모든 Controller 객체들을 저장하고 있다가, 클라이언트의 요청이 들어오면 요청을 처리할 특정 Controller 검색하는 기능을 제공한다. HandlerMapping 객체는 DispatcherServlet이 사용하는 객체이다. 따라서 DispatcherServlet이 생성되고 init() 메소드가 호출될 때 단 한 번 생성된다.

  • HandlerMapping.java
public class HandlerMapping{
    Private Map<String,Controller> mappings;
    
    public HandlerMapping() {
        mappings = new HashMap<String, Controller>();
        mappings.put("/login.do", new LoginContoller());
    }
    
    public Controller getController(String path){
        return mappings.get(path);
    }

즉 HandlerMappin은 내부에 Map 타입의 컬렉션을 멤버변수로 가지고 있으면서 사용자의 URI(path)를 판별하여 사용자의 요청을 올바르게 처리할 수 있는 Controller를 반환해준다.

ViewResolver 클래스 작성

ViewResolver 클래스는 Contoller가 리턴한 View 이름에 접두사(prefix)와 접미사(suffix)를 결합하여 최종으로 실행될 View 경로와 파일명을 완성한다. ViewResolver 또한 HandlerMapping과 마찬가지로 DispatcherServlet의 init()메소드가 호출될 때 생성된다.

  • ViewResolver.java
public class ViewResolver{
    public String prefix;
    public String suffix;
    
    public void setPrefix(String prefix){
        this.prefix = prefix;
    }
    
    public void setSuffix(String suffix){
        this.suffix = suffix;
    }
    
    public String getView(String viewName){
        return prefix + viewName + suffix;
    }

Dispatcher Servlet 클래스 완성

DispatcherServlet은 FrontContoller(최전방의) 기능의 클래스로서 Contoller 구성요소 중 어떻게 보면 가장 중요한 역할을 한다.

위에 작성한 미완성된 DispatcherServlet 코드를 들고와서 마무리 지어보자.

  • DispatcherServlet.java
public class DispatcherServlet extends HttpServlet{
    private HandlerMapping handlerMapping;
    private ViewResolver viewResolver;
    
    public void init() throws ServletException{
        HandlerMapping = new HadlerMapping();
        viewResolver = new ViewResolver();
        viewResolver.setPrefix("./");
        viewResolver.setSuffix(".jsp");
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOExcpetion {
        process(request, response);
    }
    
    @Override
    protected void doPost(HttpServletRequest request,HttpServletResponse res){
        process(request, response);
    }
   
    protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String uri = request.getRequestURI():
        String path = uri.substring(uri.lastIndexOf("/"));
    
        Controller ctrl = handlerMapping.getController(path);
    
        String viewName = ctrl.handleRequest(request,response);
    
        String view = null;
        if(!viewName.contains(".do")){
            view = viewResolver.getView(viewName);
        } else {
            view = viewName;
        }
    }
    
}

위와 받은 코드는 다음과 같이 동작된다.

  1. 사용자의 요청을 최전선에서 DispatcherServlet이 받는다.
  2. URI를 Parsing해서 Path를 HandlerMapping에게 넘긴다.
  3. HandlerMapping은 사용자 요청을 처리할 수 있는 올바른 Controller를 리턴한다.
  4. Controller는 내부의 HandlerRequest를 통해서 요청을 처리한다.
  5. 요청을 처리한 뒤 이동할 화면을 ViewResolver를 통해 JSP(혹은 View)파일의 이름과 경로를 리턴받는다.
  6. 사용자에게 올바른 뷰를 보여준다.

사실 우리는 위처럼 DispatcherServlet을 직접 구현할 필욘 없다.

왜냐하면 스프링 MVC 에서 다 지원을 해주기 때문이다.

이건 2부로 나눠서 글을 적어야 될 것 같다.

profile
수동적인 과신과 행운이 아닌, 능동적인 노력과 치열함

0개의 댓글