본문 바로가기

spring

MVC 프레임워크 구현 - (중)

  • Controller 인터페이스 작성

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

 

구체적인 Controller 클래스들을 구현하기에 앞서 모든 Controller를 같은 타입으로 관리하기 위한 인터페이스를 만들어야 한다. 클라이언트의 요청을 받은 DispatcherServlet은 HandlerMapping을 통해 Controller 객체를 검색하고, 검색된 Controller를 실행한다. 

 

이때 어떤 Controller 객체가 검색되더라도 같은 코드로 실행하려면 모든 Controller의 최상위 인터페이스가 필요하다.

package com.springbook.view.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

 

  • LogingController 구현

Controller 인터페이스를 구현한 LoginController 클래스를 만들고 다음과 같이 작성한다.

이때 DispatcherServlet 클래스에서 "/login.do"에 해당하는 소스를 복사하면 더욱 쉽게 구현할 수 있다.

package com.springbook.view.user;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.springbook.biz.user.UserVO;
import com.springbook.biz.user.impl.UserDAO;
import com.springbook.viw.controller.Controller;


public class LoginController implements Controller {
    @Override
    pubic String handleRequest(HttpServletRequest request,
                               HttpServletResponse response) {
        
        System.out.println("로그인 처리");
        
        // 1. 사용자 입력 정보 추출
        String id = request.getParameter("id");
        String password = request.getParameter("password");
        
        // 2. DB 연동 처리
        UserVO vo = new UserVO();
        vo.setId(id);
        vo.setPassword(password);
        
        UserDAO userDAO = new UserDAO();
        UserVO user = userDAO.getUser(vo);
        
        // 3. 화면 네비게이션
        if (user != null) {
            return "getBoardList.do";
        } else {
            return "login";
        }     
    }                     
}

 로그인 처리 소스는 DispatcherServlet의 로그인 처리 기능과 같다. 다만, Controller 인터페이스의 handleRequet() 메소드를 재정의했으므로 로그인 처리 기능의 마지막은 이동할 화면을 리다이렉트하지 않고 리턴하는 것으로 처리한다.

 

그런데 로그인에 실패했을 때 이동할 화면 정보가 login.jsp가 아니라 그냥 login이다. 이는 나중에 추가할 ViewResolver 클래스를 만들어야 정확하게 이해할 수 있다. 여기서는 단지 handleRequest() 메소드가 확장자 없는 문자열을 리턴하면, 자동으로 '.jsp' 확장자가 붙어서 처리도니다는 것으로만 이해하고 넘어간다.

 

 

  • HandlerMapping 클래스 작성

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

 

package com.springbook.view.controller;

import java.util.HashMap;
improt java.util.Map;

improt com.springbook.view.user.LoginController;

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

HandlerMapping은 Map 타입의 컬렌션을 멤버변수로 가지고 있으면서 게시판 프로그램에 필요한 모든 Controller 객체들을 등록하고 관리한다.

 

getController() 메소드는 매개변수로 받은 path에 해당하는 Controller 객체를 HashMap 컬렉션으로 검색하려 리턴한다. 지금은 LoginController 객체 하나만 등록되어 있지만, 앞으로 계속 Controller 객체들이 추가될 것이다. 따라서 HashMap에 등록된 정보를 보면 Controller 객체가 어떤 ".do" 요청과 매핑되어 있는지 확인할 수 있다.

 

 

  • ViewResolver 클래스 작성

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

package com.springbook.view.controller;

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;
    }
}

ViewResolver는 setPrefix()와 setSuffix() 메소드로 접두사(prefix)와 접미사(suffix)를 초기화한다. 그리고 getView() 메소드가 호출될 때 viewName 앞에 prefix를 겷바하고 viewName 뒤에 suffix를 결합하여 리턴한다.

 

 

  • DispatcherServlet 수정

DispatcherServlet은 Front Controller 기능의 클래스로서 Controller 구성 요소 중 가장 중요한 역할을 수행한다. 지금부터 DispatcherServlet 클래스를 수정해야 하는데, 수정하기 전에 바탕화면이나 다른 고셍 복사본을 만들어놓아야 한다. 그래야 나중에 구체적인 Controller 클래스 구현에서 소스를 재사용할 수 있다.

 

