ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3. JSP, Servlet을 활용한 MVC 패턴 설계
    Back-end Developer/Spring Framework, 설정 및 실습 2018. 12. 5. 17:27

    저번에 했던 Servlet 개념정리와 이를 이용한 간단한 실습을 통해서,

    오늘은 JSP도 활용하고, 좀 더 체계적인 모양새를 내볼까 합니다. MVC 패턴도 살짝 맛만 볼게요!



    JSP(Java Server Pages) ?


    JSP(Java Server Pages)는  HTML 내에 Java 코드를 삽입하여 web server에서 동적으로 web page를 생성해 web browser에게 전달해 주는 언어입니다. 이는 서버측 스크립트로서 서블릿 코드를 작성하는 편리한 방법을 제공합니다.


    • WAS에서 동작하는 Java EE 스펙
    • Servlet은 Java와 html 태그가 섞여있어 분리적인 측면에서 프로그래밍 효율이 떨어져 이를 보완하기 위해 나온것이 JSP
    • 하지만, JSP 또한 로직과 디자인이 한 파일 내에 섞여있어 유지 보수가 어렵다.
    HTML 표준에 의해 작성되므로 웹 디자인에 용이한 홈페이지나 웹 사이트를 만들 수 있는 파일

    개념적인 부분만 봐도 JSP는 어느정도 감이 오실 것 같아요.

    HTML 태그를 사용해신 분들이라면 이후 해 볼 실습에서 더 친근하게 느끼실 수 있을겁니다.



    MVC pattern(Model, View, Controller) ?


    MVC pattern(Model, View, Controller)은 소프트웨어 디자인 패턴입니다. 

    Model: business 영역의 logic을 처리

    View: business 영역에 대한 presentation view(사용자가 보게 될 결과 화면)을 담당

    Controller: 사용자의 입력 처리와  흐름 제어 담당


    이 패턴에는 2가지 모델이 존재합니다.

    모델 1: JSP에서 출력과 로직을 전부처리하는 경우

    모델 2: JSP에서 출력만 처리하는 경우



    각 모델의 장, 단점

    • Model 1
      • 장점
      • 개발 속도가 빠르고, 구조가 간단해 진입 장벽이 낮다.
      • 중, 소형 프로젝트에 적합하다.
      • 단점
      • 디자인(표현 코드)과 로직이 하나의 JSP 페이지에 표현되므로 코드가 복잡하다. (위에서 설명한 JSP의 단점이 바로 이것이었죠.)
      • 그렇기 때문에 개발자와 디자이너의 분업도 쉽지 않다.
      • 코드의 재 사용성, 유지보수가 매우 떨어진다.

    • Model 2
      • 장점
      • 출력을 위한 뷰 코드와 로직 처리를 위한 자바 코드를 분리해 코드가 복잡하지 않다.
      • 뷰, 로직의 역할 분담이 수월해진다.
      • 기능에 따라 분리가 되어있기 때문에 유지보수, 재 사용성, 확장성이 좋다.
      • 단점
      • 구조가 복잡해 배우기 어렵고 작업량이 많다.
      • 자바 코드에 비해 비교적 높은 이해도가 요구된다.
      • 구조 설계를 위한 시간이 많이 소요된다.
    Model1 과 달리 Model2는 웹브라우저의 사용자 요청을 Servlet(Controller)가 받고 Servlet은 요청을 받아 View로 보여줄 것인지 Model로 보여줄 것인지 정하여 전송합니다.

    MVC 패턴은 정말 중요한 내용이죠. 애매하다 싶으시면 꼭 더 찾아보시고 공부해보세요. 따라하진 않더라도 누군가 설계하는 코드나 강의 등도 보시면 어느정도 개념이 잡힐 것 같습니다. ^___^




    실습


    이제 실습으로 들어가볼게요.



    프로젝트 생성


    이전처럼 Dynamic Web Project를 helloMVC라는 이름으로 하나 생성해주세요. 이 방법은 어렵지 않으니 따로 쓰지 않을게요.

    기억이 나지 않으시면 여기의 실습을 참고해주세요.

    만들어지면 해당 프로젝트의 properties -> Web Project Settings를 선택하면 아래와 같이 뜹니다.



    Context root가 helloMVC라고 적혀있는데, 이 application은 tomcat에서 helloMVC라는 이름으로 실행된다. 라는 것을 의미합니다.

    어쨌든 이렇게 제대로 루트가 잡혀있는지 확인하셨으면 닫아주세요~



    JSP 파일 생성 (View)


    다음으로 WebContent에서 JSP를 index라는 이름으로 하나 생성할게요.

    그리고 아래와 같이 코드를 입력해 줍시다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>helloMVC Web Application</title>
    </head>
    <body>
        
        This is the home page for shopping
        
        <p> <a href = "/helloMVC/home?action=login"> go to login page </a></p>
        <p> <a href = "/helloMVC/home?action=help"> go to help  page </a></p>
        
    </body>
    </html>
    cs


    title 지정하고, body는 p태그 부분만 동일하게 작성해 주시면 됩니다.

    읽어보시면 login, help 페이지로 이동하는 링크를 걸어 두었다는 것은 다들 아실 겁니다.

    사실 우리가 두 페이지를 만들진 않았기 때문에, 실행시 클릭하면 404가 발생하겠죠.. ㅎㅎ


    그냥 시험삼아 실행 한번 해볼게요!


    역시 문제없이 잘 됩니다. 방금 말씀드린대로 링크 누르시면 404 발생합니다.

    우린 스마트한 사람이기 때문에 놀라지 마시고 그냥 단호하게 종료 누르시면 됩니다.



    Servlet 파일 생성 (Controller)


    또한 스마트한 우리에게 404는 참 거슬립니다. 따라서 이제부터 login과 help에 해당하는 기능을 해줄 Servlet을 만들어 볼게요.

    위에서 써두었다시피 Servlet은 Controller에 해당하니까요. Controller 패키지를 하나 만들어주세요.

    Java Resources -> src -> controller로 패키지 생성.


    그리고 controller 패키지에서 Home servlet 클래스를 하나 생성해주세요.



    URL mapping은 소문자로 지정해야하니까 '/Home'를 더블 클릭해 아래와 같이 수정해주세요.


    Next 누르시고, 다음에 우린 JSP에서 method 명시를 하지 않았잖아요? 그럼 get method가 실행될테니까 doGet만 체크해주세요.



    다음으로 home servlet의 doGet 메소드를 구현 해봤습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String action = request.getParameter("action");
        String page = null;
            
        if(action.equals("login")) {
            page = "/view/loginform.jsp";
        }
            
        else if(action.equals("help")) {
            page = "/view/help.jsp";
        }
            
        else {
            page = "/view/error.jsp";
        }
            
        RequestDispatcher dispatcher = request.getRequestDispatcher(page);
        dispatcher.forward(request, response);
    }
    cs


    위의 코드는 대부분 저번 포스트를 보셨다면 이해가 되실텐데요. 간단하게 설명드리면

    1. 'action'이라 정의된 문자열로 사용자가 누른 값(request)이 넘어옵니다. (login or help)
    2. action의 값에 따라 조건문을 나누고, 알맞는 페이지로 이동시켜줍니다.
    3. 단, 잘못된 값이 넘어온 경우 error 페이지로 이동합니다.
    해당 조건문의 url에 대한 페이지는 아직 만들진 않았기 때문에, 따로 이동은 못 할 겁니다.
    해당 페이지로 forwarding 시키기 위해선 RequestDispatcher라는 것을 필요로 합니다.
    따라서 요청된 페이지 값을 dispatcher에 저장하고, forwarding 시키면 해당 request에 대한 response가 발생하는것이죠.

    여기까지 완료했으면 실행해서 잘 만들어졌는지 확인해 봅시다.



    우선 여기는 별 차이가 없네요? 뭘 보고 놀래시는 겁니까. 이것은 이전에 실행한 프로그램의 잔상입니다만?

    네 아니구요. 우리가 방금 실행한 프로그램이 맞겠죠. 왜냐면 이 부분(index.jsp)에 대하여 건든건 없으니까요 ㅎㅎ

    'go to login page'를 눌러보시면 여전히 404가 발생하죠.

    하지만, 중요한 것은 경로가  우리가 설정한대로 붙어있죠. message: /helloMVC/view/loginform.jsp 이렇게요.

    성공했다!고 보시면됩니다.


    이제 불편한 404가 또 생겨버렸으니, 이를 위해 loginform, help, error에 대한 페이지도 만들어줄게요!!

    우선 view 패키지부터 만들게요. WebContent -> 새 폴더를 생성해주세요 이름은 view로 지정!

    view -> new -> JSP File 누르고 loginform, help, error 페이지를 하나씩 생성해 주세요.


    각 페이지를 만드셨으면 body 태그에 아무말이나 입력해보세요. (저는 This is "page name" page. 라고 써놨어요.)

    이제 실행하고 go to login page를 눌러 보시면 아래와 같이 보이게됩니다. help 페이지도 마찬가지로 잘 뜹니다.



    ++


    저렇게 그냥 둬도 사실 상관은 없겠지만 좀 아쉽잖아요? (아쉽지 않다면 다음에 봬요...ㅠㅠ) 

    그래서 로그인 기능은 아니더라도 비슷하게 아래와 같이 구현을 해볼게요.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
     
        <form action = "/helloMVC/doLogin" method = "get">
            Customer ID: 
            <input type = "text" name = "customerId" /> <br>
            <input type = "submit" value = "press" />
        </form>
        
    </body>
    </html>
    cs



    이렇게 쓰시고 실행하면, 밑의 이미지 처럼 나옵니다.


    여기서 아이디를 치고 press를 누르면! 또 404 발생하겠죠?

    위의 JSP에서 doLogin으로 get method를 통해 이동한다고 써 놨는데, 애초에 우리에겐 doLogin이 없잖아요... ㅇㅅㅇ;;


    URL이 helloMVC/helloMVC/doLogin -> 이런식으로 두번 뜨는경우 경로가 상대경로로 지정되었기 때문입니다.

    loginform.jsp 에서 action = "/helloMVC/doLogin/" 과 같이 맨 앞에 '/'를 빼먹지 않았는지 확인해주세요.



    그럼 이제 DoLogin Servlet을 만들어볼게요.

    controller 패키지에 'DoLogin'으로 서블릿 하나를 생성해주세요~

    (당연하게도 url mapping은 우리가 지정한대로 doLogin으로 변경해주시구요! method도 doGet만 체크해주세요!!)


    순차적으로 하나씩 만들어볼게요. 일단 doGet method에서 Customer Id를 읽어오는 작업부터 만들어 봅시다.

    단, 원래는 CustomerId를 request method를 통해 받아오고, 이를 통해 DB로 접근해 관련 정보를 가져오는 방식으로 해야하지만,

    우린 아직 DB를 제대로 구성하지 않았기 때문에 Map 자료구조를 통해 약식으로나마 만들어서 사용해 보겠습니다.

    전체적으로 보면 bean을 반환해주는 방식으로 구현할건데요, 이 bean에는 Customer 정보가 들어있겠죠!   

    (bean은 아래 실습을 통해 보여드릴게요)


    DoLogin의 doGet method 구현 부분입니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
    */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String customerId = request.getParameter("customerId");
            
        // perform bussiness login return a bean as a result
            
        String page = null;
            
        if(customer == null) {
            page = "/view/error.jsp";
        }
        else {
            page = "/view/success.jsp"
        }
            
        RequestDispatcher dispatcher = request.getRequestDispatcher(page);
        dispatcher.forward(request, response);
    }
    cs


    우선 이렇게만 쓸게요, 당연히 customer 변수를 정의하지 않았기 때문에 빨간줄이 뜨겠지만, 

    아직 변수를 정의해 가져올 bean을 만들지 않았기때문에, 그냥 둘게요. 나머지 코드는 Home servlet과 유사하기 때문에 생략할게요!

    주석처리된 'perform bussiness logic' 여기에서는 이후에 bean을 생성하고 할 행동들을 쓸 예정입니다.



    Class 파일 생성 (Model)


    이제 customer 객체가 필요하니 customer의 데이터 정보를 저장 할 Model을 생성 해볼게요.

    Java Resources -> src -> model로 패키지 생성해주시고 해당 패키지에 Customer로 클래스를 하나 생성할게요.

    여기까지 해서 최종적으로 MVC 모델의 요소가 모두 등장하게 되었네요. ㅎㅅㅎ


    클래스 변수로 id, name, email을 만들어주세요. 변수 타입은 String, 접근 한정자는 private으로 선언합니다.

    이제 이 변수에대한 생성자를 만들어볼게요. 직접 치셔도 무관하고, 귀찮으시면 저와 같이 하시면됩니다.

    코드를 친 현 페이지에서 마우스 오른쪽 -> source -> generate Constructor using field 선택! 하시면 이와 같은 창이 뜹니다.


    세개 모두 체크해주시고, 'Generate'를 누르면 완성됩니다.


    이렇게 완성된 class를 bean이라고 합니다. 아 그리고 같은 방법으로 generate getter, setter를 통해 get, set method도 만들어주세요.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package model;
     
    import lombok.Getter;
    import lombok.Setter;
     
    @Getter
    @Setter
    public class Customer {
        private String id;
        private String name;
        private String email;
        
        public Customer(String id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }
     
    //    public String getId() {
    //        return id;
    //    }
    //
    //    public void setId(String id) {
    //        this.id = id;
    //    }
    //
    //    public String getName() {
    //        return name;
    //    }
    //
    //    public void setName(String name) {
    //        this.name = name;
    //    }
    //
    //    public String getEmail() {
    //        return email;
    //    }
    //
    //    public void setEmail(String email) {
    //        this.email = email;
    //    }
    }
     
    cs


    근데 제 코드는 조금 다르죠? 사실 get, set method는 너무 코드길이가 길어지기 때문에 저렇게 Annotation(@)으로 표현이 가능합니다.

    이를 가능하게 해주는 것이 Lombok 입니다.



    Lombok 설치 및 의존성 추가


    Lombok 이란?

    자바에서 DTO, VO, Domain Object 생성 시 멤버필드, get / set method, 멤버필드의 생성자 코드 등 기계적으로 만드는 코드에 대해 annotation을 통해 코드 작성량과 길이를 줄여주는 Library.



    lombok 검색하셔서 설치하시고 열면 이런 창이 뜹니다.

    원래는 IDEs에 해당 eclipse가 감지되어야 하는데, 저는 안되는거같아요.. 되는 분들은 Install / Update 눌러서 바로 실행해주세요.

    저처럼 안뜨시는 분들은 'Specify location...'으로 들어가서 eclipse.ini이라는 파일을 찾아 경로를 따주시면됩니다.


    이제 pom.xml에 의존성 코드를 넣던지 추가를 해주면되는데... 이제보니 maven 프로젝트가 아니네요!! (이럴수가...)

    프로젝트 파일 이름에 마우스 오른쪽 -> configure -> convert to maven project 로 프로젝트를 전환해주겠습니다.


    좌측 파일 트리를 보시면 pom.xml이 생성된것이 보이죠? 의존성 추가를 해볼게요 열어주세요!

    페이지와 콘솔창 사이에 dependencies라고 쓰여있는 글자를 눌러 해당 페이지를 열어보시면, 이렇게 뜹니다.


    파랗게 떠있는 빈칸에 lombok이라고 검색해서 뜬다면 상관이없지만, 안뜨면 위의 3칸을 직접 채우셔야합니다.

    • Group Id: org.projectlombok
    • Artifact Id: lombok
    • Version: 1.18.2 (제 기준 최신이므로 최신을 받으시던 이 버전을 받으시던 본인이 원하시는거 받으시면 됩니다.)

    이렇게 입력해주시거나 찾아서 선택하고 OK를 눌러서 저장하면 설치가 진행되니까 꼭 저장을 해주세요.(ctrl + s)

    설치가 완료되면 이렇게 좌측 트리의 Libraries -> Maven Dependencies에 lombok이 추가됩니다!! ^____^/



    Lombok을 통해 해당코드를 저처럼 간소화 시키실 수 있습니다. get, set 말고도 여려 기능이있으니 찾아보며 사용해보세요!

    (아 import 꼭 해주시구요.)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package model;
     
    import lombok.Getter;
    import lombok.Setter;
     
    @Getter
    @Setter
    public class Customer {
        private String id;
        private String name;
        private String email;
        
        public Customer(String id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }
    }
     
    cs



    이제 Service를 만들게요. 데이터를 받아서 Map에 저장하고, 맵을 조회해 알맞은 정보를 반환해주는 역할을 할겁니다.

    다른 패키지들과 같은 경로에 service로 패키지 하나 생성해주세요. 그리고 해당 패키지에 CustomerService 클래스도 하나 생성합시다.


    Map을 하나 선언하고, 아까 했던대로 생성자도 하나 만듭니다.

    그리고 Customer의 정보를 추가해야하니까 addCustomer라는 method를 만들어서 수동으로 몇개 추가시켜주세요.

    그리고 저장된 회원을 조회도 해야하니까, findCustomer method도 하나 만들게요.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package service;
     
    import java.util.HashMap;
    import java.util.Map;
     
    import model.Customer;
     
    public class CustomerService {
        private Map<String, Customer> customers;
     
        public CustomerService() {
            customers = new HashMap<String, Customer>();
            
            addCustomer(new Customer("id001""Alice""alice.snut.ac.kr"));
            addCustomer(new Customer("id002""Bob""bob.snut.ac.kr"));
            addCustomer(new Customer("id003""Charlie""charlie.snut.ac.kr"));
            addCustomer(new Customer("id004""David""david.snut.ac.kr"));
            addCustomer(new Customer("id005""Trudy""trudy.snut.ac.kr"));
        }
            
        public void addCustomer(Customer customer) {
            customers.put(customer.getId(), customer);
        }
        
        public Customer findCustomer(String id) {
            if(id != null) {
                return customers.get(id.toLowerCase());
            }
            
            else {
                return null;
            }
        }
    }
     
    cs


    이런식으로 하시면 됩니다.

    Map value에는 Customer 클래스가 담겨있습니다. 따라서 id, name, email 아까 우리가 만든 생성자 순으로 데이터를 받구요.

    Key는 id가 들어가는데 DB에서 인덱스같은 기능을 위해 약식으로 하나 만들어봤어요.


    find method에서는 id가 존재하지 않으면 null을 존재하면 해당 id를 모두 소문자로 반환해주도록 했습니다.

    그냥 뽑아줘도 상관없는데 그냥 통일시키는게 이쁠거 같아서 저렇게 했어요.



    find까지 완료했으니 DoLogin으로 다시 넘어가서 진행해 봅시다.

    DoLogin 클래스에서 Customer 객체를 받아와서 id를 통해 Customer의 정보를 찾아온 결과를 customer 변수에 저장하고,
    이것을 Attribute에 넣어서 요청을 처리할 수 있도록 설정합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String customerId = request.getParameter("customerId");
            
        
        CustomerService service = new CustomerService();
        Customer customer = service.findCustomer(customerId);
        request.setAttribute("customer", customer);
            
        String page = null;
            
        if(customer == null) {
            page = "/view/error.jsp";
        }
        else {
            page = "/view/success.jsp"
        }
            
        RequestDispatcher dispatcher = request.getRequestDispatcher(page);
        dispatcher.forward(request, response);
    }
     
    cs


    완성된 모습입니다.



    View++


    여기까지 구현하고 로그인 처리가 잘 되었는지 확인 시켜줄 success.jsp 하나를 만들어 볼게요.

    view 폴더에서 새로운 JSP를 success로 만들어주세요.

    여기 페이지에선 로그인 후 성공했다면, 그 사람의 id, name, email을 띄워주는 기능을 구현해보겠습니다.

    우선 값을 받아오려면 bean에 저장된 데이터를 가져와야하는데... 일반적인 방법으론 안될거같아요.


    따라서, JSP Expression Language를 이용해 접근해보겠습니다.

    ${customer.id}: 이렇게 써주면, JSP를 이용해 해당 bean의 id를 읽어오겠다는 뜻이됩니다. 이름과 이메일도 똑같이 만들어주세요!


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
     
    <body>
        <%-- JSP Expression Language --%>
        <ul>
            <li>Id: ${customer.id }</li>
            <li>Name: ${customer.name }</li>
            <li>E-mail: ${customer.email }</li>
        </ul>
    </body>
    </html>
    cs



    실행 결과


    이렇게 하셨으면 완성 하신거에요.

    완성되었으니 확인 작업 한번 해볼게요.

    실행 -> go to login page -> 아까 추가한 아이디중 하나 id001~5 입력 -> press (저는 id004를 입력해 봤습니다.)


    결과



    이렇게 다 되었습니다. 아 error 페이지로 넘어갔을때 그냥 끝내버리면 너무 정 없으니까.

    Id를 잘못입력시에 error로 넘어가면 다시 시도할 수 있도록 Main 페이지로 다시 올 수 있는 링크만 하나 더 만들어 보세요!



    결과는 이런식으로 나오겠죠. 이제 잘못된 아이디를 입력해도 다시 시도할 수 있게 되었네요. \^___^/



    사실상 그렇게 어려운건 없었지만.. 중요한 개념들이 많이 있었어요. 복습을 오지게 해야겠네요. 후후



    반응형

    댓글

Designed by minchoba.