- 3장의 전반적인 구성 내용
- 질문/답변 게시판에 대한 서비스 요구사항과 실습 요구사항 제시
- Web Client와 서버간의 데이터 통신을 위한 HTTP Web Application Server 구축
- Local에서 Web Application 개발
- 버전 관리 시스템에서 소스 코드 관리
- 개발한 Application을 원격 서버에 배포
- 질문/답변 게시판에 대한 서비스 요구사항과 실습 요구사항 제시
-
질문/답변 게시판의 요구사항을 사용자의 흐름 순으로 살펴보자
- 질문/답변 게시판
- Header에서 회원가입, 로그인, 로그아웃, 개인정보 수정이 가능
- Header에서 회원가입, 로그인, 로그아웃, 개인정보 수정이 가능
- 회원가입 클릭 시 회원 가입 페이지로 이동
- 로그인 버튼을 누르면 로그인 페이지로 이동 및 로그인 가능
- 질문 목록 화면에서 질문 클릭 시 각 질문의 상세보기 화면으로 이동
- 답변 추가 및 수정/삭제 가능
- 답변 추가 및 수정/삭제 가능
- 질문/답변 게시판
- 기본적으로 Java 8 Version과 이클립스를 이용하여 구성된다. (물론 변경도 가능)
- 이클립스의 Git Perspective를 이용한 방법
-
https://github.com/slipp/web-application-server 에서 Project를 자신의 Repository에 Fork한다.
-
-
Import한 프로젝트에서 WebServer.java를 Java Application으로 실행한다.

-
- 터미널에서 Git Clone한 후 Import 하는 방법
- 참고 유튜브 영상
- 한글 인코딩 설정, 자바 8 설치 및 설정, Maven 설치 및 과정
- Git 설치, Github 저장소 Clone, 메이븐을 활용한 빌드, HTTP 웹 서버 시작, 소스 코드 수정 시 재배포 과정
-
EC2
- EC2는 AWS에서 제공하는 클라우드 컴퓨팅 서비스로 아마존이 각 세계에 구축한 데이터 센터의 서버용 컴퓨터들의 자원을 원격으로 사용할 수 있는 서비스이다. (즉 한 대의 컴퓨터를 임대 하는 것!!)
-
EC2 설정
-
인스턴스 시작을 누르면 Amazon Machine Image(AMI) 및 인스턴스 유형을 선택할 수 있는 페이지로 넘어간다. 이 부분에서 프리 티어의 Ubuntu 22.04 LTS 버전을 선택한다. 프리 티어를 사용한다면 인스턴스의 유형은 t2.mircro로 고정이다.



-
키 페어는 EC2 서버에 SSH 접속을 위해 필수적인 부분이다. 새 키페어 생성”을 통해 키 페어를 생성 하면
[key이름].pem파일이 다운로드 되는데 이 파일의 위치에 가서 ssh 명령을 수행하면 된다. 단!! 한 번 다운받은 후에는 재 다운로드가 불가능하기 때문에 백업이 필요하다.
-
네트워크는 보안 그룹을 따로 생성하도록 하였으며 SSH 트래픽의 경우 위치 무관으로 열어놓았다. 보안 상 특정 ip에서 접속하도록 설정 해야 하지만 현재는 스터디를 위한 것이므로 어느 IP에서도 접속이 가능하도록 하였다. 스토리지 구성의 경우 프리 티어는 최대 30GB 범용 SSD를 사용할 수 있어서 29GB를 할당하였다.

-
설정이 끝난 후 인스턴스 시작을 누르면 인스턴스 생성 완료 메시지가 나오며, 인스턴스 홈으로 가 보면 생성한 인스턴스를 볼 수 있다.

