HTTP (HyperText Transfer Protocol) 특징

  • 클라이언트 서버 구조
  • 무상태 프로토콜 (Stateless)
  • 비 연결성 (Connectionless)
  • 단순함, 확장가능

 

Stateless ?

Stateful과는 반대로 서버에서 클라이언트의 상태를 저장하지 않는 것을 말합니다.

Stateless 구조에서 서버는 단순하게 요청에 대해 응답을 하는 역할만 하고, 세션 관리는 Client에게 있습니다.

즉, 서버의 응답이 클라이언트의 세션 상태와 독립적입니다.

따라서, 이러한 Stateless 구조는 Client와의 세션 정보를 서버에 저장하지 않습니다. 다만, 필요에 따라 DB에 저장하며 관리할 수 있습니다.

 

아래와 같이 응답할 수 있는 서버 3개가 있고 중계서버(LoadBalancer 역할)가 있는 구조를 살펴보겠습니다.

 

로그인과 같은 세션관리가 필요한 서비스에 대한 요청이 서버1에 들어왔다고 가정해보겠습니다.

Stateful 구조에서는 서버에 세션 정보를 저장했지만, Stateless 구조에서는 서버에 세션 정보를 저장하지 않고 DB에 저장합니다.

따라서 클라이언트1의 요청이 어떤 서버로 전달되던지 클라이언트1 정보에 접근할 수 있습니다.

 

이렇게 서버에서 세션 정보를 관리하지 않기 때문에 로드밸런서가 최초 서버로 계속 연결되도록 신경 쓸 필요가 없고

Scale-out에 유리합니다. (Stateful 같은 경우에는 최초 서버와 연결 되도록 해야하고 Scale-out시 세션정보를 옮겨주는 부가적인 작업 필요)

대표적인 Stateless 프로토콜로 HTTP, UDP가 있습니다.

 

 

비연결성 (Connectionless)

HTTP는 서버에서 클라이언트와의 연결을 계속 유지하는게 아니라 데이터를 주고 받으면 연결을 종료하는 모델입니다.

일반적으로 초 단위 이하의 빠른 속도로 응답하기 때문에 서버 자원을 효율적으로 사용할 수 있습니다. (연결 유지 X 때문에)

 

비연결성 (Connectionless) 한계 와 극복

  • TCP/IP 연결을 새로 맺어야 한다 - 3 way handshake 시간 추가
  • 웹 브라우저로 사이트를 요청하면 HTML 뿐만 아니라 JS, css, 추가 이미지 등 수 많은 자원이 함께 다운로드된다 (이때마다 다시 연결)
  • 자원을 보낼 때마다 연결을 반복하는 것은 비효율적이기 때문에 지금은 HTTP 지속 연결(Persistent Connections)로 문제를 해결한다.

 

참고 :

 

인프런 : 모든 개발자를 위한 HTTP 웹 기본 지식 - 김영한 

 

https://5equal0.tistory.com/entry/StatefulStateless-Stateful-vs-Stateless-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%99%80-HTTP-%EB%B0%8F-REST

 

[Stateful/Stateless] Stateful vs. Stateless 서비스와 HTTP 및 REST

Contents 0. Prologue 1. Stateful Service 2. Stateless Service 3. Why Stateless Service 4. Stateless Service 및 HTTP, 그리고 REST Stateful/Stateless 서비스의 개념을 알아보고, 이를 바탕으로 Stateless..

5equal0.tistory.com

 

'HTTP' 카테고리의 다른 글

URI ? URN ? URL ?  (0) 2021.12.15

URI (Uniform Resource Idenfier) ? 

URI는 단어 뜻대로 리소스를 나타내는 유일한 식별자를 말합니다. 그리고 URI는 URL, URN 두 종류가 있습니다.

여기서 Resource는 html 파일, text 파일, 이미지 파일 등 같은 정적 컨텐츠 뿐만 아니라 은행장고, 장바구니 같은 요청에 따라 결과값이 달라지는 동적 컨텐츠도 포함합니다.

 

URL (Umniform Resource Locator) ?

URL은 특정서버의 리소스에 대해 위치를 나타낸 것입니다..

 

아래는 URL 포맷입니다.

scheme://[userinfo@]host[:port][/path][?query][#fragment]

  • scheme : 프로토콜을 나타냅니다. (ex. http, https, ftp 등)
  • userinfo : URL에 사용자정보를 포함해서 인증
  • host       : 호스트명(도메인명 또는 IP 주소를 직접 사용)
  • PORT     : 접속 포트
  • path       : 리소스 경로   (ex. /manage/post)
  • query     : key=value 형태로 웹서버에 제공하는 파라미터 (ex. ?q=hello&hl=ko)
  • fragment : html 내부 북마크 등에 사용하는 것으로 서버에 전송하는 정보는 아닙니다. 

 

URN (Uniform Resource Name) ?

urn:example:animal:ferret:nose <- 처럼 실제로 리소스에 주소가 아닌 이름을 부여하는 것입니다.

URL은 리소스의 위치가 이동하게 되면 URL이 더이상 유효하지 않게 됩니다.

하지만 URN은 위치를 표현한게 아니라 이름을 부여한 것이기 때문에 리소스를 옮기더라도 찾을 수 있어야합니다.

그렇게 되려면 Name을 넣었을 때 리소스 결과가 나오도록 매핑이 되어있어야하는데 이게 보편적이지 않고 어려워서 잘 사용하지 않습니다.

'HTTP' 카테고리의 다른 글

HTTP 정리  (0) 2021.12.15

Git에 Push 이벤트 발생시 자동으로 Jenkins 빌드까지 했으니 이번 포스팅에서는 Jenkins에서 EC2 서버 자동배포까지 해보겠습니다.

 

1. Jenkins SSH 설치

 

