스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작하기 때문에, 매우 우연하고 실용적이다. 과거에는

자바 언어에 애노테이션이 없기도 했고, 스프링도 처음부터 이런 유연한 컨트롤러를 제공한 것은 아니다.

 

@RequestMapping

스프링은 애노테이션을 활용한 매우 유연하고, 실용적인 컨트롤러를 만들었는데 이것이 바로

@RequestMapping 애노테이션을 사용하는 컨트롤러이다. 

 

 - RequestMappingHandlerMapping

 - RequestMappingHandlerAdapter

앞서 공부했듯이 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 위의 두가지이다.

@RequestMapping 의 앞글자를 따서 만든 이름인데, 이것이 바로 스프링에서 주로 사용하는

애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다.

 

package hello.servlet.web.springmvc.v1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process(){
        return new ModelAndView("new-form");
    }
}

@Controller

- 스프링이 자동으로 스프링 빈으로 등록한다.(내부에 @Component 애노테이션이 있어서 컴포넌트 스캔의 대상이 됨)

- 스프링  MVC에서 애노테이션 기반 컨트롤러로 인식한다.

 

@RequestMapping 

- 요청 정보를 매핑한다.

- 해당 URL이 호출되면 이 메서드가 호출된다.

- 애노테이션을 기반으로 동작하기때문에, 메서드의 이름은 임의로 지어도 된다.

 

ModelAndView

- 모델과 뷰 정보를 담아서 반환하면 된다.

 

RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 

클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.

@Component //컴포넌트 스캔을 통해 스프링 빈으로 등록
@RequestMapping
public class SpringMemberFormControllerV1 {

 	@RequestMapping("/springmvc/v1/members/new-form")
 	public ModelAndView process() {
 		return new ModelAndView("new-form");
        
 	}
}

이 방식도 동작하고

직접 빈으로 등록해도 동작한다.

package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

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

@Controller
public class SpringMemberSaveControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username,age);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject("member",member);
        return mv;
    }
}
package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;
import java.util.Map;

@Controller
public class SpringMemberListControllerV1 {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members")
    public ModelAndView process() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members",members);

        return mv;
    }
}

save, List 기능하는 클래스도 만들었다.

뷰 리졸버에 대해 자세히 알아보았다.

package hello.servlet.web.springmvc.old;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

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

@Component("/springmvc/old-controller")
public class OldController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return new ModelAndView("new-form");
    }
}

이전 코드의 반환을 null이 아닌 ModelAndView를 생성하여 논리 주소를 넣어서 반환하였다.

정상 호출되지만,  Whitelabel Error Page오류가 발생한다.

 

application.properties에 

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

이 코드를 추가하면 된다.

스프링 부트는 InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록하는데, 이때 application.properties에 등록한

위의 설정정보를 사용해서 등록한다.

 

동작 방식

1. 핸들러 어댑터 호출

  핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름을 획득한다.

 

2. ViewResolver 호출

  new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.

  BeanNameViewResolver는 new-form이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하지만 없다.

  InternalResourceViewResolver가 호출된다.

 

3. InternalResourceViewResolver

  이 뷰 리졸버는 InternalResourceViewResolver를 반환한다.

 

4. 뷰 - IntervalResourceView

  internalResourceView는 JSP 포워드를 호출해서 처리할 수 있는 경우에 사용한다.

 

5. view.render()

  view.render()가 호출되고 InternalResourceView는  forward()를 사용해서 JSP를 실행한다.

핸들러 매핑과 핸들러 어댑터가 어떤 것들이 어떻게 사용되는지 알아보았다.

지금은 전혀 사용하지 않지만, 과거에 주로 사용했던 스프링이 제공하는 간단한 컨트롤러로 핸들러 매핑과 어댑터를 이해해보았다.

 

Controller 인터페이스

과거 버전 스프링 컨트롤러

public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse
response) throws Exception;
}

스프링도 처음에는 이런 딱딱한 형식의 컨트롤러를 제공했다.

 

package hello.servlet.web.springmvc.old;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

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

@Component("/springmvc/old-controller")
public class OldController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return null;
    }
}

구형 버전의 컨트롤러를 구현해보았다.

@Component : 이 컨트롤러는 /springmvc/old-controller라는 이름의 스프링 빈으로 등록되었다.

빈의 이름으로 URL을 매핑할 것이라는 뜻이다.

 