-
AWS EC2 인스턴스는 서버를 중지하고 다시 실행하면 Public IP가 변경되기 때문에 Reboot을 해도 사용할 수 있는 IP가 필요하다.
탄력적 IP는 만들어놓고 사용하지 않더라도 과금이 되기 때문에 필요한 만큼만 생성하자! 메뉴의 “탄력적 IP”를 클릭하여 “탄력적 IP 주소 할당”을 선택한다.
- 이제 인스턴스를 연결해보자.
선택한 인스턴스에서 오른쪽 마우스 클릭 시 인스턴스 연결 정보를 알려주긴 하지만 매번 이런식으로 연결하면 매우 불편하다. 이때 호스트를 등록하는 방법을 사용하면 간단하게 접속할 수 있다.
- 우선 키 파일이 있는 폴더에 가서 터미널 실행 후 키 파일이 권한을 변경한다.
$ chmod 400 "본인의 키 파일 이름".pem- 그 후 키 페어 파일을
~/.ssh/로 복사하고(백업) 복사한 키 파일 권한을 변경한다.
$ cp "본인의 키 파일 이름".pem ~/.ssh/ $ cd ~/.ssh $ chmod 600 "본인의 키 파일 이름".pem
- 그 후
~/.ssh/디렉터리에config파일을 생성해서 다음의 코드를 입력한다. 작성자는 이미 만들어놓은 설정 정보가 존재하여 밑에 추가를 해주었다.
$ vi ~/.ssh/config # 아래는 파일 내용 # ssh -i {키 페어 파일} {유저 이름}@{탄력적 IP} Host {원하는 호스트 이름} User {유저 이름} HostName {탄력적 IP} IdentityFile {키 페어 파일 위치}- 이제 키 페어 파일이 없는 곳에서도 SSH 접속이 가능하다.
$ ssh "설정한 호스트이름"-
마지막으로 보안그룹을 설정해주자. 우선 AWS에서 쓸 수 있는 보안 그룹은 두 가지 종류가 있다.
- 인바운드 (Inbound): 외부 -> EC2 인스턴스 내부 허용
- 아웃바운드 (Outbound): EC2 인스턴스 내부 -> 외부 허용
보안 그룹은 인스턴스의 보안 Tab에서 볼 수 있다.
- 인바운드 규칙에서는 외부에서 EC2로 요청할 때 허용할 IP 대역을 설정할 수 있다. EC2를 생성할 때 보안 그룹이 Default로 만들어졌지만, 새로 생성한 규칙을 통해 적용할 것이다.
유형을 먼저 선택하면 그에 맞게 프로토콜과 포트의 범위가 고정되며, 특정 IP나 대역을 넣으려면 “사용자 지정”으로 추가할 수 있다.
우선 가장 많이 사용하는 “SSH”, “HTTP”, “HTTPS”를 추가한다. 0.0.0.0/0은 모든 IP의 접속을 허용한다는 얘기이다.

