본문 바로가기
Spring/스프링 MVC

[스프링 MVC 1편] 4 - (3) 프론트 컨트롤러 - v3

by Poorm 푸름 2023. 11. 10.

 *  스프링 입문 = window, 스프링 MVC 1편 = Mac 으로 진행합니다

 *  진도 : 섹션4 - (4)

 *          : 자바 클래스명,         : 코드,         : 단축키

 

1. 프론트 컨트롤러_구현 3단계 

 

  • 서블릿 종속제거

    • HttpServletRequest
    • HttpServletResponse

더보기

 < 코드 예시 >

 public MyView process(HttpServletRequest request, HttpServletResponse response) 
 throws ServletException, IOException {

 

  • 뷰 이름 중복제거

    • 뷰 이름의 중복(/WEB-INF/views/)을 제거하고 논리 이름만 반환
더보기

< 코드 예시 >

return new MyView("/WEB-INF/views/new-form.jsp");

 

  • ModelView

    • 서블릿의 종속성을 제거하기 위해 Model을 직접 만들고, 추가로 View 이름까지 전달하는 객체를 만들자
      (컨트롤러에서 HttpServletRequest 사용 X → 직접 request.setAttribute() 호출 X → Model 별도로 필요)

 

1) ModelView 클래스 만들자!
 [ src - java - hello.servlet - web - frontcontroller - ModelView ]

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

 

  1. 뷰 클래스는 제일 먼저 논리적 이름 가져가기 private String viewName

  2. 생성 private Map<String, Object> model ~
    (나머지는 Map 다 String 이다)

  3. 생성자 만들기 1

    1. command + n - constructor 클릭 - viewName.String Ok 클릭

  4. 생성자 만들기 2

    1. command + n - Getter and Setter 클릭 - 모두 선택한 다음 Ok 클릭

 

더보기
< 기존의 MyView 클래스에서 삭제한 코드 >

  • 렌더링 public void render ~

  • request.getRequestDispatcher (option + command + v)

  • dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능(서버 내부에서 호출)
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);
    }
}

 

2) 인터페이스를 만들자!
 [ src - java - hello.servlet - web - frontcontroller .v3 - ControllerV3 ]

public interface ControllerV3 {
    ModelView process(Map<String, String> paramMap);

  

  1. ModelView를 반환하는 인터페이스

    1. 서블릿과 앞부분 이름 차이
      protected void service  MyView process

  2. HttpServletRequest, HttpServletResponse 를 제거하고 Map<> paramMap으로 변경

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

  4. 인터페이스는 어딘가에 상속 혹은 종속 되어있지 않아서 extend나 implements 사용 X

더보기

 < 기존의 ControllerV2 인터페이스에서 삭제한 코드 >

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

 

 3) 회원 등록 컨트롤러
 [ src - java - hello .servlet - web - frontcontroller .v3 - controller - MemberFormControllerV3 ]

public class MemberFormControllerV3 implements ControllerV3 {

    @Override
    public ModelView process(Map<String, String> paramMap) {
        return new ModelView("new-form");

    }
}

 

  • 상속 implements ControllerV3 ~ @Override (option + enter)

    • 이제는 HttpServlet Re~ 코드가 아닌 맵이 자동으로 생성이 된다

  • 뷰 영역 return new ModelView("// JSP 경로 //")  에서 경로가 물리적 주소가 아닌 논리적 이름만 입력

    • 논리적 이름 = "new-form"

더보기

 < 기존의 MemberSaveControllerV2 클래스에서 삭제한 코드 >

  • HttpServletRequest, HttpServletResponse
  • WEB-INF/views/new-form.jsp (물리적 주소)

 

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

 

 4) 회원 저장 컨트롤러
[ src - java - hello .servlet - web - frontcontroller .v3 - controller - MemberSaveControllerV3 ]

public class MemberSaveControllerV3 implements ControllerV3 {
    
    private MemberRepository memberRepository = MemberRepository.getInstance(); 
    

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

        Member member = new Member(username, age);
        memberRepository.save(member);
        
        ModelView mv = new ModelView("save-result");
        mv.getModel().put("member", member);
        return mv;
                

    }
}

 

  • 상속 implements ControllerV3 ~ @Override (option + enter)

    • 이제는 HttpServlet Re~ 코드가 아닌 맵이 자동으로 생성이 된다

  • 리포지토리 가져오기 private MemberRepository.getInstance (서블릿 방식 때랑 같음)

  • form data 값 읽어오기 request.get 이 아닌 paramMap.get으로 변경  (command + option + v)

  • Member 객체 생성 및 저장 (서블릿 방식 때랑 같음)

  • 뷰 영역 객체 생성  ~ new ModelView("// 논리적 이름 //")

  • 뷰 객체에 member 담기 ~mv.getModel().put("member")

  • 출력 return mv; 

더보기

 < 기존의 MemberSaveControllerV2 클래스에서 삭제한 코드 >

  • HttpServletRequest, HttpServletResponse
  • form data 값 읽어오기request.getParameter()
  • 모델에 데이터 담기request.setAttribute()
  • 뷰 영역 return new MyView("// JSP 경로 //") 

 

 

