목차

sun cloud


복습

jsp 내용을 작성할 당시 상속 관계에 대한 설명이 부족했던 것 같아 이 부분을 복습한 뒤 오늘 주제인 Singleton 패턴에 대해 공부하도록 하겠습니다.


jsp는 WAS(Web Container)에 의해 java로 생성되고 컴파일되어 실행된다고 설명을 드렸었습니다.

그 과정을 살펴보면 다음 그림과 같습니다.

image

jspHttpJspBase라는 클래스를 상속받고, HttpJspBase -> HttpServlet-> GenericServlet, 최종적으로 Servlet interface를 implements 하는 구조입니다.

위와 같은 구조는 interface로 다양한 계층 구조를 형성하고 표준화에 기여하며 abstract로 재사용성을 높일 수 있습니다.


복습은 간단히 마치도록 하고 오늘의 핵심 내용인 싱글톤 패턴을 배워보겠습니다.




Singleton Design Pattern

: 시스템 상에서 단 한 번 객체를 생성해서 여러 곳에 공유해 사용하고자 할 때 적용하는 설계 패턴입니다. (참고. spring framework에서의 기본 객체 운용방식이 singleton 방식)


객체를 한 번만 생성한 뒤 공유해서 사용하면 서버 자원의 효율을 높일 수 있으며 객체 생성 시간 또한 줄어들게 됩니다. 그래서 보통 시스템 주요 컴포넌트 중 신속성이 필요한 객체들에게 singleton 적용한다고 합니다.


그러면 어떻게 객체를 단 한 번만 생성하게하고 이를 공유할 수 있을까요?

Model.DAO object를 한 번 생성해서 각각의 기능에 재사용되도록 자원을 공유해보는 연습을 하겠습니다.


먼저 필요한 작업은 다른 클래스에서 new ()로 재생성을 못하게 막는 것 입니다.

  • private DAO(){}: 생성자 앞에 private 접근제어자를 적용함으로써 외부에서의 객체 생성(new)을 막을 수 있습니다.

다른 클래스에서의 생성을 막았으니 공유할 수 있는 객체를 자기 자신의 클래스에서 생성해야 합니다.

  • private static DAO dao = new DAO();: static 멤버변수로 자신의 객체를 생성합니다. (클래스 로딩 시점에 자신의 생성자를 이용해 객체를 단 한 번 생성하고, meta space에 주소값을 저장합니다)

마지막으로 생성한 객체를 외부에서 사용할 수 있도록 객체를 반환받는 메서드를 작성합니다.

  • public static method(){}: static method로 외부에 공유해 여러 곳에서 사용할 수 있도록 합니다(static 변수에 저장된 객체의 주소값을 반환하여 사용하게 합니다)


위와 같이 싱글톤 패턴을 적용했다면 싱글톤 적용한 클래스명.getInstance().클래스 내에 작성한 메서드명();과 같이 사용할 수 있습니다.




MVC Singleton 예제

예제를 진행하겠습니다.

전 수업에서 진행했던 것과 유사한 예제이며, 전반적으로 같은 유형에 Singleton 패턴을 적용했습니다.

만들어야할 파일들과 구조는 다음과 같습니다.

image

java 파일이 Model, jsp가 View, Servlet이 Controller 역할을 담당하도록 합니다.

아래의 두 가지 기능을 구현해보도록 하겠습니다.

  1. 아이디를 통한 제품 검색 기능
  2. 제품명, 제조사, 가격을 입력하면 등록이 되는 기능




첫 화면인 index.jsp입니다. 브라우저에서는 아래와 같이 보여집니다.

image

코드는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Model2 MVC, Singleton</title>
</head>
<body>
    <h4>Model2 MVC + Singleton Design Pattern</h4>
    <form action="FindProductByIdServlet">
        <input type="number" name="productId" placeholder="상품아이디" required="required">
        <button>검색</button>
    </form>
    <hr>                 
    <form action="RegisterProductServlet" method="post">
        <input type="text" name="name" placeholder="상품명" required="required"><br>
        <input type="text" name="maker" placeholder="제조사" required="required"><br>
        <input type="number" name="price" placeholder="가격" required="required"><br>
        <button>등록</button>
    </form>
</body>
</html>

