본문 바로가기

STUDY/국비과정

[JAVA 웹 개발 공부] 국비지원 64일차 - Listener, Build Tool, Maven, logger

ServletContextListener

 

1. Listener

이벤트 발생한 특정 사건(마우스 클릭, 키보드 입력, 클라이언트로부터의 HTTP 요청, 웹어플리케이션 시작, 웹어플리케이션 종료 등)
이벤트 소스  이벤트가 발생한 대상(근원지)으로 마우스, 키보드, 웹어플리케이션(ServletContext) 등
리스너, 핸들러 이벤트가 발생되기를 기다렸다가 발생시 실행되는 메서드나 함수. 또는 메서드를 가진 객체

 

2. ServletContextListener
Servlet/JSP는 웹 어플리케이션을 개발하기 위한 기술이므로, 웹 환경에 관련된 이벤트들이 존재한다.

따라서 웹과 관련된 이벤트를 리스닝하고 처리할 수 있는 리스너를 구현해야 한다. Servlet/JSP에서는 웹 환경과 관련된 이벤트 리스너를 구현할 수 있도록 몇 가지 리스너 인터페이스를 제공하고 있다.

 

*Servlet / JSP 이벤트소스와 리스너 종류

이벤트 소스
이벤트 리스너
발생 이벤트 객체
설명
ServletContext
ServletContextListener
ServletContextEvent
웹어플리케이션의 시작, 종료 이벤트에 대한 이벤트 리스너
ServletContextAttributeListener
ServletContextAttributeEvent
ServletContext에 attribute를 추가하거나 제거, 수정됐을 때에 대한 이벤트 리스너
HttpSession
HttpSessionListener
HttpSessionEvent
HTTP 세션의 시작, 종료 이벤트에 대한 이벤트 리스너
HttpSessionAttributeListener
HttpSessionBindingEvent
HttpSession에 attribute를 추가하거나 제거, 수정됐을 때에 대한 이벤트 리스너
ServletRequest
ServletRequestListener
ServletRequestEvent
클라이언트로부터의 요청으로 인한 ServletRequest 생성과 응답 이후 ServletRequest 제거시에 대한 이벤트 리스너
ServletRequestAttributeListener
ServletRequestAttributeEvent
ServletRequest에 attribute를 추가하거나 제거, 수정됐을 때에 대한 이벤트 리스너

 

3. ServletContextListener 구현

웹 어플리케이션이 시작되고 종료될 때 특정한 기능을 실행하려면 다음과 같이 코드를 작성하면 된다.

1. javax.servlet.ServletContextListener 인터페이스를 구현한 클래스를 작성한다.

2. web.xml 파일에 1번에서 작성한 클래스를 등록한다.

 

 

init-param & context-param

 

특정 서블릿이 생성될 때 초기에 필요한 데이터들이 있다. 이러한 데이터들을 초기화 파라미터라고 한다.

init parameter는 그 매개변수가 선언된 서블릿에서만 사용할 수 있고 다른 서블릿은 참조할 수 없다. 여러 서블릿이 공통의 환경 정보를 사용한다면 context parameter를 사용하는게 좋다. context parameter는 같은 웹 어플리케이션의 서블릿들이 같이 공유할 수 있는 매개변수이다.

 

*초기화 작성하는 태그

<init-param> 특정 서블릿에만 적용시킨다.
<context-param> 여러 서블릿에서 사용가능하다.

 

*태그 안에 작성할 데이터

<param-name> 데이터를 구분하는 이름
<param-value> 초기화 데이터 값
<context-param>
    <param-name>mySettingName</param-name>
    <param-value>mySettingValue</param-value>
</context-param>


*태그별 호출 방식

<init-param> getInitParameter( )
<context-praram> getServletContext( ).getInitParameter( )

 

 

DataSource

 

1. 커넥션풀(ConnectionPool) 
기존 데이터베이스 연동 방법은 애플리케이션에서 데이터베이스에 연결하는 과정에서 시간이 많이 걸리는데,
이 문제를 해결하기 위해 나온 방법이 커넥션풀(ConnectionPool)이다. 
웹 애플리케이션이 실행됨과 동시에 연동할 데이터베이스와의 연결을 미리 설정해둔다.
그리고 필요할 때마다 미리 연결해 놓은 상태를 이용해 빠르게 데이터베이스와 연동하여 작업한다.

 

