서블릿 (Servlet)

  • 동적 웹 페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술
  • 웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 지원
  • HTTP 요청을 통해 매핑된 URL이 호출되면 service() 메서드를 실행
  • HTTP 요청, 응답 메시지를 편리하게 사용할 수 있도록 도와주는 HttpServletRequest, HttpServletResponse 객체

 

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
	@Override
    protected void service(HttpServletRequest request, HttpServletResponse
  response) throws ServletException, IOException {
  	...
   
  }
}

 

HTTP 통신 과정 변화

  • 서버 TCP/IP 연결 대기, 소켓 연결
  • HTTP 요청 메시지 파싱 (헤더부, 바디부 ..)Request 객체
  • 비즈니스 로직
  • HTTP 응답 메시지 생성Response 객체
  • TCP/IP 응답 전달, 소켓 종료

 

MVC 패턴

  • 비즈니스 로직과 뷰 렌더링을 한번에 처리 -> 너무 많은 역할
  • 비즈니스 로직을 수정하는 일과 UI를 수정하는 일은 각각 다르게 발생할 경우가 많고 대부분 서로에게 영향을 주지 않음
  • MVC 패턴Controller와 View 영역으로 서로 역할을 나눈 것
  • Controller : HTTP 요청을 받아서 파라미터 검증, 비즈니스 로직(Service 계층), View에 전달할 데이터를 Model에 저장
  • Model : View에 전달할 데이터를 저장
  • View : Model에 담겨있는 데이터를 사용해서 화면을 렌더링하는데 집중

 

Servlet + 기본 MVC 패턴의 문제점

  • View로 이동하는 dispatcher.forward(request, response) 코드 중복 호출
  • 모든 컨트롤러에 Servlet 선언 (종속성)
  • 공통 처리가 어려움 -> 컨트롤러 호출 전에 공통 기능을 처리해줄 무언가 필요 (프론트 컨트롤러)

 

프론트 컨트롤러

  • 스프링 MVC의 DispatcherServlet 역할
  • 하나의 서블릿으로 사용 (공통 처리)
  • URL 요청을 전부 이곳에서 받아 요청에 맞는 컨트롤러를 호출 -> 서블릿 종속성 제거
  • 각각의 컨트롤러에서 View 네임을 반환하면 프론트 컨트롤러에서 경로(viewPath)를 생성 -> 코드 중복 제거
  • 프론트 컨트롤러에서만 서블릿 선언을 해서 나머지 컨트롤러에서 서블릿 종속성 제거
  • 모든 컨트롤러에서 Model을 반환하지 않도록 프론트 컨트롤러에서 따로 Model 객체를 생성해서 전달
  • 여러 컨트롤러를 처리할 수 있도록 핸들러 어댑터 생성

 

@WebServlet(name = "FrontControllerSerlvetV5", urlPatterns = "/front-controller/v/*")
public class FrontControllerSerlvetV5 extends HttpServlet {

    private final Map<String, Object> handlerMappingMap = new HashMap<>();
    private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();

    // 핸들러 매핑 등록, 핸들러 어댑터 등록
    public FrontControllerSerlvetV5() {
        initHandlerMappingMap();
        initHandlerAdapters();
    }


    // 핸들러(컨트롤러) 초기화
    private void initHandlerMappingMap() {
    	//v3 버전 컨트롤러
        handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
        handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
        handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());

		//v4 버전 컨트롤러
        handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
        handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
        handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
    }

    // 어댑터 초기화
    private void initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
        handlerAdapters.add(new ControllerV4HandlerAdapter());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 핸들러 조회
        Object handler = getHandler(request);

        // 요청 URL에 맞는 핸들러 객체가 없으면 종료
        if (handler == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        // 3. 핸들러에 맞는 핸들러 어댑터 조회
        MyHandlerAdapter adapter = getHandlerAdapter(handler);

        // 5. 핸들러 어댑터에 전달
        // 어댑터는 request 파라미터를 Map 객체에 담고 model 객체를 따로 생성해서 전달
        // 핸들러는 Servlet 종속성이 제거되고 ViewName만 반환하면 됨
        ModelView mv = adapter.handle(request, response, handler);

        // 6. ViewName(논리 이름)으로 View Resolver(풀 경로 생성) 실행
        String viewName = mv.getViewName();
        MyView view = viewResolver(viewName);

        // 7. View 렌더링
        view.render(mv.getModel(), request, response);
    }

    // 2. 요청 URL에 맞는 핸들러 객체를 handlerMappingMap에서 조회, 핸들러 객체 반환
    private Object getHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return handlerMappingMap.get(requestURI);
    }

    // 4. 해당 핸들러를 지원하는 핸들러 어댑터가 있는지 확인
    private MyHandlerAdapter getHandlerAdapter(Object handler) {
        for (MyHandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        throw new IllegalArgumentException("Error");
    }

    //ViewPass생성
    private MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }
}

 

[참고]인프런 김영한님 강의를 공부한 내용입니다.

+ Recent posts