58일차 (2) - 스프링 (mvc패턴을 스프링을 통해 구현)

Yohan·2024년 5월 14일
0

코딩기록

목록 보기
85/156
post-custom-banner

기존 JSP/Servlet을 통해 MVC패턴 구현

1. HomeServlet을 통해 요청하여 index.jsp를 띄움

@WebServlet("/index")
public class HomeServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        RequestDispatcher dp = req.getRequestDispatcher("/WEB-INF/index.jsp");
        dp.forward(req, resp);
    }
}

2. RegisterServlet에서 요청하여 reg_form.jsp (회원가입 폼)에 들어감

@WebServlet("/chap01/join")
public class RegisterServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String viewName = "/WEB-INF/views/reg_form.jsp";

        RequestDispatcher dp
                = req.getRequestDispatcher(viewName);
        dp.forward(req, resp);
    }
}

3. SaveServlet를 통해 저장하고 조회화면을 띄워줌 (조회화면으로 리다이렉트)

  • 일반적으로 데이터를 변경하는 작업인 경우(예: 저장, 수정, 삭제 등), 보통 redirect를 사용
    • 데이터를 변경한 후에 리다이렉트를 사용하면, 사용자가 새로고침을 하더라도 중복 데이터 변경을 방지
    • 또한 새로고침을 눌렀을 때 경고메시지나 에러 방지
@WebServlet("/chap01/save")
public class SaveServlet extends HttpServlet {

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 1. 회원가입 폼에서 넘어온 데이터 읽기
        String userName = req.getParameter("userName");
        String account = req.getParameter("account");
        String password = req.getParameter("password");

        // 2. 회원 정보를 객체로 포장하여 적절한 저장소에 저장
        Member member = new Member(account, password, userName);
        repo.save(member);

        // 3. 적절한 페이지로 이동 - 조회화면으로 리다이렉트
        resp.sendRedirect("/chap01/show");

    }
}

4. ShowServlet을 통해 JSP를 통해 화면을 렌더링하여 띄워줌

@WebServlet("/chap01/show")
public class ShowServlet extends HttpServlet {

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 1. 적절한 저장소에서 회원정보들을 가져옴
        List<Member> memberList = repo.findAll();

        // 2. 해당 회원정보를 JSP파일에 전송하기 위한 세팅을 함 (수송객체에 담음)
        req.setAttribute("memberList", memberList);

        // 3. 적절한 JSP를 찾아 화면 렌더링
        String viewName = "/WEB-INF/views/m-list.jsp";

        RequestDispatcher dp = req.getRequestDispatcher(viewName);
        dp.forward(req, resp);

    }
}

기존 JSP/Servlet을 통해 MVC패턴 구현의 문제점

  • 하나의 요청 당 하나의 Servlet을 만들어야 함

스프링을 통해 MVC패턴 구현

V1) 요청하는 Servlet을 하나로 통합

  • FrontControllerV1이라는 통합 Servlet에서 필요한 Controller(이전 Servlet)들을 Map에 넣어서 요청 uri를 key로하는 value에 해당하는 각 Controller을 실행
// 브라우저의 /chap02/v1/~ 시작하는 요청을 처리하는 서블릿
// v1에서는 1개의 서블릿으로 통합시켰다.
@WebServlet("/chap02/v1/*")
public class FrontControllerV1 extends HttpServlet {

    /*
    * /chap02/v1/join : 회원가입 화면 열기 요청
    * /chap02/v1/save : 회원가입 등록 요청
    * /chap02/v1/show : 회원리스트 조회 요청
    */

    // key : 요청 URI, value : 요청에 맞는 하위 컨트롤러 객체
    private Map<String, ControllerV1> controllerMap = new HashMap<>();