2. JNDI(Java Naming and Directory Interface) 
웹 애플리케이션 실행 시 톰캣이 만들어 놓은 ConnectionPool 객체에 접근할 때는 JNDI를 이용한다.
즉, 미리 접근할 자원에 키를 지정한 후 애플리케이션이 실행 중일 때 이 키를 이용해 자원에 접근해서 작업한다.
*JNDI 사용예
-웹 브라우저에서 name/value 쌍으로 전송한 후 서블릿에서 getParameter(name)으로 값을 가져올 때
-해시맵(HashMap)이나 해시테이블(HashTable)에 키/값으로 저장한 후 키를 이용해 값을 가져올 때

 

3. DataSource
커넥션풀에는 여러개의 Connection 객체가 생성되어 운용되는데, 이를 직접 웹 애플리케이션에서 다루기 힘들기 때문에 DataSource라는 개념을 도입하여 사용한다.
DataSource는 커넥션 풀의 Connection을 관리하기 위한 객체로, JNDI Server를 통해서 이용된다.
DataSource 객체를 통해서 필요한 Connection을 획득, 반납 등의 작업을 한다.

 

*DataSource 사용방법
(1) JNDI Server에서 lookup( ) 메소드를 통해 DataSource 객체를 획득한다.
(2) DataSource 객체의 getConnection( ) 메소드를 통해서 Connection Pool에서 Free 상태의 Connection 객체를 획득한다.
(3) Connection 객체를 통한 DBMS 작업을 수행한다.
(4) 모든 작업이 끝나면 DataSource 객체를 통해서 Connection Pool에 Connection을 반납한다.

 

 

DataSource 연결

 

*MyContextListener.java

package web06;

import java.sql.Connection;
import java.sql.SQLException;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

public class MyContextListener implements ServletContextListener {
	public static DataSource dataSource;
	
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("어플리케이션 시작시 발생하는 이벤트를 처리 가능합니다.");
		
		System.out.println("어플리케이션 전역에서 사용할 설정을 미리 수행합니다.");
		ServletContext context = sce.getServletContext();
		String jdbcUrl = context.getInitParameter("jdbcUrl");
		String jdbcClassname = context.getInitParameter("jdbcClassName");
		String jdbcUser = context.getInitParameter("jdbcUser");
		String jdbcPassword = context.getInitParameter("jdbcPassword");
		
		BasicDataSource ds = new BasicDataSource();
		ds.setDriverClassName(jdbcClassname);
		ds.setUrl(jdbcUrl);
		ds.setUsername(jdbcUser);
		ds.setPassword(jdbcPassword);
		dataSource = ds;
	}
	
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}
}

 

*web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
	version="4.0">
	<display-name>web06</display-name>

	<listener>
		<listener-class>web06.MyContextListener</listener-class>
	</listener>

	<context-param>
		<param-name>jdbcUrl</param-name>
		<param-value>jdbc:mysql://localhost:3306/my_db</param-value>
	</context-param>
	<context-param>
		<param-name>jdbcClassName</param-name>
		<param-value>com.mysql.cj.jdbc.Driver</param-value>
	</context-param>
	<context-param>
		<param-name>jdbcUser</param-name>
		<param-value>root</param-value>
	</context-param>
	<context-param>
		<param-name>jdbcPassword</param-name>
		<param-value>root</param-value>
	</context-param>
</web-app>

 

*TestDB.jsp

<%@page import="java.sql.ResultSet"%>
<%@page import="java.sql.PreparedStatement"%>
<%@page import="java.sql.Connection"%>
<%@page import="web06.MyContextListener"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>데이터소스 연결 확인 페이지</title>
</head>
<body>
<%
	try (Connection conn = MyContextListener.getConnection();
			PreparedStatement stmt = conn.prepareStatement("SELECT 1");
			ResultSet rs = stmt.executeQuery()) {
		if (rs.next()) {
			out.println(rs.getInt(1) == 1);
		}
	}
%>
</body>
</html>

 

 

Build Tool

 

빌드 툴을 소스 코드의 빌드 과정을 자동으로 처리해주며 외부 소스코드(외부 라이브러리)를 자동 추가 및 관리해주는 프로그램이다. 빌드 툴은 외부 라이브러리 의존성을 해결하기 위해서 사용한다.

빌드 툴에는 Ant, Maven, Gradle 이 있다.

 

*Maven vs Gradle

  Maven Gradle