- 규칙을 다 설정하고 저장했다면 인스턴스로 와서 생성한 보안그룹으로 변경하면 정상적으로 적용된다.
-
- AWS EC2로 실습을 진행할 경우 다음 단계부터 진행하면 된다.
- AWS EC2가 아닌 다른 ubuntu 서버를 이용할 경우
- 다음 명령을 실행하여 시스템 전체 계정에서 한글과 관련한 인코딩을 사용할 수 있도록 설정한다.
sudo locale-gen ko_KR.EUC-KR ko_KR.UTF-8
sudo dpkg-reconfigure locales- 각 계정의 디렉토리의 .bash_profile에 다음과 같은 설정을 추가한다.
- 만약 .bash_profile 파일이 없다면 vi 명령을 통해 만들어준다.
(만약 .bash_profile 파일이 없다면)
vi .bash_profile
(.bash_profile 파일)
LANG="ko_KR.UTF-8"
LANGUAGE="ko_KR:ko:en_US:en"
(저장 후 서버를 재시작해야 반영하지만 source 명령을 이용하여 Reboot 없이 바로 서버에 반영할 수 있다.)
source .bash_profile
(env 명령을 통해 반영 결과를 확인할 수 있다.)
(env: 현재 지정되어 있는 환경 변수들을 출력하거나, 새로운 환경 변수를 설정하고 적용된 내용을 출력하는 명령어)
env-
JDK 8 설치
wget --no-cookies --no-check-certificate --header "Cookie: oraclelicense=accept-securebackup-cookie" https://javadl.oracle.com/webapps/download/GetFile/1.8.0_301-b09/d3c52aa6bfa54d3ca74e617f18309292/linux-i586/jdk-8u301-linux-x64.tar.gz
- EC2 인스턴스에서 wget 명령을 통해 ubuntu 버전 jdk를 다운로드 받고 압축을 풀어준다.
wget [다운로드 주소] tar -xvf [압축을 해제할 파일 이름]
- 계정 Home 디렉 토리의 .bash_profile 파일에 JAVA_HOME/bin 디렉토리를 PATH로 등록한다.
(심볼릭 링크를 연결) ln -s [자바 디렉토리 이름]/ java vi .bash_profile (.bahs_profile 안에서) export JAVA_HOME=~/java export PATH=$PATH:$JAVA_HOME/bin
- PATH 설정을 완료 후 java -version 명령을 실행해 설치한 자바 버전을 확인한다.
java -version
-
Maven 설치
- https://maven.apache.org/
- 위 링크에서 maven을 다운로드 한다. (tar.gz 파일)
- 압축을 해제하면 설치가 완료된다.
wget [다운로드 주소] tar -xvf [압축을 해제할 파일 이름]
- .bash_profile에 MAVEN_HOME/bin 디렉토리를 PATH로 설정한다.
(심볼릭 링크를 연결) ln -s [메이븐 디렉토리 이름]/ maven vi .bash_profile (.bahs_profile 안에서) export JAVA_HOME=~/maven export PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
- PATH 설정 완료 후 mvn -version 명령을 통해 설치한 Maven 버전을 확인한다.
mvn -version
-
서버 실행
java -cp target/classes/:target/dependency/* webserver.WebServer 8080 // 백그라운드 실행 java -cp target/classes/:target/dependency/* webserver.WebServer 8080 &
- git 설치
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git- git clone
git clone [clone할 url]-
3장에서의 실습 요구 사항은 다음과 같은 구성으로 되어있다.
- index.html 응답하기
- GET 방식으로 회원가입하기
- POST 방식으로 회원가입하기
- 302 status code 적용
- 로그인하기
- 로그인 여부에 따른 사용자 목록 출력
- CSS 지원하기
-
소스 코드 분석
- WebServer.java
- 웹 서버를 시작하고, 사용자의 요청이 있을 때까지 대기 상태에 있다가 사용자 요청이 있을 경우 사용자의 요청을 RequestHandler 클래스에 위임
- RequestHandler.java
- 사용자의 요청에 대한 처리와 응답에 대한 처리를 담당하는 클래스
- WebServer.java
-
WebServer.java를 실행하고 브라우저에서 localhost:8080/index.html 로 접속해보자
-
접속하고 개발자 도구 → Network → localhost의 Header를 보면 다음과 같은 Request Header 메시지를 볼 수 있다.
-
이 요청 값은 InputStream을 통해 들어오고 이를 BufferdReader를 통해 값을 출력할 수 있다.
// 요청 라인 추출하기 -> HTTP Header : GET /index.html HTTP 1.1
BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String headerMessage = br.readLine();
log.debug("HTTP Request Header : {}", headerMessage);
// 전체 값 추출하기
if (headerMessage == null) return;
while (!headerMessage.equals("")) {
headerMessage = br.readLine();
log.debug(" : {}", headerMessage);
}- 요청 라인 값에서 /index.html을 추출하기 위해서 공백을 구분자로 split 함수를 이용한다
String tokens[] = headerMessage.split(" ");- 추출한 /index.html을 이용하여 웹 서버에 요청을 보내면 index.html의 내용을 응답받을 수 있다.
byte[] body = Files.readAllBytes(new File("./webapp" + tokens[1]).toPath());
DataOutputStream dos = new DataOutputStream(out);
response200Header(dos, body.length);
responseBody(dos, body);-
지금 상태에서 웹 서버를 띄운 상태에서 회원 가입을 누르면 아래와 같은 HTTP Header 요청을 볼 수 있다.

-
이 요청에서 알 수 있는건 GET 방식으로 요청이 온다는 점과 경로가 /user/create? 로 시작한다는 점이다.
-
이 URL 정보를 통해 우리는 회원가입을 진행할 수 있는 힌트를 얻을 수 있다.
// 요구사항 2. GET 방식으로 회원 가입하기
// http header 요청 메세지
String url = tokens[1];
// startWith([String]) -> String 문자열로 시작하면 True를, 아니라면 false를 반환하는 함수
if (url.startsWith("/user/create")) {
// url에서 ?의 위치를 반환
int index = url.indexOf("?");
String queryString = url.substring(index + 1);
// parseQueryString()
// &를 기준으로 나누며, =를 기준으로 key : value로 나누어 Map에 저장한다.
Map<String, String> params =
HttpRequestUtils.parseQueryString(queryString);
User user = new User(params.get("userId"), params.get("password"),
params.get("name"), params.get("email"));
log.debug("User : {}", user);-
이 실습까지는 회원가입 처리 후 응답 메세지를 보내지 않았기 때문에 브라우저는 “응답 없음”을 띄울 것이다.

-
GET 방식은 사용자가 입력한 데이터가 URL에 표시 되기 때문에 보안적인 측면에서 취약하다.
-
또 브라우저의 정책마다 다르지만 크롬 브라우저의 경우 2MB의 길이로 제한(주소창 표시는 32KB)을 둔다고 한다.
<출처 : Chromium Docs >
- GET 방식을 POST 방식으로 변경하기 위해 form.html의 form 태그 method 속성을 get에서 post로 수정한다.
<form name="question" method="post" action="/user/create">- POST 방식은 GET 방식과 다르게 URL에 쿼리 스트링이 표시되지 않으며, 이 쿼리 스트링은 HTTP 요청의 Body를 통해 전달된다.
- POST 방식으로 데이터를 전달하면서 헤더에 본문 데이터의 길이가 Content-Length라는 필드 이름으로 전달된다.
- 데이터의 파싱 방법은 “요구사항 (2)”와 같다.
// 요구사항 3. POST 방식으로 회원 가입하기
int contentLength = 0;
while (!headerMessage.equals("")) {
headerMessage = br.readLine();
log.debug(" : {}", headerMessage);
if (headerMessage.contains("Content-Length")) {
String[] headerTokens = headerMessage.split(":");
contentLength = Integer.parseInt(headerTokens[1].trim());
}
}
String url = tokens[1];
if (url.startsWith("/user/create")) {
String queryString = IOUtils.readData(br, contentLength);
Map<String, String> params =
HttpRequestUtils.parseQueryString(queryString);
User user = new User(params.get("userId"), params.get("password"),
params.get("name"), params.get("email"));
log.debug("User : {}", user);
} else {
DataOutputStream dos = new DataOutputStream(out);
byte[] body = Files.readAllBytes(new File("./webapp" + tokens[1]).toPath());
response200Header(dos, body.length);
responseBody(dos, body);
}-
HTML에서는 기본으로 GET과 POST만을 지원한다.
- GET
- 서버에 존재하는 데이터 또는 자원을 가져오는 것
- POST
- 서버에 요청을 보내 데이터 추가, 수정, 삭제와 같은 작업을 실행하도록 하는 것
- GET
-
그럼 PUT과 DELETE는 왜 지원하지 않는걸까?
- 2010년도에 버그 리포트 를 이슈로 등록하고 PR을 올림
- 실제로 파이어 폭스 베타 버전에서는 관련 스펙이 추가되기도 했음
- 그러나 공식적으로 추가되지는 않음
- 코멘트에는 “PUT에는 Form의 Patyoad를 넣는 것을 기대하지 않고 delete 역시 payload가 있는 것이 맞지 않다.”는 내용이 있다.
-
302 status Code란
- Redirection 클래스에 속하며 일시 (Temporarily) 리다이렉션이라고 부른다.
- 보통 리다이렉트 방식으로 이동한다고 한다면 내부적으로 302 상태 코드를 이용하여 이동한다고 생각하면 된다.
-
저번 실습까지 회원 가입을 완료하면 콘솔에만 log가 찍힐 뿐 페이지가 이동하지 않았다.
-
페이지를 이동하려면 “/index.html”을 담고 요청 URL 값을 이 값으로 변경하면 웹 서버가 이를 읽어 응답으로 보낼 수 있다.
DataOutputStream dos = new DataOutputStream(out);
byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath());
response200Header(dos, body.length);
responseBody(dos, body);-
그러나 이 방식을 사용하면 브라우저가 이전 요청 정보를 유지 하고 있기 때문에 회원 가입 요청이 다시 실행된다.
-
url을 보면 index.html로 이동했음에도 /user/create로 되어 있는 것을 알 수 있다.
-
이러면 데이터의 중복이 발생하므로 회원 가입을 처리하는 /user/create 요청과 index.html을 보여주는 요청을 분리한 후 HTTP의 302 상태 코드를 활용하여 해결할 수 있다.
-
/index.html로 이동하도록 응답을 보낼 때 응답 헤더의 Location으로 다음과 같은 응답을 보내면 클라이언트는 상태 코드를 확인 한 후 302라면 Location의 값으로 서버에 재요청을 전송한다.
-
이러면 /user/create 요청이 아니라 /index.html 요청이 보내진다.
HTTP/1.1 302 Found
Location: /index.htmlresponse302Header(dos, "/index.html");
private void response302Header(DataOutputStream dos, String url) {
try {
dos.writeBytes("HTTP/1.1 302 Redirect \r\n");
dos.writeBytes("Location: " + url + "\r\n");
dos.writeBytes("HTTP/1.1 302 Redirect \r\n");
} catch (Exception e) {
log.error(e.getMessage());
}
}- 상태 코드 정리
- 2XX : 성공. 클라이언트가 요청한 동작을 수신하여 이해했고 승낙햇으며, 성공적으로 처리
- 3XX : 리다이렉션. 클라이언트는 요청을 마치기 위해 추가 동작이 필요함
- 4XX : 요청 오류. 클라이언트에 오류가 발생
- 5XX : 서버 오류. 서버가 유효한 요청을 수행하지 못했을 때 발생
-
로그인 페이지로 이동하여 로그인을 한다.
-
로그인에 성공하면 “index.html”페이지로 이동하고 로그인이 실패하면 “/user/login_failed.html”로 이동한다.
-
HTTP는 기본적으로 stateless(무상태) 방식이다.
-
즉 한번 요청을 보내그 응답을 받으면 클라이언트와 서버간의 연결을 끊기 때문에 각 요청 사이의 상태를 공유할 수 없다.
-
이 때 서버가 클라이언트를 식별할 수 없는 문제가 발생하는데, 이를 쿠키를 통해 해결할 수 있다.
-
이 실습에서는
-
/user/create url로 요청이 오면 user를 만든후 addUser()를 통해 User를 저장한다.
if (url.startsWith("/user/create")) {
String queryString = IOUtils.readData(br, contentLength);
Map<String, String> params =
HttpRequestUtils.parseQueryString(queryString);
User user = new User(params.get("userId"), params.get("password"),
params.get("name"), params.get("email"));
DataBase.addUser(user);
}- 로그인 요청이 오면 가입되어 있는 유저중 form으로 넘어온 id 값과 일치한 유저가 있고 password가 동일하다면 response302LoginSuccessHeader()를 실행하여 쿠키를 설정하고 아니라면 responseResource()를 실행하여 로그인 실패 페이지로 이동한다.
if (url.startsWith("/user/create")) {
String queryString = IOUtils.readData(br, contentLength);
Map<String, String> params =
HttpRequestUtils.parseQueryString(queryString);
User user = new User(params.get("userId"), params.get("password"),
params.get("name"), params.get("email"));
DataBase.addUser(user);
}private void response302LoginSuccessHeader(DataOutputStream dos) {
try {
dos.writeBytes("HTTP/1.1 302 OK \r\n");
dos.writeBytes("Set-Cookie: logined=true \r\n");
dos.writeBytes("Location: /index.html \r\n");
dos.writeBytes("\r\n");
} catch (IOException e) {
log.error(e.getMessage());
}
}private void responseResource(OutputStream out, String url) throws IOException{
DataOutputStream dos = new DataOutputStream(out);
byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath());
response200Header(dos, body.length);
responseBody(dos, body);
}-
접근하고 있는 사용자가 “로그인” 상태일 경우 (이때는 쿠기 값이 logined=true) 사용자 목록 URL(http://localhost:8080/user/list)로 접근했을 대 사용자 목록을 출력한다. 만약 로그인한 사용자가 아니라면 로그인 페이지(login.html)로 이동한다.
-
우선 로그인한 상태인지 아닌지를 boolean 변수를 통해 판단한다.
while (!headerMessage.equals("")) {
headerMessage = br.readLine();
if (headerMessage.contains("Cookie")) {
logined = isLogin(headerMessage);
}
}
// 쿠키가 있는지 판별하는 함수
private boolean isLogin(String headerMessage) {
String[] headerTokens = headerMessage.split(":");
Map<String, String> cookies = HttpRequestUtils.parseCookies(headerTokens[1].trim());
String value = cookies.get("logined");
if (value == null) return false;
return Boolean.parseBoolean(value);
}- 만약 로그인한 상태라면 테이블을 만드는 html 코드를 데이터로 담아 요청을 보내며 로그인 하지 않았다면 로그인 페이지로 이동한다
else if ("/user/list".equals(url)) {
if (!logined) {
responseResource(out, "/user/login.html");
}
Collection<User> users = DataBase.findAll();
StringBuilder sb = new StringBuilder();
sb.append("<table border='1'>");
for (User user : users) {
sb.append("<tr>");
sb.append("<td>" + user.getUserId() + "</td>");
sb.append("<td>" + user.getName() + "</td>");
sb.append("<td>" + user.getEmail() + "</td>");
sb.append("<tr>");
}
sb.append("</table>");
byte[] body = sb.toString().getBytes();
DataOutputStream dos = new DataOutputStream(out);
response200Header(dos, body.length);
responseBody(dos, body);
}-
이때 쿠키는 프로젝트를 종료하고 다시 웹 서버를 실행하도 남아 있기 때문에 개발자 도구를 이용하여 쿠키를 지워줘야 한다.
- 개발자 도구 → Application → Cookies → URL에 마우스 우클릭 → Clear
-
본 예제에서는 로그인한 정보를 쿠키에 담아 클라이언트에 보관했다. 이는 보안 이슈로 인해 좋지 않은 방법이며 보통 로그인 정보는 상태 데이터를 서버에 저장하는 Session에 담는 것이 관례이다.
-
이클립스 log Message를 보면 CSS 파일을 정상적으로 요청함을 알 수 있다.
-
하지만 CSS 적용이 안되는 것을 알 수 있는데, 이는 모든 컨텐츠의 타입을 text/html로 보내기 때문이다.
-
브라우저는 응답을 받은 후 Content-Type 헤더 값을 통해 Body에 포함되어 있는 컨텐츠가 어떤 것인지 판단한다.
-
그러나 지금까지 구현한 응답은 text/html로 고정되어 있어서 브라우저는 CSS 파일도 HTML로 인식했기 대문에 동작하지 않았다.
-
이는 CSS 요청에 대해 Content-Type 헤더 값을 text/html이 아니라 text/css로 응답을 보내면 해결할 수 있다.
else if (url.endsWith(".css")) {
DataOutputStream dos = new DataOutputStream(out);
byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath());
response200CssHeader(dos, body.length);
responseBody(dos, body);
}
private void response200CssHeader(DataOutputStream dos, int length) {
try {
dos.writeBytes("HTTP/1.1 200 OK \r\n");
dos.writeBytes("Content-Type: text/css\r\n");
dos.writeBytes("Content-Length:" + length + " \r\n");
dos.writeBytes("\r\n");
} catch (IOException e) {
log.error(e.getMessage());
}
}-
빌드 관리 도구
- 프로젝트와 관련한 설정을 관리하면서 소스코드에 대한 컴파일, 컴파일을 위해 필요한 라이브러리 관리, 테스트, 배포를 위한 패키징 작업 등의 작업을 자동화할 수 있도록 지원하는 도구
-
Maven
- Java 전용 프로젝트 관리 도구로, Lifecycle 관리 목적 빌드 도구이며, Apache Ant의 대안으로 만들어졌다.
- XML을 기반으로 만들어진다.
- 상속 구조를 가진다.
- 다음의 8단계의 LifeCycle을 가진다.
- clean : 빌드 시 생성되어있었던 파일들을 삭제한다.
- validate : 프로젝트가 올바른지 확인하고 필요한 모든 정보를 사용할 수 있는지 확인하는 단계
- compile : 프로젝트 소스코드를 컴파일 하는 단계
- test : 단위 테스트를 수행하는 단계. 테스트 실패 시 빌드 실패로 처리하며, 스킵이 가능하다.
- package : 실제 컴파일된 소스 코드와 리소스들을 jar, war 등의 파일의 배포를 위한 패키지로 만든다.
- verify : 통합 테스트 결과에 대한 검사를 실행하여 품질 기준을 충족하는지 확인한다.
- site : 프로젝트 문서와 사이트 작성, 생성하는 단계
- deploy : 만들어진 package를 원격 저장소에 release하는 단계
- 필요한 라이브러리를
pom.xml에 기술하며 Project Modeling이라고 한다. - pom → (Project Object Model)
-
Gradle
- Maven을 대체할 수 있는 프로젝트 구성 관리 및 범용 빌드 툴이며 Ant Builder와 Groovy Script를 기반으로 구축되어 기존 Ant의 역할과 배포 스크립의 기능을 모두 사용가능하며 스프링부트와 안드로이드에서 사용된다.
- XML을 사용하는 Maven보다 간결한 정의가 가능하다.
- 프로젝트 설정 주입 방식으로 정의하여 Maven의 상속 구조보다 재사용에 용의하다.
- Gradle 설치 없이
Gradle Wrapper를 이용하여 빌드를지원
- Application이 정상적으로 동작하는지, 혹은 문제가 발생했을 떄 원인을 파악하기 위해 System.out.print API를 많이 사용한다.
- 하지만 이는 파일로 메시지를 출력하는 작업이기 때문에 많은 비용이 발생하여 성능 저하를 일으킬 수 있다.
- 또 디버깅을 목적으로 사용하는 경우 원복해야하는 문제점도 있다.
- 이를 보완하기 위해 등장한 라이브러리가
Logging라이브러리이다. - 이 중 SLF4J 라이브러리를 이용해 로깅 API에 대한 창구를 일원화했다.
- 즉 SLF4J를 이용하여 디버깅 메세지를 남기면 디버깅 메시지를 출력하는 구현체는 Log4J, Logback이 담당하는 방식으로 동작하며, 추후 다른 라이브러리로 교체한다 해도 소스코드를 수정할 필요 없이 구현체를 담당하는 라이브러리만 교체하면 된다.
<!-- Log Back 의존성 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>// SLF4J를 import 하여 log 라이브러리 사용
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log = LoggerFactory.getLogger(RequestHandler.class);-
자바 진영의 로깅 라이브러리는 메시지 출력 여부를 로그 레벨로 관리한다.
- TRACE < DEBUG < INFO < WARN < ERROR가 있으며, 이 순서대로 로그 레벨이 높아진다.
- 로그 레벨이 높아질 수록 출력되는 메시지는 적어지고, 로그 레벨이 낮을 수록 더 많은 로깅 레벨이 출력된다.
- 실습에서 진행했던 Handler의
log.debug()는 DEBUG 레벨이며 log.trace(),log.info(),log.warn(),log.error()메소드가 있다.
-
이 로그 레벨 설정은 logback.xml 파일에서 설정할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%-5level] [%thread] [%logger{36}] - %m%n</Pattern>
</layout>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>

