package com.springbook.view.controller;

import javax.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DispathcerServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private handlerMapping handlerMapping;
    private ViewResolver viewResolver;
    
    public void init() throws ServletExcetpion {
        handlerMapping = new HandlerMapping();
        viewResolver = new Resolver();
        viewResolver.setPrefix("./");
        viewResolver.setSuffix(".jsp");
    }
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
                          throws ServletException, IOExcetpion {
        process(request, response);
    }
   
    protected void doPost(HttpServletrequest, HttpServletResponse response) 
                           throws ServletException, IOException {
        request,setCharacterEncoding("EUC-KR");
        process(request, response);
    }
   
    private void process(HttpServletRequest request, HttpServletResponse response)
                           throws IOException {
        // 1. 클라이언트의 요청 path 정보를 추출한다.
        String uri = reqeust.getRequestURI();
        String path = uri.substring(rui.lastIndexOf("/"));
       
        // 2. HandlerMapping을 통해 path에 해당하는 Controller를 검색한다.
        Controller ctrl = hanlderMapping.getController(path);
       
        // 3. 검색된 Controller를 실행한다.
        String viewName = ctrl.handlerRequest(request, response);
       
        // 4. ViewResolver를 통해 viewName에 해당하는 화면을 검색한다.
        String view = null;
        if (!viewName.contains(".do")) {
            view = viewResolver.getView(viewName);
        } else {
            view = viewName;
        }
       
        // 5. 검색된 화면으로 이동한다.
        response.sendRedirect(view);
    } 
}

수정된 DispatcherServlet 클래스에는 inti() 메소드가 재정의되어 있다. 서블릿의 inti() 메소드는 서블릿 객체가 생성된 후에 멤버변수를 초기화하기 위해 자동으로 실행된다. 따라서 init() 메소드에서  DispatcherServlet이 사용할 HandlerMapping와 ViewResolver 객체를 초기화한다. 그리고 DispatcherServlet은 이렇게 생성된 HandlerMapping과 ViewResolver를 이용하여 사용자의 요청을 처리한다.

 

가장 중요한 process() 메소드를 보면 클라이언트의 요청 path 정보를 추출하는 코드를 제외하고 코드 대부분이 수정되었다. 먼저 클라이언트의 요청 path에 해당하는 Controller를 검색하기 위해 HandlerMapping 객체의 getController() 메소드를 호출한다. 그러고 나서 검색된 Controller의 handlerRequest() 메소드를 호출한다. 그러고 나서 검색된 Controller의 handleRequest() 메소드를 호출하여 요청에 해당하는 로직을 처리하고 나면 이동할 화면 정보가 리턴된다. 마지막으로 Controller가 리턴한 view 이름을 이용하여 실행될 view를 찾아 해당 화면을 이동한다.

 

다음은 지금까지 개발한 로그인 기능이 동작하는 과정을 그림으로 표현한 것이다.

1. 클라이언트가 로그인 버튼을 클릭하여 "/login.do" 요청을 전송하면 DispatcherServlet이 요청을 받는다.

2. DispatcherServlet은 HandlerMapping 객체를 통해 로그인 요청을 처리할 LoginController를 검색하고,

3. 검색된 LoginController의 handelRequest() 메소드를 호출하면 로그인 로직이 처리된다.

4. 로그인 처리 후에 이동할 화면 정보가 리턴되면

5. DispatcherServlet은 ViewResolver를 통해 접두사와 접미사가 부튼 JSP 파일의 이름과 경로를 리턴받는다.

6. 그리고 최종적으로 JSP를 실행하고 실행결과가 브라우저에 응답된다.

'spring' 카테고리의 다른 글

Spring MVC 구조 - (하)  (0) 2020.10.20
IOC(Inversion of Control)컨테이너 - 서블릿 컨테이너  (0) 2020.10.03
@ModelAttribute와 커맨드 객체(2)  (0) 2020.09.06
@ModelAttribute와 커맨드 객체(1)  (0) 2020.09.06
web.xml - Element  (0) 2020.03.15