    public FrontControllerV1() {
        controllerMap.put("/chap02/v1/join", new JoinController());
        controllerMap.put("/chap02/v1/save", new SaveController());
        controllerMap.put("/chap02/v1/show", new ShowController());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("브라우저 요청이 들어옴!!");

        // 들어온 요청 구분하기 (uri로)
        String uri = req.getRequestURI();
        System.out.println("요청 uri = " + uri);

        // 요청에 맞는 적당한 컨트롤러객체를 맵에서 꺼내기
        ControllerV1 controller = controllerMap.get(uri);

        // 각 객체가 오버라이딩해서 구현한 process 실행
        controller.process(req, resp);
    }
}
  • 나머지 join, save, show에 대한 Controller는 이 전 Servlet들과 동일
  • JoinController
public class JoinController implements ControllerV1 {

    @Override	
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String viewName = "/WEB-INF/views/v1/reg_form.jsp";

        RequestDispatcher dp
                = request.getRequestDispatcher(viewName);
        dp.forward(request, response);

    }
}
  • SaveController
public class SaveController implements ControllerV1{

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String userName = request.getParameter("userName");
        String account = request.getParameter("account");
        String password = request.getParameter("password");

        Member member = new Member(account, password, userName);
        repo.save(member);

        response.sendRedirect("/chap02/v1/show");
    }
}
  • ShowController
public class ShowController implements ControllerV1{

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        List<Member> memberList = repo.findAll();

        request.setAttribute("memberList", memberList);

        String viewName = "/WEB-INF/views/v1/m-list.jsp";

        RequestDispatcher dp = request.getRequestDispatcher(viewName);
        dp.forward(request, response);
    }
}

V2) 요청하는 Servlet을 하나로 통합 + Forward, Redirect 기능을 클래스로 분리

  • FrontControllerV2
// 브라우저의 /chap02/v1/~ 시작하는 요청을 처리하는 서블릿
@WebServlet("/chap02/v2/*")
public class FrontControllerV2 extends HttpServlet {


    // key: 요청 URI, value: 요청에 맞는 하위 컨트롤러 객체
    private Map<String, ControllerV2> controllerMap = new HashMap<>();

    public FrontControllerV2() {
        controllerMap.put("/chap02/v2/join", new JoinController());
        controllerMap.put("/chap02/v2/save", new SaveController());
        controllerMap.put("/chap02/v2/show", new ShowController());
    }


    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("브라우저 요청이 들어옴!!");

        // 들어온 요청 구분하기
        String uri = req.getRequestURI();

        // 요청에 맞는 적당한 컨트롤러객체를 맵에서 꺼내기
        ControllerV2 controller = controllerMap.get(uri);

        View view = controller.process(req, resp);
        view.render(req, resp); // 추가

    }
  • View (JSP파일을 포워딩 하는 역할)
// JSP파일을 포워딩하는 역할
public class View {

    private String viewName; // 포워딩할 뷰의 경로
    private String prefix; // 경로에 있는 공통 접두사
    private String suffix; // 경로에 있는 공통 접미사
    private boolean redirect; // 리다이렉트 여부

    // viewName에 register를 전달하면
    // 완성된 이름은 /WEB-INF/views/register.jsp
    // redirect일 때는 앞에 있는 redirect: 을 제거
    public View(String viewName) {
        this.prefix = "/WEB-INF/views/";
        this.suffix = ".jsp";

        if(!isRedirect(viewName)) {
            this.viewName = prefix +viewName + suffix;
        } else {
            //  uri는 /chap02/v2/show 이기 때문에
            //  redirect:/chap02/v2/show 이면 redirect:를 잘라내야함
            this.viewName = viewName.substring(viewName.indexOf(":") + 1);
        }
    }

    // 포워딩 or 리다이렉트 기능
    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        if(!this.redirect) {
            RequestDispatcher dp = request.getRequestDispatcher(viewName);
            dp.forward(request, response);
        } else {
            response.sendRedirect(viewName);
        }
    }
    // 리다이렉트인지 확인
    private boolean isRedirect(String viewName) {
        this.redirect = viewName.startsWith("redirect:");
        return this.redirect;
    }
}
  • 이렇게 코드를 작성하게되면 Join, Save, ShowController에서 따로 forward, redirect 하지않고 아래코드처럼 View로 경로만 리턴해주면 View에서 알아서 처리해줌!
  • JoinController