검색 기능은 get 방식으로 요청하며 Servlet에서 forward방식으로 View쪽에 전달합니다.

등록 기능은 post 방식으로 요청하며 Servlet에서 redirect방식으로 View쪽에 전달합니다.



우선 검색 기능부터 확인하도록 하겠습니다.

구조는 다음과 같습니다.

image

사용자가 index.jsp에서 아이디를 입력하고 검색 버튼을 누르면 ControllerFindProductByIdServlet에 데이터가 전송됩니다.

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
package org.kosta.webstudy16.controller;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kosta.webstudy16.model.ProductDAO;
import org.kosta.webstudy16.model.ProductVO;

/**
 * Servlet implementation class FindProductByIdServlet
 */
@WebServlet("/FindProductByIdServlet")
public class FindProductByIdServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	    String id=request.getParameter("productId");
	    //static method로 객체의 주소값을 반환받아 사용한다
	    try {
            ProductVO vo = ProductDAO.getInstance().findProductById(id);
            String path=null;
            if(vo==null) {
                path="find-fail.jsp";
            }else {
                request.setAttribute("productVO", vo);
                path="find-ok.jsp";
            }
            request.getRequestDispatcher(path).forward(request, response);
        } catch (SQLException e) {
            e.printStackTrace();
        }
	}
}

전송받은 productId 정보를 Model의 ProductDAO와 연동해서 일치하는 VO가 있는지 findProductById() 메서드를 통해 판단고자 합니다.

여기에서 오늘 배운 Singleton 패턴이 적용되는 것을 확인할 수 있습니다.

findProductById(id)를 바로 호출할 수 없고, ProductDAO를 통해 getInstance() 메서드로 객체를 반환하여 findProductById(id)를 호출하는 방식입니다.

마지막으로 view쪽으로 연결해줘야 하는데, 일치 여부에 따라 forward 방식으로 각각 find-fail.jsp 또는 find-ok.jsp로 연결시켜주면 됩니다.



다음으로 model쪽 파일인 ProductDAO와 ProductVO입니다.

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package org.kosta.webstudy16.model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class ProductDAO {
    private static ProductDAO instance = new ProductDAO(); // 2
    private String driver = "oracle.jdbc.OracleDriver";
    private String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
    private String username = "scott";
    private String password = "tiger";

    private ProductDAO() {// 1
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static ProductDAO getInstance() { // 3
        return instance;
    }

    public void closeAll(ResultSet rs, PreparedStatement pstmt, Connection con) throws SQLException {
        if (rs != null)
            rs.close();
        closeAll(pstmt, con);
    }

    public void closeAll(PreparedStatement pstmt, Connection con) throws SQLException {
        if (pstmt != null)
            pstmt.close();
        if (con != null)
            con.close();
    }
    public ProductVO findProductById(String id) throws SQLException {
        ProductVO vo=null;
        Connection con=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;
        try {
            con=DriverManager.getConnection(url,username,password);
            String sql="select name, maker, price from web_product where id=?";
            pstmt=con.prepareStatement(sql);
            pstmt.setString(1, id);
            rs=pstmt.executeQuery();
            if(rs.next()) {
                vo=new ProductVO(Integer.parseInt(id), rs.getString(1), rs.getString(2), rs.getInt(3));
            }
        }finally {
            closeAll(rs, pstmt, con);
        }
        return vo;
    }
}

Singleton Design Pattern을 적용하는데 동작 원리를 이해할 수 있도록 주석에 1, 2, 3 순서를 적어두었습니다.

먼저 생성자에 private 접근제어자를 사용하여 다른 클래스에서 객체를 생성하지 못하도록 합니다. 드라이버 로딩은 jdk 1.6 이상에서 자동 지원을 해주지만, 연습을 위해 직접 작성했습니다.


ProductVO에는 private int id;, private String name;, private String maker;, private int price; 변수를 선언하고 생성자와 getter, setter, toString()를 만듭니다. 간단한 내용이어서 코드는 생략하겠습니다.



다음으로 등록 기능을 구현하겠습니다.

image

index.jsp에서는 데이터 양과 정보가 노출되지 않도록 post 방식으로 요청하였으며 RegisterProductServlet에서는 modelProductDAORegister() 메서드를 통해 사용자가 입력한 데이터를 등록하고 해당 결과를 반환받아 redirect 방식으로 결과 페이지에 보여주도록 합니다.


가장 먼저 사용자의 정보를 입력받는 index.jsp에서 해당 부분의 코드를 확인하겠습니다.

1
2
3
4
5
6
<form action="RegisterProductServlet" method="post">
<input type="text" name="name" placeholder="상품명" required="required"><br>
<input type="text" name="maker" placeholder="제조사" required="required"><br>
<input type="number" name="price" placeholder="가격" required="required"><br>
<button>등록</button>
</form>

post 방식으로 3개의 데이터를 입력받도록 했습니다.


다음으로 DB와 연동하는 Register() 메서드를 확인해보겠습니다. 앞서 작성한 ProductDAO에 해당 메서드만 추가적으로 작성했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void register(ProductVO vo) throws SQLException {
    Connection con=null;
    PreparedStatement pstmt=null;
    try {
        con=DriverManager.getConnection(url,username,password);
        StringBuilder sql=new StringBuilder("insert into web_product(id,name,maker,price) ");
        sql.append("values(web_product_seq.nextval,?,?,?)");
        pstmt=con.prepareStatement(sql.toString());
        pstmt.setString(1, vo.getName());
        pstmt.setString(2, vo.getMaker());
        pstmt.setInt(3, vo.getPrice());
        pstmt.executeUpdate();
    }finally {
        closeAll(pstmt, con);
    }
}

DB 수업에서 여러 번 공부했기에 자세한 설명은 생략하겠습니다.


Controller에 해당하는 RegisterProductServlet의 코드입니다.

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
package org.kosta.webstudy16.controller;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kosta.webstudy16.model.ProductDAO;
import org.kosta.webstudy16.model.ProductVO;

/**
 * Servlet implementation class RegisterProductServlet
 */
@WebServlet("/RegisterProductServlet")
public class RegisterProductServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	    request.setCharacterEncoding("utf-8");
	    String name=request.getParameter("name");
	    String maker=request.getParameter("maker");
	    int price=Integer.parseInt(request.getParameter("price"));
	    try {
            ProductDAO.getInstance().register(new ProductVO(name,maker,price));
            response.sendRedirect("register-result.jsp");
        } catch (SQLException e) {
            e.printStackTrace();
        }
	}
}