특징 - 프로젝트에 필요한 모든 종속성(Dependency)를 리스트의 형태로 Maven에게 알려서 종속성을 관리
- XML, Repository를 가져올 수 있음 ('Jar', 'Class Path'를 선언하면 자동으로 필요한 라이브러리 파일을 가져옴)
- JVM 기반의 빌드도구
- Ant와 Maven의 단점을 보완
- 오픈소스 기반의 Builde 자동화 도구
- Groovy 기반 DSL로 작성
장단점 - 라이브러리가 서로 종속할 경우 XML이 복잡함
- 계층적인 데이터를 표현하기에는 좋지만, 플로우나 조건부 상황을 표현하기 어려움  
- 편리하나 맞춤화된 로직 실행이 어려움
- 프로젝트 시작시 설정에 드는 시간 절약

 

 

Maven 

 

메이븐(Maven) 은 아파치에서 만든 프로젝트 관리 도구이다. 

특히 Web Application 을 개발할 때 참조하고 있는 라이브러리들의 의존성 문제를 해결할 수 있기 때문에 많이 사용한다. 

메이븐 프로젝트를 생성 및 설정하면 pom.xml이라는 파일이 생성되는데, 메이븐 프로젝트의 설정을 위해 존재하는 xml 파일이다.

 

프로젝트 우클릭 > Configure > Convert to Maven Project > Artifact 설정 

Artifact 설정 
*Group Id : 회사 패키지명 역순(영소문자)
*Artifact Id : 프로젝트 이름(영소문자)
*Version : 버전
*Packaging : 패키지 방식(war)

 

 

외부 라이브러리 사용

 

1. Apache Commons DBCP

https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2/2.9.0

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>

 

2. MySQL Connector Java

https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.32

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.32</version>
</dependency>

 

*pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>web06</groupId>
	<artifactId>web06</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.9.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.32</version>
		</dependency>
	</dependencies>

	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.2.3</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

 

 

 

logger

 

1. logging
프로그램을 개발하거나 운영할 때 생기는 문제점을 관리하고 모니터링할 수 있는 데이터를 말한다.
기록이란 뜻으로 프로그램이 제대로 동작하고 있는지 실행 기록을 남기는 것을 말한다. 

2. console 기록 - System.out.println()

System.out.println()로 console 기록을 남기는 것은 기록을 남기는 형태 중 가장 느리다. 

기록은 영구히 남기고자 한다면 파일이나 디비, 이메일 등 다른 형태로 다양하게 남기는 것이 좋으나 콘솔기록은 콘솔로 밖에 남기지 못한다. 또한, 메세지의 중요도를 알 수 있는 방법이 적다.

 

3. logger

중요도, 퍼포먼스, 다양한곳에 원하는만큼 기록으로 남기기 위해 외부라이브러리 logger를 사용하여 기록을 남긴다.

logger 라이브러리는 사용법이 다 다르기 때문에 다양한 logger를 사용하더라도 똑같은 방식 제공해 줄 수 있는 SLF4J 라이브러리를 추가로 같이 사용한다.

 

4. logging tool

log4j 효율적인 메모리 관리로 그동안 많이 사용됨
logback log4j 보다 성능이 더 우수해 최근 주로 사용 
SLF4j logback을 사용하기 위한 인터페이스

 

*Logback Classic Module » 1.3.5

https://mvnrepository.com/artifact/ch.qos.logback/logback-classic/1.3.5

<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.3.5</version>
</dependency>

 

*SLF4J API Module

https://mvnrepository.com/artifact/org.slf4j/slf4j-api

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.6</version>
</dependency>

 

5. logger 생성

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
private static final Logger logger = LoggerFactory.getLogger(MyRequestListener.class);

 

6. Log Level(로그레벨)

TRACE > DEBUG > INFO > WARN > ERROR > FATAL

 

7. Logback Documentation

https://logback.qos.ch/documentation.html

 

Documentation

Logback documentation Below is a list of logback-related documentation currently available. Source code related documentation: Articles and Presentations In french

logback.qos.ch

 

 

로그 파일 작성하기

 

1. configuration 설정


2. appender(어디에 출력할 지)에서 콘솔에 출력되는 형식 지정
pattern 에서 지정한 방식대로 시간과 레벨 등의 설정이 되고난 후 콘솔에 메세지를 출력한다. 해당 append 설정은 STDOUT 이라는 변수명으로 저장해뒀다고 생각하면 된다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

 

