[Java] 56. Web service의 Servlet에서 초기화 작업(properties 설정)


Study/Java  2021. 7. 2. 17:07

안녕하세요. 명월입니다.


이 글은 Web service의 Servlet에서 초기화 작업(properties 설정)에 대한 글입니다.


Web service 프로그램을 만들면, 서버가 기동할 때 초기화 작업이 필요할 때가 있습니다.

예를 들면, 마스터 테이블의 데이터를 미리 인스턴스 생성을 해서 메모리에 할당을 한다던가, 서버의 실행 디렉토리 설정등등이 있습니다.


먼저 우리가 Web service에서 ORM을 JPA로 사용할 경우 최초 Connection 초기화가 필요합니다.

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import dao.UserDao;
import model.User;

@Controller
public class Home {
  // UserDao 인스턴스의 의존성 주입
  @Autowired
  @Qualifier("UserDao")
  private UserDao userDao;

  // 요청 url 패턴 (index.html)
  @RequestMapping(value = "/index.html")
  public String index(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // view 파일 매핑
    return "index";
  }

  // 요청 url 패턴 (test.json)
  @RequestMapping(value = "/test.json")
  // view 파일 매핑 안함
  @ResponseBody
  public String index(HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // user 테이블에서 id가 nowonbun를 취득함
    User user = userDao.selectById("nowonbun");
    // 반환
    return user.getName();
  }
}
<%@ 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>
  <!-- ajax 통신이 시작하기 전의 시간 -->
  <br />startdate - <span id="startdate"></span>
  <!-- ajax 통신이 완료된 시간 -->
  <br />enddate - <span id="endate"></span>
  <!-- cdn jquery -->
  <script src="//code.jquery.com/jquery-3.4.1.min.js"></script>
  <!-- cdn 날짜 관계된 moment -->
  <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
  <script>
    $(function() {
      // 시작하면 시간 설정
      $("#startdate").html(moment().format('HH:mm:ss'));
      // ajax 설정
      $.ajax({
        type : "GET",
        url : "test.json",
        dataType : "html",
        success : function(msg) {
          // 값을 콘솔에 설정
          console.log(msg);
          // ajax가 완료되면 시간 설정
          $("#endate").html(moment().format('HH:mm:ss'));
        }
      });
    });
  </script>
</body>
</html>

위 예제는 먼저 index.html 페이지를 호출하게 되면 ajax로 test.json의 데이터를 취득합니다.

최초 기동한 후에 index.html이 로딩된 후에 test.json를 호출할 경우 시간이 얼마나 걸리는 지 확인해 봅시다.

약 1초 정도 시간이 걸립니다. 시간이 걸리는 이유는 최초 connection 초기화 때문에 시간이 걸리는 것입니다.

재기동을 하지 않고 같은 페이지를 재요청(F5 -reload)을 하게 되면 ajax로 데이터를 취득해 오는 데 1초도 걸리지 않습니다.

이 뜻은 서비스가 처음만 살짝(?) 느려지는 것이니 초기화를 할 필요는 없지만 그래도 조금은 완벽하게(?)하기 위해서는 초기화를 해야합니다.


초기화 설정은 web.xml에서 설정합니다.

<servlet>
  <!-- servlet 이름은 아무거나 상관없다(가능하면 겹치지 않는 이름으로) -->
  <servlet-name>startup</servlet-name>
  <!-- 실행할 클래스(package 이름 포함) -->
  <servlet-class>common.InitController</servlet-class>
  <!-- 실행 순서 -->
  <load-on-startup>1</load-on-startup>
</servlet>

실행 순서는 초기화할 클래스가 한개가 아닐 경우에는 load-on-startup를 통해서 순서를 설정할 수 있습니다.

package common;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import dao.FactoryDao;
import dao.UserDao;

// 서블릿을 상속받는다.
public class InitController extends HttpServlet {
  private static final long serialVersionUID = 1L;
  // get, post가 아닌 init를 override를 한다.
  @Override
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    // 마스터 테이블를 호출해서 기동한다.
    FactoryDao.getDao(PermissionDao.class).reflesh();
  }
}

