minghxx.blog
  • [Spring] 스프링 MVC 1편 3) 서블릿, JSP, MVC 패턴
    2023년 11월 02일 22시 37분 51초에 업로드 된 글입니다.
    작성자: 민발자
    728x90

    스프링 MVC 1편 백엔드 웹 개발 핵심 기술 

    Session 3 서블릿, JSP, MVC 패턴

    1. 회원 관리 웹 애플리케이션 요구사항

    1) 회원 도메인 모델

    회원 정보 : 이름(username), 나이(age)

    기능 요구사항 : 회원저장, 회원 목록 조회


    2. 서블릿으로 회원 관리 웹 애플리케이션 만들기 

    1) MemberFormServlet 회원 등록 폼

    단순하게 회원 정보를 입력할 수 있는 html form 만들어 응답

     

    2) MemberSaveServlet 회원 저장

    파라미터를 조회해 Member 객체를 만든다.

    Member 객체를 MemberRepository를 통해 저장

    Member 객체를 사용해서 결과 화면용 html을 동적으로 만들어 응답

     

    3) MemberListServlet 회원 목록

    MemberRepository의 findAll() 메서드 통해 모든 회원 조회

    회원 목록 html을 for 루프를 통해 회원 수만큼 동적으로 생성하고 응답

    for (Member member : members) {
        w.write("    <tr>");
        w.write("        <td>" + member.getId() + "</td>");
        w.write("        <td>" + member.getUsername() + "</td>");
        w.write("        <td>" + member.getAge() + "</td>");
        w.write("    </tr>");
    }

     

    4) 템플릿 엔진

    서블릿 덕분에 동적으로 원하는 HTML을 만들수 있음.

    동적인 HTML을 만들기가 어렵고 코드를 보면 매우 복잡하고 비효율적이다 ▶ 템플릿엔진을 사용해 해결

    템플릿 엔진을 사용하면 HTML문서에 필요한 곳만 코드를 적용해 동적으로 변경 가능

    JSP, Thymeleaf 등이 있다.


    3. JSP로 회원 관리 웹 애플리케이션 만들기

    1) JSP 라이브러리 추가

    build.gradle 추가

    //JSP 추가 시작
    implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
    implementation 'jakarta.servlet:jakarta.servlet-api' //스프링부트 3.0 이상 
    implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' //스프링부트 3.0 이상
    implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl' //스프링부트 3.0 이상 
    //JSP 추가 끝

     

    2) 회원 등록 폼 JSP 

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
        <head>
          <title>Title</title>
        </head>
        <body>
            <form action="/jsp/members/save.jsp" method="post">
              username: <input type="text" name="username" />
              age: <input type="text" name="age" /> 
              <button type="submit">전송</button>
            </form>
        </body>
    </html>

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>

    ▶ jsp문서라는 의미

    첫줄을 제외하고 HTML문서와 동일

    jsp는 서버 내부에서 서블릿으로 변환

    ▶ MemberFormServlet과 거의 비슷한 모습으로 변환된다.

     

    3) 회원 저장 jsp

    <%@ page import="hello.servlet.domain.member.MemberRepository" %>
    <%@ page import="hello.servlet.domain.member.Member" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%
        //request, response 사용 가능
        MemberRepository memberRepository = MemberRepository.getInstance();
        System.out.println("save.jsp");
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        Member member = new Member(username, age);
        System.out.println("member = " + member);
        memberRepository.save(member);
    %>
    <html>
        <head>
          <meta charset="UTF-8">
        </head>
        <body> 성공
            <ul>
              <li>id=<%=member.getId()%></li>
              <li>username=<%=member.getUsername()%></li>
              <li>age=<%=member.getAge()%></li>
            </ul>
            <a href="/index.html">메인</a>
        </body>
    </html>

     

    <%@ page import="hello.servlet.domain.member.Member" %> ▶ 자바의 import문과 동일

    <% %> ▶ 자바 코드 입력

    <%= %> ▶ 자바 코드 출력

     

    4) 회원 목록 jsp

    ...
    <%
        MemberRepository memberRepository = MemberRepository.getInstance();
        List<Member> members = memberRepository.findAll();
    %>
    
    ...
    
                <tbody>
                    <%
                        for (Member member : members) {
                            out.write("    <tr>");
                            out.write("     <td>" + member.getId() + "</td>");
                            out.write("     <td>" + member.getUsername() + "</td>");
                            out.write("     <td>" + member.getAge() + "</td>");
                            out.write("    </tr>");
                        }
                    %>
                </tbody>
         
    ...

    결과 list를 <tr><td> 태그 사용해 반복 출력

     

    5) 서블릿과 jsp의 한계

    서블릿으로 개발할 때는 뷰 화면을 위한 html 작업이 자바 코드와 섞여 복잡

    jsp를 사용하면 뷰 화면을 위한 html작업을 깔끔해지고 중간 동적으로 변경이 필요한 부분만 자바 코드를 적용

    자바 코드, 데이터 조회하는 리포지토리 등 코드가 jsp에 모두 노출되어 있음

    ▶ jsp가 너무 많은 역할

     

    6) mvc 패턴의 등장

    비즈니스 로직은 서블릿처럼 다른 곳에서 처리하고 jsp는 목적에 맞게 뷰를 그리는 일에 집중


    4. MVC 패턴 개요

    1) 너무 많은 역할

    하나의 서블릿이나 jsp만으로 비즈니스 로직과 뷰 렌더링까지 모두 처리 많은 역할, 유지보수 어려움

    비즈니스 로직을 호출하는 부분에 변경이 발생하면 해당 코드 수정필요, ui 수정 시에도 비즈니스 로직이 있는 해당 파일을 수정

     

    2) 변경의 라이프 사이클

    둘 사이의 변경 라이프 사이클이 다르다.

    ui일부 수정, 비즈니스 로직 수정은 각각 다르게 발생할 확률 높고 서로에게 영향을 주지 않는다.

    변경의 라이프 사이클이 다른 부분을 하나의 코드로 관리하는 것은 유지보수에 어려움이 많다.

     

    3) 기능 특화

    jsp 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화되어 있기 때문에 이 부분의 업무만 담담하는 것이 효과적

     

    4) Model View Controller

    하나의 서블릿이나 jsp로 처리하던 것을 컨트롤러와 뷰라는 영역으로 서로 역할을 나눈 것

    컨트롤러 : HTTP 요청을 받아 파라미터를 검증하고 비즈니스 로직을 실행, 뷰에 전달할 결과 데이터를 조회해 모델에 담는다.

    모델 : 뷰에 출력할 데이터를 담아둔다. 뷰가 필요한 데이터를 모두 모델에 담아서 전달, 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고 화면 렌더링에만 집중

    뷰 : 모델에 담겨있는 데이터를 사용해 화면을 그리는 일에만 집중, html을 생성하는 부분

    서비스 : 컨트롤러에 비즈니스 로직을 두면 많은 역할을 담당, 일반적으로 비즈니스 로직은 서비스 계층을 별로로 만들어 처리, 참고로 비즈니스 로직을 변경하면 비즈니스 로직을 호출하는 컨트롤러의 코드도 변경될 수 있다.

     


    5.  MVC 패턴 적용

    1) mvc 패턴 적용

    서블릿을 컨트롤러로 사용하고 jsp를 뷰로 사용해서 mvc 패턴을 적용

    Model은 HttpServletRequest 객체를 사용

    request는 내부에 데이터 저장소를 가지고 있는데, request.setAttribute(), request.getAttribute()를 사용하면 데이터를 보관하고 조회가능

     

    2) 회원등록 폼 컨트롤러

    @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); // jsp로 이동
    }

    dispatcher.forward() 다른 서블릿이나 jsp로 이동할 수 있는 기능, 서버 내부에서 다시 호출 발생

    /WEB-INF 이 경로 안에 있으면 외부에서 직접 호출이 불가능, 컨트롤러를 이용해 jsp를 호출

     

    2) redirect vs forward

    리다이렉트는 실제 클라이언트에 응답이 갔다가 클라이언트가 리다이렉트 경로로 다시 요청, 클라이언트가 인지 가능, URL 경로도 변경

    포워드는 서버 내부에서 일어나는 호출로 클라이언트가 알 수 없다.

     

    3) 회원 등록 폼 뷰

    <!-- 상대경로 사용, [현재 URL이 속한 계층 경로 + /save] --> 
    <form action="save" method="post">
        username: <input type="text" name="username" /> 
        age: <input type="text" name="age" /> 
        <button type="submit">전송</button>
    </form>

    save 재사용 위해 상대경로 사용!

    상대경로 사용 시 현재 URL이 속한 계층 + save가 호출된다 → /servlet-mvc/members/save

     

    4) 회원 목록 조회 뷰

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    
    ...
    
       <c:forEach var="item" items="${members}">
            <tr>
                <td>${item.id}</td>
                <td>${item.username}</td>
                <td>${item.age}</td>
            </tr>
        </c:forEach>
    ...

     

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> ▶ jstl을 사용하기 위해 필요

    <c:forEach> 기능을 사용해 list 출력


    6. MVC 패턴 한계

    1) mvc 컨트롤러의 단점

    -forward 중복

    -viewPath 중복

    jsp를 다른 뷰로 변경한다면 전체 코드 다 변경 필요

    -사용하지 않는 코드

    reponse는 현재 코드에서 사용되지 않는다.

    HttpServletRequest, HttpServletResponse를 사용하는 코드는 테스트 케이스 작성도 어려움

    -공통 처리가 어려움

    기능이 복잡할수록 공통처리해야 할 부분이 많아짐, 공통 기능을 메서드로 만들어도 될 것 같지만 메서드를 항상 호출해야 함

    호출 자체도 중복이다.

     

    2) 공통 처리가 어렵다

    컨트롤러 호출 전에 먼저 공통 기능을 처리해야 함 프론트 컨트롤러 패턴 도입

     

    728x90
    댓글