이 컨트롤러는 어떻게 호출될까?

 

이 컨트롤러가 호출되려면 2가지가 필요하다.

- HandlerMapping

   핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야한다.

   스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.

- HandlerAdapter

   핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.

   Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.

 

1. 핸들러 매핑으로 핸들러 조회

  HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다.

  이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 

  BeanNameUrlHandlerMapping이 실행되고 OldController를 반환한다.

 

2. 핸들러 어댑터 조회

  HandlerAdapter의 supports() 를 순서대로 호출한다.

  SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하므로 대상이 된다.

 

3. 핸들러 어댑터 실행

  디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함꼐 넘겨준다.

  SimpleControllerHandlerAdapter는 핸들러인 OldController를 내부에서 실행하고, 그 결과를 반환한다.

 

package hello.servlet.web.springmvc.old;

import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyHttpRequestHandler.handleRequest");
    }
}

1. 핸들러 매핑으로 핸들러 조회

  이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는

  BeanNameUrlHandlerMapping이 실행되고 핸들러인 MyHttpRequestHandler를 반환한다.

 

2. 핸들러 어댑터 조회

  HandlerAdapter의 supports()를 순서대로 호출한다.

  HttpRequestHandlerAdapter가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.

 

3. 핸들러 어댑터 실행

  디스패처 서블릿이 조회한 HttpRequestHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.

  HttpRequestHandlerAdapter는 핸들러인 MyHttpRequestHandler를 내부에서 실행하고, 그 결과를 반환한다.

 

'웹프로그래밍 > 스프링 MVC' 카테고리의 다른 글

34. 스프링 MVC - 시작하기  (0) 2022.04.01
33. 뷰 리졸버  (0) 2022.03.31
31. 스프링 MVC 전체 구조  (0) 2022.03.31
30. 유연한 컨트롤러2 - V5  (0) 2022.03.30
29. 유연한 컨트롤러1 - V5  (0) 2022.03.30

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/lecture/71202?volume=1.00&tab=note

지금까지 개선하며 제작한 MVC 프레임워크와 

스프링MVC를 비교해보았다.

직접 만든 프레임워크 스프링 MVC
FrontController DispatcherServlet
handlerMappintMap HandlerMapping
MyHandlerAdapter HandlerAdapter
ModelView ModelAndView
viewResolver ViewResolver
MyView View

 

DispatcherServlet 구조에 대하여

org.springframework.web.servlet.DispatcherServlet

 

스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어있다.

스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿이다.

이 디스패처 서블릿이 바로 스프링MVC의 핵심이다.

 

DispatcherServlet 서블릿 등록

DispatcherServlet도 부모 클래스에서 HttpServlet을 상속받아서 사용하고, 서블릿으로 동작한다.

스프링 부트는 DispatcherServlet 을 서블릿으로 자동으로 등록하면서 모든 경로에 대해서 매핑한다.

 

요청 흐름

서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.

스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 오버라이드 해두었다.

FrameworkServlet.service()를 시작으로 여러 메서드가 호출되면서

DispatcherServlet.doDispatch()가 호출된다.


SpringMVC 동작 순서

 

1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러를 조회한다.

2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.

3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.

4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.

5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환하여 반환한다.

6. viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.

7. View 반환 : 뷰 리졸버는 뷰의 논리이름을 물리이름으로 바꾸고 랜더링 역할을 담당하는 View객체를 반환한다.

8. 뷰 랜더링 : 뷰를 통해서 뷰를 랜더링 한다.

 

스프링 MVC의 큰 강점은 DispatcherServlet코드의 변경 없이 , 원하는 기능을 변경하거나 확장할 수 있다는점이다.

인터페이스들만 구현해서 DispatcherServlet에 등록하면 나만의 컨트롤러를 만들 수도 있다.

동작 방식을 직접 구현해보면서 향후 문제가 발생하였을때 어떤 부분에서 문제가 발생했는지 쉽게 파악 가능하고 

문제를 해결할 수 있을 것이라고 생각한다.

 

 

FrontControllerServlet5에 ControllerV4기능도 추가해보았다.