EC2 서버 배포를 위해 Jenkins 관리 -> 플러그인 관리에서 Publish Over SSH를 설치합니다.

 

 

2. SSH 연결 서버 정보 입력

 

Name Job에서 표시할 이름
Hostname 서버 IP
Username SSH 접근 계정 이름
Remote Directory 업로드될 디렉토리

 

Jenkins 관리 -> 시스템 설정에서 배포할 서버 정보를 입력해 줍니다.

정보를 모두 입력했다면 Use password authentication, or use a different key 를 체크해줍니다.

그리고 고급을 눌러서 EC2를 생성할 때 받은 pem 키를 열어서 해당 값을 Key 부분에 전부 넣어줍니다. 

 

 

마지막으로 Test Configuration을 눌러서 Success 가 나오는 것을 확인하고 저장합니다.

 

만약 Success가 나오지 않는다면 위에 입력한 정보가 맞는지, 배포할 EC2 서버에 SSH(22) 포트가 열려있는지 확인해야합니다.

2. Build 스크립트 작성 


Jenkins 프로젝트 -> 구성에 들어가서 빌드 스크립트를 작성해줍니다.

 

저는 배포 후 수행할 스크립트를 프로젝트에서 관리하도록 했기 때문에 deploy 폴더를 생성해 jar, 스크립트 파일을 SSH로 전송하도록 작성했습니다.

 

 

