Web/...

서블릿 MVC

Jinn 2023. 10. 18. 01:18

이전글

 

Servlet과 JSP

애플리케이션 요구사항 회원 정보 이름(username) 나이(age) 기능 요구사항 회원 저장 회원 조회 회원 도메인 @Getter @Setter @NoArgsConstructor public class Member { private Long id; private String username; private int age;

pressky99.tistory.com

 

MVC 패턴

여기서는 request 객체의 내부 저장소를 모델로 사용한다.

 

회원 등록 폼 - 컨트롤러

@WebServlet(name = "mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/new-form")
public class MvcMemberFormServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

/servlet-mvc/members/new-form을 호출하면 /WEB-INF/views/new-form.jsp로 forward한다.

/WEB-INF에 있는 JSP는 외부에서 직접 호출할 수 없고 컨트롤러를 통해서 호출할 수 있다.

 

 

회원 등록 폼 - 뷰

<!-- main/webapp/WEB-INF/views/new-form.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--
    상대경로 사용, [현재 URL이 속한 계층 경로 + /save]
    /servlet-mvc/members/save
-->
<form action="save" method="post">
    username: <input type="text" name="username" />
    age: <input type="text" name="age" />
    <button type="submit">전송</button>
</form>
</body>
</html>

forward를 했기 때문에 현재 URL은 servlet-mvc/members/new-form이다.

 

 

회원 저장 - 컨트롤러

@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();
    
    @Override
    protected void service(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);
    }
}

HttpServletRequest 객체를 Model 객체로 사용한다. request는 내부에 데이터 저장소가 있어서 request.setAttribute(), request.getAttribute()를 사용하여 데이터를 보관하고 조회할 수 있다.

 

 

회원 저장 - 뷰

<!-- main/webapp/WEB-INF/views/save-result.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
성공
<ul>
    <!-- <%= request.getAttribute("member") %> -->
    <li>id=${member.id}</li>
    <li>username=${member.username}</li>
    <li>age=${member.age}</li>
</ul>
</body>
</html>

request.getAttribute() 대신 JSP의 ${}를 사용해서 더 깔끔하게 조회할 수 있다.

 

 

회원 목록 - 컨트롤러

@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();
    
    @Override
    protected void service(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);
    }
}

 

 

회원 목록 - 뷰

<!-- main/webapp/WEB-INF/views/members.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table>
    <thead>
    <th>id</th>
    <th>username</th>
    <th>age</th>
    </thead>
    <tbody>
    <c:forEach var="item" items="${members}">
        <tr>
            <td>${item.id}</td>
            <td>${item.username}</td>
            <td>${item.age}</td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

JSP가 제공하는 taglib 기능을 사용해서 members를 출력한다.

 

 

 

 

이전 글에서 서블릿이나 JSP만으로 작성한 코드를 서블릿(컨트롤러)과 JSP(뷰)로 MVC 패턴을 적용했다.

그러나 컨트롤러에서 같은 코드가 중복되는 문제가 있다.

// 중복 코드
String viewPath = "...";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

프론트 컨트롤러를 도입하여 이를 해결할 수 있다.

 

프론트 컨트롤러 도입 전/후

 

프론트 컨트롤러를 도입한 구조

 

MyView

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 객체가 처리한다.

넘어온 viewPath로 render() 메서드가 포워드한다.

 

 

Controller

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

프론트 컨트롤러에서 여러 컨트롤러를 모두 다룰 수 있도록 다형성을 위해 인터페이스로 작성한다.

 

 

회원 등록 - 컨트롤러

public class MemberFormController implements Controller {

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

 

 

회원 저장 - 컨트롤러

public class MemberSaveController implements Controller {

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

 

 

회원 목록 - 컨트롤러

public class MemberListController implements Controller {

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

 

 

프론트 컨트롤러

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

    private Map<String, Controller> controllerMap = new HashMap<>();
    
    public FrontControllerServlet() {
        controllerMap.put("/front-controller/members/new-form", new MemberFormController());
        controllerMap.put("/front-controller/members/save", new MemberSaveController());
        controllerMap.put("/front-controller/members", new MemberListController());
    }
    
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        Controller controller = controllerMap.get(requestURI);
        
        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        MyView view = controller.process(request, response);
        view.render(request, response);
    }
}

 

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의 (inflearn.com)