package hello.servlet.web.frontcontroller.V5;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.V3.ControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberFormControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberSaveControllerV3;
import hello.servlet.web.frontcontroller.V5.adapter.ControllerV3HandlerAdapter;
import hello.servlet.web.frontcontroller.V5.adapter.ControllerV4HandlerAdapter;
import hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name="frontControllerServletV5",urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {

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

    private void initHandlerMappingMap() {
        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());
    }
    public FrontControllerServletV5() {
        initHandlerMappingMap();
        initHandlerAdapters();
    }

    private void initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
        handlerAdapters.add(new ControllerV4HandlerAdapter());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Object handler = getHandler(request);
        if (handler == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        MyHandlerAdapter adapter = getHandlerAdapter(handler);

        ModelView mv = adapter.handle(request, response, handler);

        String viewName = mv.getViewName();
        MyView view = viewResolver(viewName);

        view.render(mv.getModel(), request, response);

    }

    private MyHandlerAdapter getHandlerAdapter(Object handler) {
        MyHandlerAdapter a;
        for (MyHandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
    }

    private Object getHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return handlerMappingMap.get(requestURI);
    }

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

핸들러 매핑에 controllerV4를 사용하는 컨트롤러를 추가하고 해당 컨트롤러를 처리할 수 있는

어댑터인 ControllerV4HandlerAdapter도 추가하였다.

 

package hello.servlet.web.frontcontroller.V5.adapter;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.V5.MyHandlerAdapter;
import hello.servlet.web.frontcontroller.v4.ControllerV4;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof ControllerV4);
    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        ControllerV4 controller = (ControllerV4) handler;

        Map<String, String> paramMap = createParamMap(request);
        HashMap<String,Object> model = new HashMap<>();

        String viewName = controller.process(paramMap, model);

        ModelView mv = new ModelView(viewName);
        mv.setModel(model);

        return mv;
    }
    private Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paraMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paraMap.put(paramName, request.getParameter(paramName)));
        return paraMap;
    }
}

supports()는 

handler가 ControllerV4인 경우에만 처리하는 어댑터이다.

 

handler를 ControllerV4로 캐스팅하고, paramMap, model을 만들어서 해당 컨트롤러를 호출하고

viewName을 반환받는다.

 

어댑터가 호출하는 ControllerV4는 뷰의 이름을 반환한다. 그런데 어댑터는 뷰의 이름이 아니라 ModelView를 만들어서

반환해야한다. 여기서 어댑터의 최대 기능이 나온다.

ControllerV4는 뷰의 이름을 반환했지만 , 어댑터는 이것을 ModelView로 만들어서 형식을 맞추어 반환한다.

만일 어떤 개발자는 V3 , 누구는 V4방식으로 개발하고 싶다면 어떻게 해야할까?

 

어댑터 패턴

지금까지 프론트 컨트롤러에서는 한가지 방식으로 인터페이스를 고정해서 사용할 수 있었다.

따라서 호환이 불가능하다. 어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의

컨트롤러를 처리할 수 있도록 변경해 보았다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/lecture/71194?volume=1.00&tab=note&speed=0.75&mm=close

핸들러 어댑터 : 중간에 어댑터가 추가되었다. 덕분에 다양한 종류의 컨트롤러를 호출할 수 있다.

핸들러 : 컨트롤러의 이름을 더 넗은 범위인 핸들러로 변경했다. 어댑터가 있기 때문에 꼭 컨트롤러의 개념 뿐만 아니라

어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있기 때문이다.

 

package hello.servlet.web.frontcontroller.V5;

import hello.servlet.web.frontcontroller.ModelView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public interface MyHandlerAdapter {

    boolean supports(Object handler);
    ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}

어댑터용 인터페이스를 생성하였다.

supports는 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메서드다.

handler는 컨트롤러를 말한다.

 

어댑터는 실제 컨트롤러를 호출하고 ,ModelView를 반환해야한다.

실제 컨트롤러가 ModelView를 반환하지 못하면, 어댑터가 ModelView를 직접 생성해서라도 반환해야한다.

프론트 컨트롤러가 이 어댑터를 통해 실제 컨트롤러가 호출된다.

package hello.servlet.web.frontcontroller.V5.adapter;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.V3.ControllerV3;
import hello.servlet.web.frontcontroller.V5.MyHandlerAdapter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof ControllerV3);
    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        ControllerV3 controller = (ControllerV3) handler;

        Map<String, String> paramMap = createParamMap(request);
        ModelView mv = controller.process(paramMap);

        return mv;
    }


    private Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paraMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paraMap.put(paramName, request.getParameter(paramName)));
        return paraMap;
    }
}