InitController 클래스는 HttpServlet를 상속받습니다. 그리고 init 함수에 ORM을 통해서 데이터를 취득하는 것으로 ORM connection을 생성합니다.

초기화에서 취득하는 함수는 마스터 테이블을 미리 메모리에 할당하고 controller에서는 재사용하는 것으로 성능을 향상시킵니다.

처음 기동부터 빠른 데이터 취득을 확인 할 수 있습니다.


그 외에 웹에서 환경 설정 데이터를 설정하는 경우가 있습니다. 예를 들면 라이센스 키라던가, SMTP 이메일 비밀번호등을 사용할 수 있겠네요.

package common;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import dao.FactoryDao;
import dao.PermissionDao;

// 서블릿을 상속받는다.
public class InitController extends HttpServlet {
  private static final long serialVersionUID = 1L;
  // 실행  디렉토리
  private static String localPath;
  // flyweight 패턴
  private static final Map<String, Properties> flyweight = new Hashtable<>();

  // get, post가 아닌 init를 override를 한다.
  @Override
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    // 마스터 테이블를 호출해서 기동한다.
    FactoryDao.getDao(PermissionDao.class).reflesh();
    // 초기화 웹 서비스 실행 디렉토리
    localPath = getServletContext().getRealPath("/");
  }
  // property 값을 취득하는 함수
  // session은 property 파일 이름, key는 키
  public static String getProperty(String session, String key) {
    try {
      // flyweight 패턴으로 property 설정 값을 한번 로드하면 메모리에 할당한다.
      // 즉, 실행 중에 환경설정 값을 바꾸면 서버를 재기동해야 적용된다.
      if (!flyweight.containsKey(session)) {
        // property 인스턴스 생성
        Properties property = new Properties();
        // flyweight 패턴에 넣는다.
        flyweight.put(session, property);
        // 서비스 실행 경로에서 session 파라미터 명으로 파일을 읽어온다.
        File file = new File(localPath + "WEB-INF" + File.separator + "classes" + File.separator + session + ".properties");
        // stream 생성
        try (InputStream straem = new FileInputStream(file)) {
          // 프로퍼티를 읽어온다.
          property.load(straem);
        }
      }
      // flyweight 패턴으로 property 취득
      Properties property = flyweight.get(session);
      // 키를 통해서 값을 취득
      return property.getProperty(key);
    } catch (IOException e) {
      return null;
    }
  }
}

InitController에 property를 취득하는 함수를 작성했습니다.

static 타입으로 flyweight 패턴으로 처음 한번 property를 읽어오면 메모리에 할당하여 재사용하는 방식으로 작성했습니다. (property 값이 바뀌면 서버를 재기동 해야 합니다.)

여기서 중요한 것은 local 경로입니다. 최초 서버를 기동할 때, servlet의 init함수에서 실행 위치를 취득할 수 있습니다.

test=test-key

그리고 위 property값을 Controller에서 사용하겠습니다.

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import common.InitController;

@Controller
public class Home {
  // 요청 url 패턴 (index.html)
  @RequestMapping(value = "/index.html")
  public String index(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // InitController 클래스의 getProperty 함수를 통해서 config.properties 파일의 test 키 값을 취득하고
    // modelmap에 설정한다.
    modelmap.addAttribute("data", InitController.getProperty("config", "test"));
    // view 파일 매핑
    return "index";
  }
}
<%@ 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>
  <!-- modelmap으로 넘겨 받는 데이터 -->
  ${data}
</body>
</html>

property 설정 값을 취득하고 view의 파일에 매핑하고 실행 하면 화면에 표시가 됩니다.


여기까지 설정이 완료되면 사실 Java Spring Framework의 프로젝트를 시작할 기본 설정은 완료가 되었습니다. 그밖에는 이제 사양에 맞게 디자인 패턴으로 추상 클래스와 인터페이스를 생성하고 그에 따른 프로그램일 작성하면 될 듯 싶네요.


여기까지 Web service의 Servlet에서 초기화 작업(properties 설정)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.