public class MemberSaveControllerV2 implements ControllerV2 {

    private 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);

        request.setAttribute("member", member);

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

    }
}

 

 5) 회원 목록 컨트롤러
 [ src - java - hello .servlet - web - frontcontroller .v3 - controller - MemberListControllerV3 ]

public class MemberListControllerV3 implements ControllerV3{
    private MemberRepository memberRepository = MemberRepository.getInstance();

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

        return mv;
    }
}

 

  • 상속 implements ControllerV3 ~ @Override (option + enter)

    • 이제는 HttpServlet Re~ 코드가 아닌 맵이 자동으로 생성이 된다

  • 리포지토리 가져오기private MemberRepository.getInstance(서블릿 방식 때랑 같음)

  • 데이터 조회 memberRepository.findAll() (option + command + v) (서블릿 방식 때랑 같음)
  • 뷰 영역 객체 생성  ~ new ModelView("// 논리적 이름 //")

  • 뷰 객체에 member 담기 ~mv.getModel().put("member")

  • 출력 return mv; 

더보기

 < 기존의 MemberSaveControllerV2 클래스에서 삭제한 코드 >

  • HttpServletRequest, HttpServletResponse
  • 데이터 조회 memberRepository.findAll() (option + command + v)
  • 모델에 데이터 담기request.setAttribute()
  • 뷰 영역 return new MyView("// JSP 경로 //") 

 

public class MemberListControllerV2 implements ControllerV2 {

    private 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");
    }
}

 

 5) 프론트 컨트롤러 만들기!!
[ src - java - hello.servlet - web - frontcontroller. v3 - FrontControllerServletV3 ]

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

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

    public FrontControllerServiceV3() {
        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 {

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

        Map<String, String> paramMap = createParamMap(request);

        ModelView mv = controller.process(paramMap);

        String viewName = mv.getViewName();

        MyView view = viewResolver(viewName);

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

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

    private static Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }
}
  • 프론트 컨트롤러는 꼭 서블릿으로 작성한다!

1. 상속 extends HttpServlet

 

2. 서블릿 생성@WebServlet

  • /* 표시는 해당 위치에 어떤 url 이 들어와도 서블릿 무조건 호출 가능하다는 의미

3. 서비스 생성 protected service

 

4. 맵 생성 private Map

 

  • key = url, value = ControllerV2
  • 매핑 정보 담을 공간 = controllerMap

5. 매핑 정보 public FrontControllerServiceV3()

  • command + n 하고 constructor 클릭 → Select None 클릭
  • 매핑정보를 담기!
  • key 값이 요청되면 value를 실행하게끔 설정

6. URI 주소 가져오기 request.getRequestURI (option + command + v)

7. 맵에서 URI 찾아 컨트롤러 반환 controllerMap.get(requestURI)(option + command + v)

  1. 예외처리는 404 출력 SC_NOT_FOUND

8. 맵 생성2 Map<String, String> paraMap

9. 파라미터네임 꺼내기 request.getParameterName

  • request.getParameter() : ("파라미터 이름")을 작성하면 값을 꺼낼 수 있다
  • asIterator() : iterator 인터페이스형으로 바꿔서 반환
  • Iterator.forEachRemaining() : Iterator의 forEachRemaining() 메서드를 사용하여 키-값 쌍을 콘솔에 출력
  • controller.process() 에서 request, response 가 아닌 paramMap 을  넘겨주기 위해 사용
  • param -> paramName.put(paramName, request.getParameter(paramName))
    • key = paraName, value = request.getParameter(paramName)

너무 길어서 따로 메서드로 뽑는게 좋다!
         마지막에 option + command + m하고 createParamMap 입력하기

 

10. 파라미터 받기  controller.process(paramMap) (option + command + v)

 

 11. 뷰 설정

  • viewName 객체 설정

  • new MyView("/WEB-INF/views/" + viewName + ".jsp") (option + command + v) 하고
    option + command + m
     해서 viewResolver(viewName) 입력하기
    • 컨트롤러가 반환한 논리 뷰 이름을 실제 물리 뷰 경로로 변경, 실제 물리 경로가 있는 MyView 객체를 반환
      논리 뷰 이름: members
      물리 뷰 경로: /WEB-INF/views/members.jsp


  • 렌더 호출 view.render()mv.getModel() 넣고 (option + enter) create~ 클릭
  • mv.getModel() 에 커서두고 (option + enter)하면 MyView로 이동해서 아래코드로 작성
더보기
model.forEach((key, value) -> request.setAttribute(key, value));
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

 

하고 (option + command + m) 해서 modelToRequestAttribute 입력

 

 

<실행과정>

  1. URL =http://localhost:8080/front-controller/v3/*실행
    * 자리에 아무거나 넣어도 하위 메서드는 모두 서블릿 호출 가능

  2.  request.getRequestURI 경로를 가져온다

  3. createParamMap 을 통해 파라미터를 모두 뽑고 paramMap으로 반환

  4. process.paramMap에서 논리 주소 찾기
  5. 논리 주소를 갖고  된viewResolver 호출

  6. MyView 반환 & 렌더 호출

  7. 렌더 호출하면 모델 정보(Attribute) 넘기기