ControllerV3를 지원하는 어댑터를 생성하였다.

먼저 supports에서 instanceof연산자를 사용해서 처리가능한지 판단한다.

 

handler를 컨트롤러 V3로 변환한 다음에 V3 형식에 맞도록 호출한다.

supports()를 통해 ControllerV3만 지원하기 때문에 캐스팅은 걱정할 필요가 없다.

ControllerV3는 ModelView를 반환하므로 그대로 ModelView를 반환한다.

package hello.servlet.web.frontcontroller.V5;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.V3.ControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberFormControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberSaveControllerV3;
import hello.servlet.web.frontcontroller.V5.adapter.ControllerV3HandlerAdapter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name="frontControllerServletV5",urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {

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

    public FrontControllerServletV5() {
        initHandlerMappingMap();
        initHandlerAdapters();
    }

    private boolean initHandlerAdapters() {
        return handlerAdapters.add(new ControllerV3HandlerAdapter());
    }

    private void initHandlerMappingMap() {
        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());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Object handler = getHandler(request);

        if (handler == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        MyHandlerAdapter adapter = getHandlerAdapter(handler);

        ModelView mv = adapter.handle(request, response, handler);

        String viewName = mv.getViewName();
        MyView view = viewResolver(viewName);

        view.render(mv.getModel(), request, response);

    }

    private MyHandlerAdapter getHandlerAdapter(Object handler) {
        MyHandlerAdapter a;
        for (MyHandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
    }

    private Object getHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return handlerMappingMap.get(requestURI);
    }

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

이전에는 컨트롤러를 직접 매핑해서 사용했는데 이젠 어댑터를 사용하기 때문에,

컨트롤러 뿐만 아니라 어댑터가 지원하기만 하면, 어떤 것이라도 URL에 매핑해서 사용할 수 있다 그래서

이름은 핸들러로 변경하였다.

 

생성자는 핸들러 매핑과 어댑터를 초기화한다.

매핑 정보의 값이 ControllerV3, ControllerV4같은 인터페이스가 아니라 Object로 변경되었다.

 

핸들러 매핑 정보인 handlerMappintMAp에서 URL에 매핑된 핸들러 객체를 찾아서반환한다.

 

handler를 처리할 수 있는 어댑터를 adapter.support(handler)를 통해서 찾는다.

handler가 ControllerV3인터페이스를 구현했다면 , ControllerV3HandlerAdapter객체가 반한된다.

 

어댑터의 handle(request, response, handler) 를 통해 실제 어댑터가 호출된다.

어댑터는 handler를 호출하고 그 결과를 어댑터에 맞추어 반환한다.

 

'웹프로그래밍 > 스프링 MVC' 카테고리의 다른 글

31. 스프링 MVC 전체 구조  (0) 2022.03.31
30. 유연한 컨트롤러2 - V5  (0) 2022.03.30
28. 단순하고 실용적인 컨트롤러 - V4  (0) 2022.03.30
27. Model 추가 - V3  (0) 2022.03.28
26. View 분리 - v2  (0) 2022.03.26

이전 포스팅에서 만든 v3 컨트롤러는 서블릿 종속성을 제거하고 뷰 경로의 중복을 제거하는 잘 설계한 컨트롤러이다.

그런데 실제 컨트롤러 인터페이스를 구현하는 개발자 입장에서 본다면? 항상 ModelView객체를 생성하고

반환해야하는 부분이 번거롭다.

좋은 프레임워크란 아키텍처도 중요하지만, 그와 더불어 실제 개발하는 개발자가 단순하고 편리하게 사용 할 수 있어야한다.

 

이번에는 v3를 조금 변경하여 개발자들이 편리하게 개발할 수 있는 v4를 만들어보았다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/lecture/71192?volume=1.00&tab=note&speed=0.75&mm=close

기본적인 구조는 이전 버전과 같다. 대신에 컨트롤러가 ModelView를 반환하지 않고 ViewName을 반환한다.

package hello.servlet.web.frontcontroller.v4;

import java.util.Map;

public interface ControllerV4 {

    /**
     *
     * @param paraMap
     * @param model
     * @return viewName
     */
    String process(Map<String, String> paraMap, Map<String,Object> model);
}

먼저 Controller4인터페이스를 만들었다.

이전과는 다르게 파라미터로 model객체를 넘겨준다(빈 객체)

반환값은 문자열로 논리주소를 반환한다.

package hello.servlet.web.frontcontroller.v4.controller;


import hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.Map;

public class MemberFormControllerV4 implements ControllerV4 {

    @Override
    public String process(Map<String, String> paraMap, Map<String, Object> model) {
        return "new-form" ;
    }
}
package hello.servlet.web.frontcontroller.v4.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.Map;

public class MemberSaveControllerV4 implements ControllerV4 {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paraMap, Map<String, Object> model) {
        String username = paraMap.get("username");
        int age = Integer.parseInt(paraMap.get("age"));

        Member member = new Member(username,age);
        memberRepository.save(member);
        model.put("member",member);
        return "save-result";
    }
}
package hello.servlet.web.frontcontroller.v4.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.List;
import java.util.Map;