3. 로그를 저장할 방식을 지정

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<appender name="STDOUT"
		class="ch.qos.logback.core.ConsoleAppender">
		<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder 
			by default -->
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp-
				%msg%n</pattern>
		</encoder>
	</appender>
	
	<appender name="FILE" class="ch.qos.logback.core.FileAppender">
		<file>d:/myfolder/testFile.log</file> <!-- 파일 저장 경로 -->
		<append>true</append>
		<!-- set immediateFlush to false for much higher logging throughput -->
		<immediateFlush>true</immediateFlush>
		<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder 
			by default -->
		<encoder>
			<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n 
            <!-- 해당 패턴 네이밍으로 현재 로그 기록됨 -->
			</pattern>
		</encoder>
	</appender>
	
    <!-- 로그 레벨을 출력 -->
	<root level="debug">
		<appender-ref ref="STDOUT" />
		<appender-ref ref="FILE" />
	</root>
</configuration>

 

4. 로그 파일 확인

 

logger들은 패키지의 계층구조 많이 따진다. 동작할 패키지를 명시할 수 있으며, 원하는 패키지만 숨길수 있다.

logger 라이브러리를 통해 세부적인 기록을 남기는 것이 가능하고 기록을 패키지별로 레벨별로 나누어서 원하는 만큼 원하는 장소에 출력을 할 수 있다.

 

 

FileAppender

 

https://logback.qos.ch/manual/appenders.html#FileAppender

 

Chapter 4: Appenders

There is so much to tell about the Western country in that day that it is hard to know where to start. One thing sets off a hundred others. The problem is to decide which one to tell first. —JOHN STEINBECK, East of Eden Chapter 4: Appenders What is an Ap

logback.qos.ch

 

 

파일 업로드를 위한 서블릿 구성


파일 업로드를 하기 위해서는 파일 업로드를 수행하는 서블릿을 가장 먼저 구성해야 한다.
서블릿을 구성하는 방법은 다음의 두 가지 어노테이션을 작성한다.
1. @WebServlet 어노테이션을 통해 서블릿 선언 및 URL 매핑을 수행
2. @MultipartConfig 어노테이션을 통해  서블릿이 파일 업로드 기능을 할 수 있도록 웹 컨테이너에 지시

@MultipartConfig 어노테이션의 속성은 다음과 같다.

속성명
설명
fileSizeThreshold
파일을 임시 디렉터리에 저장할 기준 크기 (단위: Bytes)
maxFileSize
한 번에 업로드 할 수 있는 파일 크기 제한 (단위: Bytes)
maxRequestSize
전체 요청의 크기 제한. 기본값은 무제한 (-1L) (단위: Bytes)
location
임시 파일을 저장할 웹 컨테이너 지정 (작성하지 않으면 서버가 기본 임시 디렉터리 설정)

 

 

파일 업로드

 

*fileupload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>파일 업로드</title>
</head>
<body>
	<form action="./file/upload" method="post" enctype="multipart/form-data">
		<input type="file" name="upload" /> 
		<input type="submit" />
	</form>
</body>
</html>

 

*FileUploadServlet.java

package web06;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@WebServlet(urlPatterns = "/file/upload")
@MultipartConfig(location = "d:\\myfolder",
	fileSizeThreshold = 1024 * 1024 * 5, maxFileSize = 1024 * 1024 * 50)
public class FileUploadServlet extends HttpServlet {
	private static final Logger logger = LoggerFactory.getLogger(FileUploadServlet.class);
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
    	throws ServletException, IOException {
		Part part = req.getPart("upload");
		
		logger.info(part.getName());
		logger.info(part.getSubmittedFileName());
		for (String name : part.getHeaderNames()) {
			logger.info(name + ":" + part.getHeader(name));
		}
		
		String rootPath = getServletContext().getRealPath("");
		logger.info("애플리케이션 root 경로 확인 : " + rootPath);
		Path uploadPath = Paths.get(rootPath, "upload");
		if (Files.exists(uploadPath)) {
			Files.createDirectories(uploadPath);
		}
		Files.copy(part.getInputStream(), Paths.get("d:\\").resolve(part.getSubmittedFileName()), 
        	StandardCopyOption.REPLACE_EXISTING);
	}
}

 

 

요약

 

1. 연습

사용자에게 파일 업로드 양식을 제공하고 DB에 저장해보기.

 

2. 정리
(1) build tool (maven, gradle)
*build tool을 사용하는 목적?
*maven의 설정 파일 이름?
*maven 설정에서 외부 라이브러리 의존성 해결하는 법.
(2) logging tool (log4j, log4j2, logback, commons logging, juli ...)
*logging tool을 사용하는 목적?
*logging level, hierarchy?
*slf4j의 특징?