public class JoinController implements ControllerV2 {

    @Override
    public View process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        return new View("v2/reg_form");
    }
}
  • SaveController
public class SaveController implements ControllerV2 {

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    @Override
    public View process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String userName = request.getParameter("userName");
        String account = request.getParameter("account");
        String password = request.getParameter("password");

        Member member = new Member(account, password, userName);
        repo.save(member);

//        response.sendRedirect("/chap02/v1/show");

        return new View("redirect:/chap02/v2/show");
    }
}
  • ShowController
public class ShowController implements ControllerV2 {

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    @Override
    public View process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        List<Member> memberList = repo.findAll();
        request.setAttribute("memberList", memberList);

        return new View("v2/m-list");
    }
}

V3) 요청하는 Servlet을 하나로 통합 + Forward, Redirect 기능을 클래스로 분리 + 요청정보(req)나 응답정보(resp)를 처리를 외부로 위임

  • ControllerV3
package com.study.springstudy.webservlet.chap02.v3.controller;

import com.study.springstudy.webservlet.ModelAndView;
import com.study.springstudy.webservlet.View;

import java.util.Map;

/*
*   이 인터페이스를 구현하는 다양한 하위 객체들이
*   요청정보(req)나 응답정보(resp)를 모두가 사용하는 것이 아니기 때문에
*   요청, 응답 정보 처리를 외부로 위임
*/
public interface ControllerV3 {

    /*
    * 요청이 들어오면 적절한 처리를 수행
    * @param paramMap: 요청 정보 (쿼리파라미터) 를 모두 읽어서 맵에 담음
    * @return 응답시 보여줄 화면 처리객체(View)와
    *         화면처리를 위해 사용할 데이터(Model)을
    *         일괄적으로 처리하는 객체인 ModelAndView
    */
    ModelAndView process(Map<String, String> paramMap);
}
  • FrontControllerV3
@WebServlet("/chap02/v3/*")
public class FrontControllerV3 extends HttpServlet {

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

    public FrontControllerV3() {
        controllerMap.put("/chap02/v3/join", new JoinController());
        controllerMap.put("/chap02/v3/save", new SaveController());
        controllerMap.put("/chap02/v3/show", new ShowController());
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 들어온 요청 구분하기
        String uri = req.getRequestURI();

        // 요청에 맞는 적당한 컨트롤러객체를 맵에서 꺼내기
        ControllerV3 controller = controllerMap.get(uri);

        // 요청 파라미터를 전부 읽어서 맵에 담아 리턴하는 메서드 호출
        // 요청 파라미터: 클라이언트가 서버로 전달한 데이터
        // ?name=xfzf&age=30
        Map<String, String> parameterMap = createParamMap(req);

        ModelAndView mv = controller.process(parameterMap);

        // model데이터 jsp로 보내기
        modelToView(req, mv);

        // view 렌더링
        mv.render(req, resp);
    }

    private void modelToView(HttpServletRequest req, ModelAndView mv) {
        Map<String, Object> modelMap = mv.getModel().getModelMap();
        for (String key : modelMap.keySet()) {
            req.setAttribute(key, modelMap.get(key));
        }
    }

    private Map<String, String> createParamMap(HttpServletRequest req) {

        Enumeration<String> parameterNames = req.getParameterNames();

        Map<String, String> paramMap = new HashMap<>();

        // parameterNames가 있는 동안에 paramMap에 key,value를 모두 넣어서 리턴
        while (parameterNames.hasMoreElements()) {
            String key = parameterNames.nextElement();
            String value = req.getParameter(key);
            paramMap.put(key, value);
        }
        return paramMap;
    }
}
  • ShowController