public class MemberListControllerV4 implements ControllerV4 {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paraMap, Map<String, Object> model) {
        List<Member> members = memberRepository.findAll();
        model.put("members",members);
        return "members";
    }
}

회원 가입, 회원 저장, 회원 목록 컨트롤러들도 만들었다.

저장, 목록에서 모델에 데이터를 put으로 넣고 

논리 이름을 반환하고 있다.

package hello.servlet.web.frontcontroller.v4;

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {

    private Map<String, ControllerV4> controllerMap = new HashMap<>();

    public FrontControllerServletV4() {
        controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
        controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
        controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();

        ControllerV4 controller = controllerMap.get(requestURI);
        if(controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> paraMap = createParamMap(request);
        Map<String, Object> model = new HashMap<>();  // 추가됨

        String viewName = controller.process(paraMap, model);

        MyView view = viewResolver(viewName);

        view.render(model,request,response);
    }

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

    private Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paraMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paraMap.put(paramName, request.getParameter(paramName)));
        return paraMap;
    }
}

model을 넘겨주는것이 추가 되고 

viewName에 각각 컨트롤러의 반환값이 들어온다.

view객체는 viewName만 받고  render()는 파라미터값으로 model만, request, response만 받으면 된다.

바뀐 것은 많이 없지만 개발자입장에서 군더더기 없는 코드를 작성할 수 있다.

 

 

'웹프로그래밍 > 스프링 MVC' 카테고리의 다른 글

30. 유연한 컨트롤러2 - V5  (0) 2022.03.30
29. 유연한 컨트롤러1 - V5  (0) 2022.03.30
27. Model 추가 - V3  (0) 2022.03.28
26. View 분리 - v2  (0) 2022.03.26
25. 프론트 컨트롤러 도입 - v1  (0) 2022.03.25

서블릿 종속성 제거

컨트롤러 입장에서 HttpServletRequest, HttpServletResponse가 꼭 필요할까?

요청 파라미터 정보는 자바의 Map으로 넘기도록 하면 컨트롤러가 서블릿 기술을 몰라도 작동할 수 있다.

request 객체를 Model로 사용하는 대신에 별도의 Model 객체를 만들어서 반환하면 된다.

컨트롤러가 서블릿 기술을 전혀 사용하지 않도록 변경해보았다.

이렇게 하면 구현 코드가 단순해지며 테스트 코드 작성에도 용이하다.

 

뷰 이름 중복 제거

컨트롤러에서 지정하는 뷰 이름에 중복이 있는 것을 확인할 수 있다.

컨트롤러는 뷰의 논리 이름을 반환하고, 실제 물리 위치의 이름은 프론트 컨트롤러에서 처리하도록

단순화 하면 향후 뷰의 폴더위치가 함게 이동해도 프론트 컨트롤러만 고치면 된다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/lecture/71191?volume=1.00&tab=note&speed=0.75&mm=close

ModelView

지금가지 컨트롤러에서 종속적인 HttpServletRequest를 사용했다. 그리고 Model도 request.setAttribute()를 통해

데이터르를 저장하고 뷰에 전달했다.

서블릿의 종속성을 제거하기 위해  Model을 직접 만들고, 추가로 View 이름가지 전달하는 객체를 만들어보았다.

package hello.servlet.web.frontcontroller;

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

public class ModelView {
    private String viewName;
    private Map<String, Object> model = new HashMap<>();

    public ModelView(String viewName) {
        this.viewName = viewName;
    }

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    public Map<String, Object> getModel() {
        return model;
    }

    public void setModel(Map<String, Object> model) {
        this.model = model;
    }
}