사용자가 입력한 상품 정보를 가져온 뒤 바로 앞에 작성한 register()의 인자값으로 할당하여 메서드를 호출했습니다.

ProductDAO는 싱글톤 패턴이 적용되어 있으므로 new 생성자로 객체 생성하는 것이 아니라 static메서드로 객체의 주소값을 리턴받아 register메서드를 호출했습니다.

그리고 forward 이동 방식은 결과 화면에서 사용자가 새로고침을 할 경우 재등록되므로 redirect를 사용하여 클라이언트에게 지정한 url로 이동하도록 처리했습니다.


이렇게 싱글톤 디자인 패턴을 적용하는 연습을 해보았고, 다음으로 jsp에서의 session 활용을 배워보겠습니다.




MVC Session

모든 jsp는 기본적으로 session을 사용할 수 있습니다.

그렇다면 jsp는 어떻게 session을 별 다른 선언 없이 바로 사용할 수 있는지 배워보겠습니다.


jsp는 web container에 의해 java로 생성된다고 배웠습니다. 이 과정에 session 변수에 getSession() 메서드를 사용해 session 객체를 할당하는 코드가 작성되어 있습니다.
(getSession(): 기존 세션이 존재한다면 기존 세션을 반환하고 아니라면 새로운 세션을 생성해 할당합니다)

그래서 회원 인증(로그인) 시 세션에 인증 정보(주로 회원 객체)를 할당하게 되는 거죠.
이후 회원 인증 여부(로그인 체크)를 확인할 때에는 세션 유무와 함께 로그인 시 할당한 인증 정보가 존재하는지 함께 확인합니다.

그렇다면 Model2 MVC를 적용한 Servlet과 JSP에서는 세션을 어떻게 활용하는지 예제를 통해 알아보도록 하겠습니다.




MVC Session 예제 1

이번 예제는 DB와의 연동이 없는 간단한 연습입니다. mvc-login.jspLoginServlet을 작성해서 진행하도록 하겠습니다.