Sources files 배포할 폴더의 위치
Remove prefix 배포할 파일만 전송할 수 있도록 prefix는 제외하는 역할을 합니다.
A/B/*를 배포할 때 B/*만 배포하고 싶다면 A/를 넣어주면 됩니다.
Remote directory 업로드될 경로
Exec command 전송이 끝난 후 실행할 명령어

 

 

3. 배포 후 실행할 스크립트 작성

 

위에 언급한 것처럼 프로젝트에서 scripts 폴더 생성 및 delpoy.sh 이라는 스크립트를 작성했습니다.

#!/bin/bash

REPOSITORY=/home/ubuntu/test
PROJECT_NAME=jenkins-springboot

echo ">>> Build 파일 복사"

cp $REPOSITORY/deploy/*.jar $REPOSITORY/

echo ">>> 현재 구동중인 애플리케이션 pid 확인"

CURRENT_PID=$(pgrep -fl jenkins-springboot | grep java | awk '{print $1}')

echo ">> PID : " $CURRENT_PID

if [ -z "$CURRENT_PID" ]; then
  echo "구동중인 애플리케이션 없음."
else
  echo ">>> kill -15 $CURRENT_PID"
  kill -15 $CURRENT_PID
  sleep 10
fi

echo ">>> 애플리케이션 배포"

JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)

echo ">>> JAR NAME : $JAR_NAME"

chmod +x $JAR_NAME

echo ">>> $JAR_NAME 실행"

nohup java -jar \
    -Dspring.config.location=classpath:/application.properties \
    $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

 

배포가 완료되면 자동으로 deploy.sh 이 실행되어 스프링부트가 실행되는 것을 확인할 수 있습니다.

 

참고 :

https://wellbell.tistory.com/9?category=976634 

 

6. 깃허브 연동된 젠킨스를 통해 AWS EC2 서버에 deploy하기

깃허브와 연동 이전에 포트포워딩을 통해서 가상머신위 우분투에 접근할수있도록 설정해야한다. 포트포워딩에 대한 내용은 다루지 않고 참조한 주소만 첨부하겠습니다. 참고로 아래 글의 포워

wellbell.tistory.com

 

'CI CD' 카테고리의 다른 글

Jenkins + SpringBoot + AWS EC2 배포 (2)  (0) 2021.11.28
Jenkins + SpringBoot + AWS EC2 배포 (1)  (0) 2021.10.28
어플리케이션 배포 전략  (0) 2021.10.23

이번 포스팅에서는 Webhook을 사용해서 Github에서 Push 이벤트가 발생할 때 자동으로 Jenkins 가 자동으로 빌드하도록 설정해보겠습니다.

 

1. Webhook 이란 ?

Webhook은 이벤트 핸들러입니다.

Github에서 Push 이벤트가 발생했을 때 설정된 EndPoint로 Post요청을 보내게 되고 Jenkins는 빌드를 수행하게 됩니다.

 

https://simsimjae.medium.com/%EC%9B%B9%ED%9B%85%EC%9D%B4%EB%9E%80-e41cf1ba92f0

 

웹훅이란?

위 사진은 웹훅을 정말 잘 설명해주고 있다.

simsimjae.medium.com

 

2. Jenkins 설정

-  Jenkins 프로젝트 -> 구성 -> GitHub hook trigger for GITScm polling 를 체크하고 저장합니다.

3. Github Repository Webhook 설정

- Repository -> Settings -> Webhook에서 Add Webhook 을 클릭한 후 

젠킨스 주소 +/github-webhook/을 추가해줍니다.

 

 

그리고 Push를 하면 빌드가 자동으로 되는 것을 확인할 수 있습니다.

'CI CD' 카테고리의 다른 글

Jenkins + SpringBoot + AWS EC2 배포 (3)  (1) 2021.11.30
Jenkins + SpringBoot + AWS EC2 배포 (1)  (0) 2021.10.28
어플리케이션 배포 전략  (0) 2021.10.23

EC2 Ubuntu Server 프리티어에서 Jenkins를 설치하고 빌드 할때마다 멈추는 현상이 발생했습니다.

검색 결과 메모리 부족라는 걸 알게 되었고 Swap 메모리 할당으로 해결했습니다.

 

Swap 메모리란 ?

RAM 용량이 부족할 경우  HDD의 일부를 RAM 처럼 사용하는 것입니다. 

이걸 사용해서 RAM 용량이 부족하여 시스템에 문제가 생기는 일을 방지할 수 있습니다.

 

Swap 공간 크기 계산

AWS EC2 에서 아래와 같이 권장 스왑 공간을 제공하고 있습니다.

 

시스템 RAM의 양권장 스왑 공간
RAM 2GB 이하 RAM 용량의 2배(최소 32MB)
RAM 2GB 초과, 32GB 미만 4GB + (RAM – 2GB)
RAM 32GB 이상 RAM 용량의 1배
 

Swap 메모리 할당 방법

1. dd 명령을 사용해서 루트 파일 시스템에 스왑 파일을 생성합니다

bs 는 블록 크기, count는 블록 수 입니다. 스왑 파일의 크기는 bs * count 입니다.

 

아래 dd 명령어의 스왑 파일은 프리티어 권장 스왑 공간인 2GB(128M x 16) 입니다.

sudo dd if=/dev/zero of=/swapfile bs=128M count=16

 

2. 스왑 파일의 읽기 및 쓰기 권한을 업데이트합니다.

sudo chmod 600 /swapfile

3. Linux 스왑 영역을 설정합니다.

sudo mkswap /swapfile

4. 스왑 공간에 스왑 파일을 추가하여 스왑 파일을 즉시 사용할 수 있도록 합니다.

sudo swapon /swapfile

5.  작업이 제대로 수행되었는지 확인합니다.

sudo swapon -s

6. /etc/fstab 파일을 편집해서 부팅 시 스왑 파일을 활성화합니다.

#명령어로 파일을 열고
sudo vi /etc/fstab
#fstab 파일에 아래 줄을 추가 후 저장, 종료합니다
/swapfile swap swap defaults 0 0

 

* 참고

https://tape22.tistory.com/22

 

EC2 프리티어에서 Jenkins가 돌아가지 않는 문제 해결하기

❗️앗 프리티어 사양이 코딱지다~! 🥲 프리티어 계정은 램이 1GB입니다. jenkins를 돌리기엔 매우 나약나약한.....나약한.....그래서 처음에 jenkins 설정을 세팅하고 빌드를 했는데 ec2 터미널이 먹통

tape22.tistory.com

https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-memory-partition-hard-drive/

 

하드 드라이브의 파티션을 사용하여 Amazon EC2 인스턴스의 스왑 공간으로 메모리 할당

하드 드라이브의 파티션을 사용하여 Amazon Elastic Compute Cloud(Amazon EC2) 인스턴스에서 스왑 공간으로 사용할 메모리를 할당하려고 합니다. 어떻게 해야 합니까?

aws.amazon.com

 

https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-memory-swap-file/

 

스왑 파일을 사용하여 Amazon EC2 인스턴스의 스왑 공간으로 메모리 할당

1.    dd 명령을 사용하여 루트 파일 시스템에 스왑 파일을 생성합니다. 명령에서 bs는 블록 크기이고 count는 블록 수입니다. 스왑 파일의 크기는 dd 명령의 블록 크기 옵션에 블록 수 옵션을 곱

aws.amazon.com

 

'AWS' 카테고리의 다른 글

Mac에서 AWS EC2 Ubuntu GUI 접속하기  (0) 2021.11.02
  1. EC2 Ubuntu Instance을 SSH 로 접속합니다.
    •  접속 방법은 해당 인스턴스에서 연결을 클릭하면 확인할 수 있습니다.
  2. Ubuntu-desktop 설치 및 tightvncserver를 설치합니다.
sudo apt update
sudo apt install ubuntu-desktop
sudo apt install tightvncserver
sudo apt install gnome-panel gnome-settings-daemon metacity nautilus gnome-terminal

   3. vncserver 를 터미널에 입력해서 실행합니다.

          - 이 때 Password를 설정하라고 나올텐데 원격 접속시 사용할 Password라서 잊어먹으면 안됩니다.

 

   4. vnc config 파일을 열어줍니다.

sudo vi ~/.vnc/xstartup

xstartup 파일을 아래와 같이 수정해줍니다.

#!/bin/sh
# Uncomment the following two lines for normal desktop:
# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc
[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey vncconfig -iconic &
x-terminal-emulator -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
x-window-manager &
gnome-panel &
gnome-settings-daemon &
metacity &
nautilus &
gnome-terminal &

 5. 설정 적용을 위해 vncserver를 재시작 해줍니다.

vncserver -kill :1
vncserver

6. AWS 로그인 후 보안그룹에서 인바운드 규칙에서 5901 포트를 열어줍니다.

7. Finder에서 command+k로 VNC 실행 후 vnc://우분투 instance Ip:5901로 연결해서 3번에서 입력한 비밀번호로 로그인하면 끝!

 

'AWS' 카테고리의 다른 글

EC2 프리티어에서 Jenkins 가 동작하지 않을 때 해결법  (0) 2021.11.11

 

Docker 로 Jenkins 컨테이너를 생성 후 AWS EC2 서버에 자동 배포까지 해보겠습니다. 

이번 포스팅에서는 Jenkins 실행 후 GitHub 연동까지 하도록 하겠습니다.

 

  1. Docker 를 설치 후 docker pull jenkins/jenkins:lts로 젠킨스 이미지를 받습니다.
  2. Jenkins 이미지를 컨테이너로 실행합니다. docker run -d -p 9090:8080 -u root jenkins/jenkins:lts
    1. docker images로 이미지를 확인할 수 있다.
      1. -d 백그라운드 실행
      2. -p 포트 지정 
    2. 그리고 도커 컨테이너에 접속해서 암호를 가지고 와서 화면에 입력합니다.
      1. docker exec -it 20e3e7d7b78f /bin/bash
      2. cat /var/jenkins_home/secrets/initialAdminPassword
  3. 정보들을 입력후 Jenkins 페이지가 뜨면 새로운 Item 을 클릭합니다.

   4. Item 이름을 입력하고 Freestyle project를 선택합니다.

 

   5. 연동할 Github Project URL과 Repository URL을 입력합니다.

   6. Build에서 Execute Shell을 선택하고 빌드시 사용할 스크립트를 작성합니다.

  7. Jenkins에서 생성한 Item에 들어가 Build Now를 클릭해주면 빌드가 진행됩니다.

8. Build 완료 후 클릭해서 Console Output을 클릭하면 Build 수행 로그를 확인할 수 있습니다.

 

 

* 혹시 EC2 프리티어에서 Jenkins를 실행중이고 Build시 젠킨스가 죽는 현상이 발생하면 https://ywook.tistory.com/35 참고하시면됩니다.

'CI CD' 카테고리의 다른 글

Jenkins + SpringBoot + AWS EC2 배포 (3)  (1) 2021.11.30
Jenkins + SpringBoot + AWS EC2 배포 (2)  (0) 2021.11.28
어플리케이션 배포 전략  (0) 2021.10.23

요즘 소프트웨어 개발에 가장 큰 변화는 배포 빈도입니다. 운영팀은 릴리즈를 운영에 더 자주, 빠르게 배포합니다

달 혹은 년 릴리즈 주기는 드물어 지고 있습니다.

오늘날, 서비스 지향 아키텍처 및 마이크로서비스 접근 방식을 사용하여 코드 기반의 모듈을 설계할 수 있습니다.

이를 통해 소스 수정과 배포를 동시에 할 수 있습니다.

하지만, 이런 변화는 운영, DevOps팀에 새로운 문제를 만듭니다. 잦은 배포는 사이트의 신뢰도와 고객들에게 부정적인 영향을 미칠 수 있습니다. 그래서 리스크를 최소화하는 배포 전략이 중요합니다.

Big Bang Deployment

Big Bang 배포는 어플리케이션의 전체 혹은 대부분을 한번에 업데이트 합니다. 그래서 릴리즈 하기전 광범위한 개발과 테스트 수행을 필요로 합니다.

 

특징

  • 하나의 배포에 모든 중요 부분이 포함됨
  • 기존 소프트웨어 버전을 한번에 새로운 것으로 교체함
  • 오랜기간 개발 및 테스트를 거치고 배포함
  • 롤백이 불가능할 수 있으므로 실패를 최소로 가정한다.
  • 배포 완료 시간이 매우 오래걸린다.

Rolling Deployment

Rolling 배포는 어플리케이션의 버전을 점진적으로 교체하는 방식이다. 실제 배포는 오랜 기간동안 진행된다. 배포 동안 old, new 버전은 user experience, 기능에 영향을 주지 않으면서 동시에 존재하게 된다. 이러한 프로세스를 통해 새로운 component에 대해 더 쉽게 롤백할 수 있다.

즉, 서버를 한 대씩 구 버전에서 새 버전으로 교체해가는 배포전략이다. 이와 같은 방식은 서버 수의 제약이 있을 경우 유용하나 배포 중 인스턴스의 수가 감소하므로 서버 처리 용량을 미리 고려해야 한다.

Blue-Green

이 방법에서는 두개의 동일한 운영환경의 서버가 병렬로 동작한다.

하나는 현재 모든 트래픽을 받고 있는 운영 서버이고, 다른 하나는 운영 서버와 동일한 클론 서버다. 둘 다 모두 동일 환경, 설정을 가지고 있다.

새로운 버전 어플리케이션이 클론서버에 배포되고 기능 및 성능 테스트가 진행된다. 테스트 후 어플리케이션 트래픽을 기존 운영서버에서 새로운 클론서버로 전환한다.

만약 클론서버에서 문제가 발생할 경우 기존 운영서버로 트래픽을 전환하면 된다.

 

특징

  • 빠른 롤백이 가능하다.
  • 운영 환경에 영향을 주지 않고 실제 서비스 환경으로 테스트 할 수 있다.
  • 자원이 두배로 필요하다.

Canary Deployment

Canary

  • 우리가 흔히 알고 있는 카나리아 새를 의미한다. canary라 쓰는데 새 이름은 카나리아이다.
  • 탄광에서 유독가스의 누출 위험을 알리는 용도로 사용됨
  • 유독가스가 많이 누출되어 사람이 느낄 수 있게 되면 매우 위험한 상태
    • 유독가스에 민감한 카나리는 그 보다 먼저 반응하여 죽기 때문에, 누출을 사전에 인지 가능

새로운 어플리케이션 코드를 운영 인프라 일부에만 배포한다.

그리고 일부 트래픽을 새 버전으로 분산하여 영향을 최소화 한다.

오류가 없을 경우 새로운 버전의 코드는 점진적으로 나머지 인프라에 점진적으로 롤아웃한다.

Canary Deployment의 주요 챌린지는 일부 사용자를 새로운 어플리케이션으로 라우팅하는 것이다.

아래와 같은 방법으로 일부 사용자를 라우팅한다.

  • 외부 사용자가 접속하기 전에 내부 사용자에게만 라우팅한다.
  • Source IP ragnge를 기반으로 라우팅한다.
  • 특정 지역에만 어플리케이션을 릴리즈 한다.
  • 어플리케이션 로직을 사용하여 특정 유저, 그룹에게만 새로운 기능을 사용할 수 있도록 한다.

특징

  • 카나리 버전에 심각한 버그가 발생된다고 해도 사용하는 사용자가 적기 때문에 피해 최소화 가능
  • 안정적인 버전과 테스트 버전이 모두 배포된 상태이기 때문에 A/B 테스트, 블루그린 배포 가능

 

참고

- https://dev.to/mostlyjason/intro-to-deployment-strategies-blue-green-canary-and-more-3a3

 

Intro to deployment strategies: blue-green, canary, and more

With modern CI/CD and microservices, developers deploy more frequently, but they must avoid breaking production. We describe deployment strategies so you can deploy with confidence.

dev.to

- https://onlywis.tistory.com/10

 

배포 전략: Rolling, Blue/Green, Canary

예전에는 수 개월(혹은 수 년)에 한 번씩 서비스를 릴리즈 했었지만, 최근에는 서비스를 더 작게 만들고(마이크로서비스) 더 자주 배포(Deployment) 하는 방식으로 변화하고 있다. 이러한 트렌드에

onlywis.tistory.com

 

 

- https://itwiki.kr/w/%EC%B9%B4%EB%82%98%EB%A6%AC

 

카나리 - IT위키

 

itwiki.kr

 

'CI CD' 카테고리의 다른 글

Jenkins + SpringBoot + AWS EC2 배포 (3)  (1) 2021.11.30
Jenkins + SpringBoot + AWS EC2 배포 (2)  (0) 2021.11.28
Jenkins + SpringBoot + AWS EC2 배포 (1)  (0) 2021.10.28

인터페이스와 람다 표현식

 

 

핵심 내용 정리

  1. 인터페이스는 구현 클래스에서 반드시 구현해야 하는 메소드를 명시
  2. 인터페이스는 해당 인터페이스를 구현하는 모든 클래스의 슈퍼타입
  3. 인터페이스는 정적 메소드를 포함할 수 있다. 인터페이스의 모든 변수는 자동으로 public static final이다.
  4. 인터페이스는 구현 클래스에서 상속하거나 오버라이드할 수 있는 기본 메소드를 포함할 수 있다.
  5. 인터페이스는 구현 클래스에서 호출하거나 오버라이드할 수 없는 비공개 메소드를 포함할 수 있다.
  6. 함수형 인터페이스는 단일 추상 메소드를 가진 인터페이스이다.
  7. 람다 표현식은 나중에 실행할 수 있는 코드 블록이다.
  8. 람다 표현식은 함수형 인터페이스로 변환된다.
  9. 메소드 참조와 생성자 참조는 메소드와 생성자를 호출하지 않고 참조한다.
  10. 람다 표현식과 지역 클래스는 자신을 감싸는 유효 범위에 있는 사실상 최종 변수에 접근할 수 있다.

 

 

3. 인터페이스

public interface IntSequence {
	boolean hasNext();
	int	next();
}
  • 기본 구현을 작성하지 않고 선언만 한 메소드를 추상(abstract) 메소드라고 한다.
  • 인터페이스의 모든 메소드는 자동으로 public이 된다. 그러므로 hasNext와 next를 public으로 선언할 필요 없다.
  • 인터페이스를 구현하는 클래스는 인터페이스의 메소드를 반드시 public으로 선언해야한다. 그렇지 않으면 클래스의 메소드는 기본적으로 패키지 접근이 된다. 하지만 인터페이스는 공개 접근을 요구하므로 컴파일로가 오류 발생
  • 클래스가 인터페이스의 메소드 중 일부만 구현한다면 해당 클래스는 반드시 abstract 제어자로 선언해야 한다.
  • 인터페이스는 또 다른 인터페이스를 extend해서 원래 있던 메소드 외의 추가 메소드를 요구하거나 제공할 수 있다.
  • 클래스는 인터페이스를 몇 개든 구현할 수 있다. (2개를 implements 하면, Super type 을 두개 둔다.)
  • 인터페이스에 정의한 변수는 자동으로 public static final이 된다.

 

3.2.1 정적메소드

팩토리 메소드는 인터페이스에 아주 잘 맞는다.

public interface IntSequenct {
	static IntSequence digitsOf(int n){
		return new DigitSequenct(n);
	}
}

3.2.2 기본 메소드

기본 메소드에는 반드시 default 제어자를 붙여야한다.

public interface IntSequence {
	default boolean hasNext() { return true;}
}

이 인터페이스를 구현하는 클래스는 hasNext 메소드를 오버라이드하거나 기본 구현을 상송하는 방법 중 하나를 선택할 수 있다.

 

* 기본 메소드 덕분에 자바 API의 Collection/AbstractCollenction이나 WindowListener/WindowAdapter처럼 인터페이스와 해당 인터페이스의 메소드를 대부분 또는 모두 구현한 동반 클래스를 제공하던 고전적인 패턴에 종지부를 찍을 수 있었다.

 

3.2.3 기본 메소드의 충돌 해결

 

클래스가 두 개의 인터페이스를 구현한다. 그런데 한 인터페이스에는 기본 메소드가 있고, 다른 한 인터페이스에는 이 메소드와 이름, 매개변수 타입이 같은 메소드가 있다면 반드시 충돌을 해결해야한다.

public interface Person{
	default int getId() { return 0 ; }
}

public interface Identified{
	default int getId() { return Math.abs(hashCode();}
}

public class Employee implements Person, Identified {
	…
}

위의 경우 컴파일러가 하나를 우선해서 선택하지 못한다. 따라서 이 문제를 해결하려면 Employee 클래스에 getId 메소드를 추가한 후 고유의 ID 체계를 구현하거나, 다음과 같이 충돌한 메소드 중 하나에 위임해야 한다.

public class Employee implements Person, Identified {
	public int getId() { return Person.super.getId();}
}

* 두 인터페이스 모두 공유 메소드의 기본 구현을 제공하지 않으면 충돌이 일어나지 않는다. 이 경우 구현 클래스에 메소드를 구현하거나 메소드를 구현하지 않고 클래스를 abstract로 선언하면 된다.

 

3.2.4 비공개 메소드

 

자바 9부터 인터페이스에 비공개 메소드를 만들 수 있다. 비공개 메소드는 static이나 인스턴스 메소드는 될 수 있지만, default 메소드 (오버라이드가 가능하므로) 될 수 없다.

비공개 메소드는 인터페이스 자체에 있는 메소드에서만 쓸 수 있으므로, 인터페이스 안에 있는 다른 메소드의 헬퍼 메소드로만 사용할 수 있다.

 

예를 들어 IntSequence 인터페이스가 다음 메소드를 제공한다고 하자.

static of(int a)
static of(int a, int b)
static of(int a, int b, int c)

이 메소드들은 다음 헬퍼 메소드를 호출할 수 있다.

private static IntSequence makeFiniteSequence(int … values) { … } 

 

3.3.1 Comparable 인터페이스

 

어떤 클래스의 객체를 정렬하려면 해당 클래스가 Comparable 인터페이스를 구현해야 한다. 이 인터페이스와 관련해 기술적으로 중요한 점이 하나 있다. 정렬을 수행할 때 문자열 대 문자열, 직원 대 직원 식으로 비교한다. 그래서 Comparable 인터페이스는 타입 매개변수를 받는다.

public interface Comparable<T> {
	int comparaTo(T other);
}

x.compareTo(y) 를 호출하면 compareTo 메소드는 x와 y 중 어느 것이 앞에 오는지 나타내는 정수 값을 반환한다.

 반환 값이 양수인 경우 x가 y 다음에 온다. 반환 값이 음수면 y가 x 다음에 온다. x, y 값이 같으면 반환 값은 0이다.

 

* Comparable이나 ArrayList처럼 타입 매개변수를 받는 타입은 제네릭(generic)이다.

 

 

3.3.2 Comparator 인터페이스

 

문자열을 사전 순사가 아닌 길이가 증가하는 순서로 비교한다면, String 클래스는  comparableTo 메소드를 두 가지 방법으로 구현하지 못한다. 그리고 String 클래스는 우리가 소유한 클래스가 아니므로 수정할 수도 없다.

 

이런 상황을 다룰 수 있는 Arrays.sort 메소드의 두 번째 버전이 있다. 배열과 비교자(comparator)를 매개 변수로 받는다. (비교자는 Comparator 인터페이스를 구현하는 클래스의 인스턴스다.)

public interface Comparator<T> {
	int compare(T first, T second);
}

문자열을 길이로 비교하려면 Comparator<String>을 구현하는 클래스를 정의해야한다.

class LengthComparator implements Comparator<String>{ 
	public int compare(String first, String second){
		return first.length() - second.length();
	} 
}

Comparator<String> comp = new LengthComparator();
if (comp.compare(word[i], word[j]) > 0) …

word[i].compareTo(word[j])와 비교하면 compare 메소드는 문자열 차제가 아니라 비교자 객체로 호출한다.

 

 

3.3.3 Runnable 인터페이스

 

태스크를 정의하려면 Runnable 인터페이스를 구현해야 한다. Runnable 인터페이스에는 메소드가 한 개만 있다.

class HelloTask implements Runnable{
	public void run(){
    	for( int i = 0; i < 1000; i++){
        	System.out.println("Hello, World!");
		}
	}
}

 

이 태스크를 새 스레드에서 실행하려면 Runnable로 스레드를 생성하고 시작해야 한다.

 

Runnable task = new HelloTask();

Thread thread = new Thread(task);

thread.start();

Runnable task = new HelloTask();
Thread thread = new Thread(task);
thread.start();

 

3.3 람다 표현식

 

람다표현식은 나중에 한 번 이상 실행할 수 있게 전달하는 코드 블록이다. 

 

자바에는 함자바는 거의 모든 것이 객체인 객체 지향 언어이다. 자바에는 함수 타입이 없다. 그 대신 객체(특정 인터페이스를 구현하는 클래스의 인스턴스)로 함수를 표현한다. 람다 표현식은 이런 인스턴스를 생성하는 아주 편리한 문법을 제공한다.

 

3.4.1 람다 표현식 문법

(String first, String second) -> first.length() - second.length()

람다 표현식은 쉽게 말해 코드 블록으로, 해당 코드에 전달해야 하는 변수의 명세까지 갖춘 것이다.

  • 람다 표현식의 바디에서 표현식 하나로는 표현할 수 없는 계산을 수행한다면 메소드를 쓸 때처럼 작성하면 된다.
(String first, String second) -> 
{
	int difference = first.length() - second.length();
	if(difference < 0) return -1;
	else if(difference > 0) return 1;
	else return 0;
}
  • 람다 표현식에 매개변수가 없으면 매개변수가 없는 메소드처럼 빈 괄호를 붙여야 한다.
Runnable task = () -> { for (int i = 0; i < 1000; i++) doWork(); }
  • 람다 표편식의 매개변수 타입을 추론할 수 있다면 다음과 같이 매개변수 타입을 생략할 수 있다.

Comparator<String> comp = (first, second) -> first.length() - second.length();

Comparator<String> comp = (first, second) -> first.length() - second.length();
  • 메소드에 매개변수가 한 개만 있고, 이 매개변수의 타입을 추론할 수 있다면 괄호도 생략할 수 있다.
EventHandler<ActionEvent> listener = event -> System.out.println("Oh");

람다 표현식의 결과 타입은 명시하지 않는다. 하지만 컴파일러는 람다 표현식 바디에서 결과 타입을 추론한 후 기대하는 타입과 일치하는지 검사한다.

 

 

3.4.2 함수형 인터페이스

 

람다 표현식은 단일 추상 메소드를 가진 인터페이스(즉, 추상 메소드가 한 개만 있는 인터페이스) 자리에 사용할 수 있다. 이런 인터페이스를 함수형 인터페이스라고 한다.

 

함수형 인터페이스로 변환하는 것을 알아보기 위해 Arrays.sort 메소드를 생각해 보자. 이 메소드의 두 번째 배개변수는 Comparator의 인스턴스를 요구한다.(Comparator 인터페이스에는 메소드가 하나만 있다.)

이 매개 변수에 다음과 같은 람다를 전달해 보자.

 

Arrays.sort(words, (first, second) -> first.length() - second.length());

Arrays.sort(words, (first, second) -> first.length() - second.length());

내부에서 Arrays.sort 메소드의 두 번째 매개변수는 Comparator<String> 을 구현한 클래스의 객체를 받는다.

이 객체로 compare 메소드를 호출하면 람다 표현식의 바디가 실행된다. 이런 객체와 클래스를 관리하는 일은 순전히 구현체의 몫이며 고도로 최적화되어 있다.

 

함수 리터럴을 지원하는 거의 모든 프로그래밍 언어에서 (String, String) -> int 처럼 함수 타입을 선언하고, 이 함수 타입으로 변수를 선언한 후 함수를 변수에 저장해 호출할 수 있다.

하지만 자바에서는 이 중 하나만 람다 표현식으로 할 수 있다. 

 

바로 람다 표현식을 함수형 인터페이스 타입 변수에 저장해서 해당 인터페이스의 인스턴스로 변환하는 것이다.

 

* 자바에서 Object 타입은 모든 클래스의 Super타입이지만, 람다 표현식은 Object 타입 변수에 저장할 수 없다. -> Object는 함수형 인터페이스가 아니라 클래스이기 때문이다. 

 

3.5.1 메소드 참조

Arrays.sort(strings, (x, y) -> x.compareToIgnoreCase(y));

이 코드 대신 다음 메소드 표현식을 전달할 수 있다.

Arrays.sort(strings, String::compareToIgnoreCase);

 

표현식은 String::compareToIgnoreCase는 람다 표현식 (x,y) -> x.compareToIgnoreCase(y)에 대응하는 메소드 참조다.

list.forEach(x -> System.out.println(x));
list.forEach(System.out::println);

list.removeIf(x -> Objects.isNull(x));
list.removeIf(Object::isNull);

::연산자는 클래스 이름과 메소드 이름을 분리하거나 객체의 이름과 메소드 이름을 분리한다.

  1. Class::instanceMethod
    • 첫 번째 매개변수가 메소드의 수신자가 되고, 나머지 매개변수는 메소드에 전달한다.(String::compareToIgnoreCase == (x,y) -> x.compareToIgnoreCase(y))
  2. Class::staticMethod
    • 모든 매개변수가 정적 메소드로 전달된다. (Objects::isNull == x -> Objects.isNull(x))
  3. object::instanceMethod
    • 주어진 객체로 메소드를 호출하며, 매개변수는 인스턴스 메소드로 전달된다. (System.out::println == x -> System.out.println(x))

 

메소드 참조에서 this 매개변수를 캡처할 수 있다. 예를 들어 this::equals는 x -> this.equals(x)와 같다.

 

*내부 클래스에서 EnclosingClass.this::method로 자신을 감싸는 클래스의 this 참조를 캡처할 수 있다.

*생성자 참조는 Employee::new로 할 수 있다 (names.stream().map(Employee::new)

'Java' 카테고리의 다른 글

자바 클래스 관련 내용 정리  (1) 2020.08.24
Mac에 Java 설치하기  (1) 2017.03.05

2.3.3 다른 생성자에서 특정 생성자 호출

  • 또 다른 생성자에서 어느 한 생성자를 호출할 수 있는데, 호출하는 쪽 생성자 바디의 첫 번째 문장으로만 허용한다.
  • 그리고 호출할 생성자 이름이 아니라 this 키워드를 사용한다.
public Employee(double salary){
	
    this("", salary); //Employee(String, double) 호출
    
    //이후에 다른 문장이 올 수 있다.
}

 

2.3.4 기본 초기화

  • 생성자 안에서 인스턴스 변수를 명시적으로 설정하지 않으면 자동으로 변수를 기본 값으로 설정한다. 숫자는 0, bool 값은 false, 객체 참조는 null이 기본 값

 

2.3.5 인스턴스 변수 초기화

  • 객체를 할당하고 나서 생성자가 실행되기 전에 일어난다.
  • 인스턴스 변수를 선언할 때 초기화하는 방법 외에 클래스 안에 임의의 초기화 블록을 넣는 방법도 있다.
  • 인스턴스 변수 초기화와 초기화 블록 클래스 선언에 나타난 순서로 실행하며, 그 다음에 생성자를 실행한다.
public class Employee(){
	
    private String name = " ";
    private int id;
    private double salary;
    
    { //초기화 블록
    	Random generator = new Random();
        id = 1 + generator.nextInt(1_000_000);
    }
    
    ...
}

 

2.4 정적 변수와 정적 메소드

 

2.4.3 정적 초기화 블록

  • 정적 변수에 초기화 작업이 추가로 필요할 때 사용한다.
  • 정적 초기화는 클래스를 처음 로드할 때 일어난다. 인스턴스 변수와 마찬가지로 정적 변수를 명시적으로 다른 값으로 설정하지 않으면 0이나 false, null이 된다.
  • 모든 정젹 변수는 초기화와 정적 초기화 블록은 클래스 선언 안에 나타난 순서로 실행된다.
public class CreditCardForm{
	
    private static final ArrayList<Integer> expiration = new ArrayList<>();
    static{
    	int year = LocalDate.now().getYear();

		for (int i = year; i <= year + 20; i++){
        	expirationYear.add(i);	
        }
    }
    
    ...
}

2.4.4 팩토리 메소드

  • 정적 메소드는 흔히 팩토리 메소드를 만드는데 사용한다.
  • 팩토리 메소드는 클래스의 새 인스턴스를 반환하는 정적 메소드를 의미한다.
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();

double x = 0.1;

System.out.println(currencyFormatter.format(x)); 

-> 왜 생성자 대신 팩토리 메소드를 사용할까? 

  • 생성자를 구별하는 유일한 방법은 생성자의 매개변수. 타입이다. 따라서 매개변수가 없는 생성자를 두개씩 둘 수 없다.
  • 게다가 new NumberFormat(…) 생성자는 NumberFormat을 돌려준다. 하지만 팩토리 메소드는 서브클래스의 객체를 반환할 수 있다.
  • 그리고 팩토리 메소드를 사용하면 불필요하게 새 객체를 생성하는 대신 공유 객체를 반환할 수 있다. 예를 들어 Collections.emptyList()를 호출하면 변경할 수 없는 빈 리스트(공유객체)를 반환한다.

 

2.6 중첩 클래스

  • 클래스를 다른 클래스 내부에 두는 방법이 있다. 이런 클래스를 중첩 클래스(nested class)라고 한다. 중첩 클래스는 가시성을 제한하거나 Element, Node, Item 처럼 일반적인 이름을 사용하면서도 정돈된 상태를 유지할 때 유용하다.
  • 자바에는 작동 방식이 약간 다른 중첩 클래스가 두 종류 있다.

 

2.6.1 정적 중첩 클래스

public class Invoice{

	private static class Item { //Invoice 내부에 Item 중첩했다.
		String description;
		int quantity;
		double unitPrice;

		double price() {return quantity * unitPrice; }
	}

	private ArrayList<Item> items = new ArrayList<>();
    
	public void addItem(String description, int quantity, double unitPrice) {
		
		Item newItem = new Item();
		
		newItem.description = description;
		newItem.quantity = quantity;
		newItem.unitPrice = unitPrice;
		
		items.add(newItem);
	}
}

Invoice.Item Item = new Invoice.Item();
myInvoice.add(newItem);

 

Invoice.Item 클래스와 다른 클래스 외부에 선언한 InvoiceItem 클래스는 근본적으로 차이가 없다. 클래스 중첩은 그저 Item 클래스가 청구서에 들어 있는 물품을 표현한다는 사실을 분명하게 할 뿐이다.

 

2.6.2 내부 클래스

  • static 을 붙이지 않은 클래스를 내부 클래스(이너 클래스 inner class)라고 한다.

각 회원이 다른 회원과 관계를 맺는 소셜 네트워크를 생각해 보자.

 

public class Network {

	public class Member{  //static 제어자를 빼면 근본적인 차이가 하나 생긴다. 예를 들어 Member 객체는 자신이 어느 네트워크에 속하는지 알게 된다.
		private String name;
		private ArrayList<Member> friends;

		public Member(String name) {
			this.name = name;
			friends = new ArrayList();
		}

		public void deactivate() {
			members.remove(this);
		}

		public boolean belongsTo(Network n){
			return Network.this == n;
		}
	}

	private ArrayList<Member> members = new ArrayList()<>;

	public Member enroll(String name) {
		Member newMember = new Member(name);
		members.add(newMember);
		return newMember;
	}
}

 

다음과 같이 멤버를 추가하고 참조를 얻을 수 있다.

Network myFace = new Network();
Network.Member fred = myFace.enroll("Fred");

이제 Fred는 멤버십을 해지하려고 한다.

fred.deactivate();

 

메소드 구현에서 볼 수 있듯이, 내부 클래스의 메소드는 외부 클래스의 인스턴스 변수에 접근할 수 있다.

이 코드에서는 내부 클래스를 생성한 외부 클래스 객체(myFace 네트워크)의 인스턴스 변수 members이다.

 

바로 이 점이 내부 클래스를 정적 중첩 클래스와 구별시키는 요인이다.

내부 클래스의 각 객체는 외부 클래스의 객체에 대한 참조를 포함한다.

 

members.remove(this); // 이 호출은 실제로 다음을 의미한다.
outer.members.remove(this);

 

여기서는 외부 클래스의 숨은 참조를 outer로 나타냈다.

정적 중첩 클래스에는 이런 참조가 없다.(static 메소드에 this 참조가 없는 것과 마찬가지)

 

중첩 클래스의 인스턴스가 자신을 감싸고 있는 클래스의 어느 인스턴스에 속하는지 알 필요가 없을 때 정적 중첩 클래스를 사용하자.

내부 클래스는 이 정보가 중요할 때만 사용하자.

 

2.6.3 내부 클래스용 특수 문법 규칙

 

앞에서는 내부 클래스 객체의 외부 클래스 참조를 outer로 지칭해서 설명했다. 외부 클래스 참조를 나타내는 실제 문법은 조금 더 복잡하다. 다음 표현식은 외부 클래스 참조를 나타낸다.

 

OuterClass.this

 

예를 들어 내부 클래스 Member의 deactivate 메소드는 다음과 같이 작성할 수 있다.

public void deactivate(){ 
	Network.this.members.remove(this)
}

여기서는 Network.this 문법은 필수가 아니다. 그냥 members로만 참조해도 암묵적으로 외부 클래스 참조를 사용한다.

하지만 외부 클래스 참조가 명시적으로 필요할 때도 있다.

public boolean belongsTo(Network n){
	return Network.this == n;
}

 

* 내부 클래스에는 시간 상수 외에 정적 멤버를 선언할 수 없다.

 

 

'Java' 카테고리의 다른 글

인터페이스와 람다 표현식 (1)  (0) 2020.09.09
Mac에 Java 설치하기  (1) 2017.03.05

+ Recent posts