뷰의 이름, 뷰를 랜더링할때 필요한 model객체를 가진 클래스를 생성한다.

package hello.servlet.web.frontcontroller.V3;

import hello.servlet.web.frontcontroller.ModelView;

import java.util.Map;

public interface ControllerV3 {

    ModelView process(Map<String, String> paraMap);
}

 

컨트롤러V3 인터페이스를 만들고 이전 버전과는 다르게 서블릿 기술을 전혀 사용하지 않았다.

package hello.servlet.web.frontcontroller.V3.controller;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.V3.ControllerV3;

import java.util.Map;

public class MemberFormControllerV3 implements ControllerV3 {
    @Override
    public ModelView process(Map<String, String> paraMap) {
        return new ModelView("new-form");
    }
}
package hello.servlet.web.frontcontroller.V3.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.V3.ControllerV3;

import java.util.Map;

public class MemberSaveControllerV3 implements ControllerV3 {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public ModelView process(Map<String, String> paraMap) {
        String username = paraMap.get("username");
        int age = Integer.parseInt(paraMap.get("age"));

        Member member = new Member(username,age);
        memberRepository.save(member);

        ModelView mv = new ModelView("save-result");
        mv.getModel().put("member",member);
        return mv;
    }
}
package hello.servlet.web.frontcontroller.V3.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.V3.ControllerV3;

import java.util.List;
import java.util.Map;

public class MemberListControllerV3 implements ControllerV3 {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public ModelView process(Map<String, String> paraMap) {
        List<Member> members = memberRepository.findAll();
        ModelView mv = new ModelView("members");
        mv.getModel().put("members",members);

        return mv;
    }
}

각각 컨트롤러들을 만들어주었다. 여기서 반환값은 논리적인 이름이다. 실제 물리적인 이름은 프론트 컨트롤러에서 처리한다.

package hello.servlet.web.frontcontroller.V3;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.V3.controller.MemberFormControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.V3.controller.MemberSaveControllerV3;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {

    private Map<String, ControllerV3> controllerMap = new HashMap<>();

    public FrontControllerServletV3() {
        controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
        controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
        controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV1.service");

        String requestURI = request.getRequestURI();

        ControllerV3 controller = controllerMap.get(requestURI);
        if(controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> paraMap = createParamMap(request);
        ModelView mv = controller.process(paraMap);
        String viewName = mv.getViewName();
        MyView view = viewResolver(viewName);
        view.render(mv.getModel(),request,response);
    }

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

    private Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paraMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paraMap.put(paramName, request.getParameter(paramName)));
        return paraMap;
    }
}

프론트 컨트롤러 코드는 이전보다 복잡해졌다.

viewResolver()가 컨트롤러가 반환한 논리이름을 실제 물리 이름으로 치환해준다. 그리고 실제 물리 경로가 있는

Myview객체를 반환하고 Myview 객체의 render()를 통해 화면을 그리게 된다.

현재까지 만든 프로젝트에는 모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있다.

이 부분을 분리하기위해 별도로 뷰를 처리하는 객체를 만들어보았다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/lecture/71190?volume=1.00&tab=note&speed=0.75&mm=close

package hello.servlet.web.frontcontroller;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyView {
    private String viewPath;

    public MyView(String viewPath) {
        this.viewPath = viewPath;
    }

    public void render(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request,response);
    }
}

중복되었던 코드들이 있는 MyView클래스 생성

package hello.servlet.web.frontcontroller.v2;

import hello.servlet.web.frontcontroller.MyView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public interface ControllerV2 {
    MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}

인터페이스 생성 기존에는 void였던 process를 MyView형식으로 반환

package hello.servlet.web.frontcontroller.v2.controller;

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MemberFormControllerV2 implements ControllerV2 {

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        return new MyView("/WEB-INF/views/new-form.jsp");
    }
}
package hello.servlet.web.frontcontroller.v2.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class MemberListControllerV2 implements ControllerV2 {
    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();
        request.setAttribute("members",members);

        return new MyView("/WEB-INF/views/members.jsp");
    }
}
package hello.servlet.web.frontcontroller.v2.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MemberSaveControllerV2 implements ControllerV2 {
   MemberRepository memberRepository = MemberRepository.getInstance();
    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username,age);
        memberRepository.save(member);

        // Model에 데이터 보관한다.
        request.setAttribute("member",member);

        return new MyView( "/WEB-INF/views/save-result.jsp");
    }
}