기본 구조는 다음과 같습니다.

image


사용자의 데이터를 입력받을 mvc-login.jsp를 작성해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>mvc-login</title>
</head>
<body>
    <form action="LoginServlet" method="post">
        <input type="text" name="memberId" placeholder="아이디" required="required"><br>
        <input type="password" name="memberPass" placeholder="패스워드" required="required"><br>
        <input type="submit" value="로그인">
    </form>
</body>
</html>

post 방식으로 요청합니다.


다음으로 LoginServlet의 코드입니다.

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
package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import model.MemberVO;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;      
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String id=request.getParameter("memberId");
		String password=request.getParameter("memberPass");
		String url=null;
		if(id.equals("java")&&password.equals("abcd")) {
			HttpSession session=request.getSession();
			session.setAttribute("mvo", new MemberVO(id,null,"홍길동",null));
			url="login-ok.jsp";
		}else {
			url="login-fail.jsp";
		}
		response.sendRedirect(url);
	}
}

사용자가 입력한 정보를 가져온 뒤 기존 아이디와 패스워드와의 일치 여부를 판단해 로그인 성공 또는 실패 페이지로 전송해주는 것은 이전에 해본 방식과 같습니다.

그렇지만 24번 라인의 코드에서부터는 session을 활용해보는데, 기존 세션 정보가 있다면 불러오고 없다면 새롭게 생성한 뒤 HttpSession session에 할당하고 setAttribute() 메서드로 새로운 멤버 객체의 정보를 할당해 다른 페이지에서 사용할 수 있도록 활용해봤습니다.


먼저 로그인 실패 페이지를 확인해보면 간단하게 로그인 실패 창을 보여준 뒤 mvc-login.jsp로 이동하게 했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>login-fail.jsp</title>
</head>
<body>
    <script type="text/javascript">
        alert("로그인 실패");
        location.href="mvc-login.jsp";
    </script>
</body>
</html>


그리고 세션 정보를 활용할 로그인 성공 페이지는 다음과 같이 LoginServlet에서 할당한 session의 회원객체에서 name을 얻어와서 화면에 보여주도록 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@page import="model.MemberVO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>login-ok</title>
</head>
<body>
    <% MemberVO vo=(MemberVO)session.getAttribute("mvo"); %>
    <%=vo.getName() %>님 로그인 OK 
</body>
</html>

실행해보면 다음과 같이 정보를 잘 불러온 것을 확인하실 수 있습니다.

image




MVC Session 예제 2

사실 세션은 저희가 직접 생성하지 않더라도 아래의 그림과 같이 이미 생성되어 있습니다.

image

아무런 조치를 취하지 않아도 session이 만들어져 있는데, 이는 web container에 의해 jsp가 java로 생성되어 실행될 때 session을 생성하기 때문입니다.

tomcat/word 경로에서 파일을 직접 확인해보면 다음과 같이 코드가 작성되어 있는 것을 확인하실 수 있습니다.

image

image


같은 원리로 jsp 내장 객체인 ServletConfigServletContext 객체도 configapplication 변수로 사용할 수 있습니다.(사용할 일이 많지는 않습니다)

image



만약 세션 정보가 필요하지 않거나 없애야 하는 상황이라면 세션 무효화 처리를 할 수 있습니다.

session.invalidate(); 이 코드를 작성하면 세션이 무효화되며 이 상태에서 세션에 객체를 할당하고 정보를 불러오면 java.lang.IllegalStateException 에러가 발생합니다.



마지막으로 세션 객체를 자동 생성하지 않도록 처리할 수도 있는데, @page 지시자에 session="false"를 명시해서 session을 자동 생성하지 않도록 설정할 수 있습니다.


지시자 태그에서 세션을 자동 생성하지 않도록 설정한 뒤 세션 객체를 불러오면 에러가 발생하겠지만, 세션 객체를 완전히 생성하지 못 하는 것은 아니고 필요에 따라 다음 코드와 같이 세션을 생성할 수 있습니다.

<% HttpSession session=request.getSession(); %>



이것으로 오늘 배운 jsp에서의 session 사용 방법에 대한 공부를 마치도록 하겠습니다.



맨 위로 이동하기

Java Web Programming 카테고리 내 다른 글 보러가기

댓글남기기