public class ShowController implements ControllerV3 {

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    @Override
    public ModelAndView process(Map<String, String> paramMap) {

        List<Member> memberList = repo.findAll();

        // v1에서 봤듯이 수송객체에서 담아서 forward해주는 역할을 ShowController에서 하는데
        // 수송객체에서 담는 것을 Model에서 forward해주는 것을 View에서 해줄 수 있기 때문에
        // ModelAndView라는 클래스를 만들어서 Model, View역할을 한 번에 해줌
        ModelAndView modelAndView = new ModelAndView("v3/m-list");
        modelAndView.addAttribute("memberList", memberList);

        return modelAndView;
    }
}
  • Model
// 역할: JSP에게 보낼 데이터를 수송하는 역할
public class Model {

    private Map<String, Object> model = new HashMap<>();

    // 데이터를 모델에 추가하는 메서드
    public void addAttribute(String key, Object value) {
        model.put(key, value);
    }

    public Map<String, Object> getModelMap() {
        return model;
    }
}
  • ModelAndView
public class ModelAndView {

    private View view;   // 화면 처리
    private Model model; // 화면에 필요한 데이터 처리

    public ModelAndView(String viewName) {
        this.view = new View(viewName);
    }

    // View의 기능
    // 화면 렌더링 기능 (포워딩 or 리다이렉트)
    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.view.render(request, response);
    }

    // 모델에 JSP가 필요한 데이터를 담는 메서드
    public void addAttribute(String key, Object value) {
        this.model.addAttribute(key, value);
    }

    public View getView() {
        return view;
    }

    public void setView(View view) {
        this.view = view;
    }

    public Model getModel() {
        return model;
    }

    public void setModel(Model model) {
        this.model = model;
    }
}

V4) 요청하는 Servlet을 하나로 통합 + Forward, Redirect 기능을 클래스로 분리 + 요청정보(req)나 응답정보(resp)를 처리를 외부로 위임 + 경로를 문자열로 리턴 (ModelAndView처리를 알아서 해줘)

package com.study.springstudy.webservlet.chap02.v4.controller;

import com.study.springstudy.webservlet.Model;
import com.study.springstudy.webservlet.ModelAndView;

import java.util.Map;

public interface ControllerV4 {

    /**
     * 요청이 들어오면 적절한 처리를 수행
     * @param1 paramMap : 요청 정보 ( 쿼리파라미터 ) 를 모두 읽어서 맵에 담음
     * @param2 model : 응답시 보여줄 JSP에 보낼 데이터를 담는 수송객체
     * @return - 응답시 포워딩하거나 리다이렉트할 경로문자열
     */
    // 경로문자열만 전달해주면 알아서 ModelAndView 과정 해줘
    String process(Map<String, String> paramMap, Model model);
}

V5

  • 스프링을 통해 지금까지 했던 것들을 간편하게 쓸 수 있음
package com.study.springstudy.webservlet.chap02.v5;

import com.study.springstudy.webservlet.MemberMemoryRepo;
import com.study.springstudy.webservlet.entity.Member;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/chap02/v5/*")
public class MemberController {

    private MemberMemoryRepo repo = MemberMemoryRepo.getInstance();

    // 회원등록폼을 열어주는 요청
    @RequestMapping("/join")
    public String join() {
        return "v5/reg_form";
    }

    // 회원 저장을 하는 요청
    @RequestMapping("/save")
    public String save(Member member) {
        repo.save(member);
        return "redirect:/chap02/v5/show";
    }

    // 회원 목록을 조회하는 요청
    @RequestMapping("/show")
    public String show(Model model) {
        List<Member> memberList = repo.findAll();
        model.addAttribute("memberList", memberList);
        return "v5/m-list";
    }

    // 회원 삭제 처리
    @RequestMapping("/delete")
    public String delete(String account) {
        repo.delete(account);
        return "redirect:/chap02/v5/show";
    }

    // 회원 개별조회 처리
    @RequestMapping("/detail")
    public String detail(String account, Model model) {
        Member member = repo.findOne(account);
        model.addAttribute("mem", member);
        return "v5/detail";
    }

}
profile
백엔드 개발자
post-custom-banner

0개의 댓글