기존에 있던 3개의 컨트롤러를 MyView객체를 생성하여 반환하게 한다. 생성자에는 경로

package hello.servlet.web.frontcontroller.v2;

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.controller.MemberFormControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberListControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberSaveControllerV2;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {

    private Map<String, ControllerV2> controllerMap = new HashMap<>();

    public FrontControllerServletV2() {
        controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
        controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
        controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV1.service");

        String requestURI = request.getRequestURI();

        ControllerV2 controller = controllerMap.get(requestURI);
        if(controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        MyView view = controller.process(request, response);
        view.render(request,response);
    }
}

프론트 컨트롤러또한 만들어 주었다. 공통로직을 한번에 처리할 수 있게 되었다.

프론트 컨트롤러를 단계적으로 도입해보았다.

이번 목표는 기존 코드를 최대한 유지하면서, 프론트 컨트롤러를 도입하는 것이다.

먼저 구조를 맞추어두고 점진적으로 리팩토링 할 예정이다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/lecture/71189?volume=1.00&tab=note&speed=0.75&mm=close

클라이언트가 HTTP요청을 하면

Front Controller라는 서블릿이 요청을 받는다.

URL 매핑 정보를 조회하고 

Front Controller가 해당 컨트롤러를 호출한다.

해당 컨트롤러에서 JSP forward로 호출한다.

 

다형성을 활용해서 인터페이스로 만들어 보았다.

package hello.servlet.web.frontcontroller.v1;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public interface ControllerV1 {

    void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

서블릿과 비슷한 모양의 컨트롤러 인터페이스를 도입하였다. 각 컨테이너들은 인터페이스를 구현해야 하고

프론트 컨트롤러는 이 인터페이스를 호출해서 구현과 관계없이 로직의 일관성을 가져갈 수 있다.

 

package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.web.frontcontroller.v1.ControllerV1;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MemberFormControllerV1 implements ControllerV1 {

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request,response);
    }
}
package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MemberSaveContollerV1 implements ControllerV1 {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username,age);
        memberRepository.save(member);

        // Model에 데이터 보관한다.
        request.setAttribute("member",member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request,response);
    }
}
package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class MemberListControllerV1 implements ControllerV1 {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();
        request.setAttribute("members",members);

        String viewPath = "/WEB-INF/views/members.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request,response);
    }
}

회원가입, 회원저장, 회원 목록 3가지 기능을 구현한 클래스이다.

이전에 서블릿공부할때랑 코드가 유사하다

다른점이있다면 상속받던 HttpServlet 대신에 위에서 만든 ControllerV1을 implements하고있고

애너테이션도 없다

또한 service를 오버라이드를 하지않고

ControllerV1의 process를 구현한다.

package hello.servlet.web.frontcontroller.v1;

import hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberSaveContollerV1;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {

    private Map<String, ControllerV1> controllerMap = new HashMap<>();

    public FrontControllerServletV1() {
        controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
        controllerMap.put("/front-controller/v1/members/save", new MemberSaveContollerV1());
        controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV1.service");

        String requestURI = request.getRequestURI();

        ControllerV1 controller = controllerMap.get(requestURI);
        if(controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        controller.process(request, response);
    }
}

프론트 컨트롤러를 만들었다 HttpServlet을 상속받고

urlPattern을 /front-controller/v1 하위에 들어오는것들을 모두 받는다.

 

해시맵을 정의하여 <URL,해당 클래스 객체(호출될 컨트롤러)> 형태로 값을 매칭 시켜준다.

service를 오버라이딩 하여

request.getRequestURI를 통해 요청에 들어온 uri를 저장하고

get으로 해당 객체를 꺼내오고 만든 인터페이스에 담는다.

없다면 404 에러를 내게된다.

그리고 controller.process를 실행하여 해당 로직을 실행시킬 수 있다.

'웹프로그래밍 > 스프링 MVC' 카테고리의 다른 글

27. Model 추가 - V3  (0) 2022.03.28
26. View 분리 - v2  (0) 2022.03.26
24. 프론트 컨트롤러 패턴 소개  (0) 2022.03.24
23. MVC 패턴 - 한계  (0) 2022.03.24
22. MVC 패턴 - 적용  (0) 2022.03.21

+ Recent posts