Shawn is Learning
매일의 기록을 남기기 위해서 시작했습니다.
하루하루 알게된 내용을 기록합니다.
대부분의 글이 정리되지 않은 노트입니다.
Rust 언어로 작성된 mdbook을 사용했습니다.
AWS Cloud 개요
-
클라우드 컴퓨팅?
- 인프라는 더 이상 하드웨어가 아니다.
- 인프라를 서비스로 생각하자.
- IT 리소스와 애플리케이션을 온디맨드로 제공하는 서비스
- 종량 과금제
- 유연성: IT 리소스를 필요한만큼 프로비저닝할 수 있음 => 하드웨어 낭비를 줄임
-
클라우드 컴퓨팅의 종류: 아래 구분은 AWS 이후 타 업체에서 구분한 것임.
- IaaS: 컴퓨터, 네트워크, 스토리지 등 (EC2, EBS, S3, ...)
- PaaS: 인프라 관리할 필요 없이 애플리케이션을 실행 (NoSQL, SQS, ...)
- SaaS: 서비스 제공자가 관리하는 제품을 사용 (Workspaces, ...)
-
이점
- 자본비용의 가변화: 사용한 만큼만 지불
- 규모의 경제: 많이 사용할수록 더 저렴해짐
- 용량 추정 불필요: 필요한 IT 리소스 산정할 필요가 없음
- 속도 및 민첩성 개선: 필요한 리소스를 몇 분 만에 사용할 수 있음
- IDC 투자비용 불필요
- 전세계 배포
-
AWS: 웹 기반 클라우드 서비스
- 관리형 vs 비관리형 서비스
- 비관리형: 사용자가 스케일링, 내결함성, 가용성을 관리
- ex) EC2, 오토 스케일링을 설정하지 않는 한 트래픽 처리량 동일함
- 관리형: 서비스에 내장되어 있음
- ex) S3 정적 웹사이트 호스팅, 서비스가 각 파일의 가용성을 관리
- 비관리형: 사용자가 스케일링, 내결함성, 가용성을 관리
- 관리형 vs 비관리형 서비스
-
클라우드 배포모델
- 올인 클라우드: 애플리케이션의 모든 부분이 클라우드에서 실행
- 애플리케이션이 클라우드에서 생성 or 기존 인프라에서 클라우드로 Mig.
- 낮은 수준의 인프라 상에 구축하거나, 주요 인프라를 관리 설계/확장할 필요가 없음
- 하이브리드: 레거시 리소스와 클라우드 리소스간의 연결
- 클라우드 리소스를 내부 시스템(On-premise)에 연결
- 올인 클라우드: 애플리케이션의 모든 부분이 클라우드에서 실행
-
마이크로서비스 특징
- 민첩성: 서비스별 소규모 독립 팀으로 구성
- 유연한 조정: 기능 요구사항에 맞게 인프라 요구에 맞게 조정
- 손쉬운 배포: CI/CD 가능
- 기술적 자유: 팀에 맞는 최적의 도구를 자유롭게 선택
- 재사용 가능한 코드
- 복원력
-
MSA 모범사례
- 구성요소를 오류없이 변경
- 인터페이스는 계약: 한번 만든 외부 공개 API는 변경하면 안됨
- 기능 변경이 소비자에게 영향을 끼치면 안됨
- 간단한 API를 사용
- 서비스 사용비용을 절감
- 복잡성 증가는 변경에 대한 저항 증가
- 적게 공유할수록 오류도 적음
- 상세 정보를 숨김
- 서버를 상태 비저장 모드로 처리
- 상태를 저장하지 않으면 Auto Scaling을 통해 인스턴스를 쉽게 추가하고 제거할 수 있음
- 구성요소를 오류없이 변경
-
아키텍쳐 진화
- EC2: H/W 추상화 / VM 단위 / 서버, 스토리지, 네트워킹, OS를 구성
- ECS: OS 추상화 / 작업 단위 / 애플리케이션 구성
- Lambda: 실행시간 / 함수 단위 / 필요시 코드를 실행
AWS 인프라 개요
- AWS 데이터 센터
- 단일 데이터 센터에서 수천 개 서버 운영
- 모든 데이터 센터를 온라인으로 연결
- 리전 및 AZ
- 리전: 여러 AZ를 포함하고 있는 지리적 위치
- AZ
- 하나 이상의 데이터 센터로 구성
- 결함 분리 방식으로 설계
- 고속 프라이빗 링크를 통해 다른 AZ와 상호연결
- AWS 내부 통신은 외부와 비교해 빠름
- 리전 선택 방법
- 법적 요구사항: 현지 법에 따라 특정 데이터의 물리적 위치를 강제할 때
- 고객 근접성: 가까울수록 빠르다
- 가용 서비스: 리전에 따라 사용할 수 있는 서비스의 차이가 있음
- 비용: 리전별로 다름 / 제일 저렴? CA / 제일 비싸? SP
AWS 기초 서비스
EC2
- 특징
- 종량 과금제: 인스턴스 사용 시간만큼 비용 지불
- HW/SW 선택의 자유: 원하는 OS, 원하는 스토리지 선택
- Amazon Machine Image: Windows, Ubuntu, RHEL 등 제공하는 이미지 선택 가능
- 필요할 때만 사용: 서버를 인스턴스로 사용
- 인스턴스 유형
- 범용(T2, M5, M4): 트래픽이 적은 웹 서버 및 소형 DB
- 컴퓨팅 최적화(C5, C4): 비디오 인코딩
- 메모리 최적화(X1e, X1, R4): 고성능 DB, 하둡/Spark
- 스토리지 최적화(H1, I3, D2): DW, 로그/데이터 처리 애플리케이션
- 가속 컴퓨팅(P3, P2, G3, F1): ML, 3D App 스트리밍
- 요금
- 단위
- 초 단위 결제: Amazon Linux, Ubuntu
- 시간 단위: 그 외 OS
- 인스턴스 종류
- 온디맨드: 일반 인스턴스
- 예약 인스턴스
- 스팟 인스턴스
- 사용되지 않는 인스턴스
- 언제든 사라질 수 있음
- 온디맨드 대비 최대 90% 비용 절감 가능
- 단위
ELB
- 특징
- 트래픽을 여러 AZ / 여러 EC2 인스턴스로 분산
- EC2 상태확인 기능 지원
- EC2 인스턴스에 대한 트래픽 라우팅 / LB 지원
- 상태확인: EC2 가용성 확인을 위해 주기적으로 Ping(상태확인)을 보냄
- 인스턴스 비정상이 확인되면 해당 인스턴스로의 라우팅을 중단
- 고정세션
- 로드밸런서가 사용자의 세션을 특정 서버에 바인딩
- 단점
- 애플리케이션으니 확장을 제한
- 서버 전체에서의 불균등한 로드
- 단일 사용자의 로드가 서버에 균등하게 분배하지 않기때문에 사용자 응답지연 가능성
- 고정세션 대신 캐시를 사용하자
- 세션정보를 외부 캐시서버(EleastiCache/DynamoDB)로 빼자
Auto Scaling
- 특징
- 애플리케이션 처리를 위한 EC2 인스턴스 수를 적절하게 유지
- 사용자 정의 조건(ex: CPU 사용률 80%)에 따라 인스턴스 수를 조정
- 조정 타입
- 수동 조정: 최대/최소 및 원하는 용량 설정
- 예약 조정: 예측가능한 이벤트에 따라 AS 발생 시간을 지정
- 동적 조정: 성능 측정 임계값을 기반으로 조정
IAM
IAM 사용하기
CLI Cheat sheet
# --profile=admin은 아래 명령의 실행권한이 있는 계정이다.
$ aws iam create-user --user-name=shawn # 'shawn'이라는 이름을 가진 계정 생성
$ aws iam get-user --user-name=shawn --profile=admin # shawn 계정 조회
{
"User": {
"Path": "/",
"UserName": "shawn",
"UserId": "AIDASK3VMLLUAZOZT2IMG",
"Arn": "arn:aws:iam::160770579176:user/shawn",
"CreateDate": "2019-04-18T04:28:26Z"
}
}
$ aws iam tag-user --profile=admin --user-name=shawn --tags Key=type,Value=cli Key=name,Value=shshin Key=desc,Value="cli test" # 태그 3건 추가
$ aws iam get-user --user-name=shawn --profile=admin # 다시 계정정보 조회하면 태깅된 것을 볼 수 있다.
{
"User": {
"Path": "/",
"UserName": "shawn",
"UserId": "AIDASK3VMLLUAZOZT2IMG",
"Arn": "arn:aws:iam::160770579176:user/shawn",
"CreateDate": "2019-04-18T04:28:26Z",
"Tags": [
{
"Key": "type",
"Value": "cli"
},
{
"Key": "name",
"Value": "shshin"
},
{
"Key": "desc",
"Value": "cli test"
}
]
}
}
$ aws iam add-user-to-group --group-name=beginner \ # 생성한 계정을 특정 그룹에 포함시키는 명령
--user-name=shawn \
--profile=admin
$ aws iam get-group --group-name=beginner --profile=admin
{
"Users": [
{
"Path": "/",
"UserName": "shawn",
"UserId": "AIDASK3VMLLUAZOZT2IMG",
"Arn": "arn:aws:iam::160770579176:user/shawn",
"CreateDate": "2019-04-18T04:28:26Z"
},
{
"Path": "/",
"UserName": "shshin-cli",
"UserId": "AIDASK3VMLLUER4D26ZLI",
"Arn": "arn:aws:iam::160770579176:user/shshin-cli",
"CreateDate": "2019-04-18T04:24:58Z"
}
],
"Group": {
"Path": "/",
"GroupName": "beginner",
"GroupId": "AGPASK3VMLLUMJYDCHGM6",
"Arn": "arn:aws:iam::160770579176:group/beginner",
"CreateDate": "2019-04-18T04:24:37Z"
}
}
VPC
FAQ
- VPC는 AWS의 가상 네트워크
Hands on
VPC Public subnet 구성하기
- VPC 생성
- Subnet 생성
- Internet Gateway 생성 및 서브넷 연결
- acl 구성
- ssh open
- http open
- Route table에서 0.0.0.0/0 -> Internet gateway로 연결
--> 서버 인스턴스에서 외부로 나가는 connection lost
EC2
FAQ
개요
- EC2는 무엇인가?
- 컴퓨팅 규모를 자유자재로 변경할 수 있는 웹 서비스
- 쉽게 웹 규모의 컴퓨팅 작업(웹 서버?)를 수행할 수 있게 설계됨
- EC2로 할 수 있는 작업?
- 웹 서비스 인터페이스를 통해 컴퓨팅을 수행
- 간편하게 필요한 용량을 얻을 수 있음
- 서버 인스턴스를 획득하고 부팅하는데 단 몇분 소요
- EC2를 구성하는 방법
- AWS 계정을 설정
- AMI를 선택하거나 만들어 선택
RunInstances
API를 사용하여 원하는 수의 인스턴스를 생성 (인스턴스 개수는 20개로 제한)- 20개가 넘는 인스턴스를 구성하려면 요청 양식을 작성해야 함
- API 요청 결과가 성공 메시지를 반환하고 인스턴스를 시작함
DescribeInstances
API를 사용해서 인스턴스의 상태를 확인TerminateInstances
API를 사용해서 인스턴스를 종료(인스턴스 소멸)- EBS를 사용하는 경우
StopInstances
API로 인스턴스를 중지시킨 경우에도 데이터를 보존할 수 있음
- 인스턴스 스토어와 EBS의 차이
- EBS는 인스턴스 수명에 관계없이 데이터 보존
- 로컬 인스턴스 스토리지는 인스턴스 수명기간 동안만 데이터가 유지됨
- 인스턴스를 중지(stop)하면 데이터가 소멸됨
- 인스턴스 실행 소요시간
- 약 10분이 걸리지 않지만, 처음 부팅하는 인스턴스는 약간 더 걸릴 수 있음
Hands on
EC2 인스턴스 생성하기
Linux 가상 머신 시작 를 보고 따라함.
- 인스턴스 정보
- 인스턴스 이미지: ami-00dc207f8ba6dc919 Amazon Linux AMI 2018.03.0 (HVM)
- 인스턴스 유형: t2.micro
인스턴스 접속 (SSH)
인스턴스 생성할 때 발급받은 Keypair(*.pem)은 파일 권한을 400으로 변경해야 한다. 아래 명령이 필요하다.
chmod 400 MyKeyPair.pem
SSH로 EC2 접속하기
$ ssh -i ~/.ssh/MyMacbookKeypair.pem ec2-user@13.125.207.77
CLI KeyPair
KeyPair 생성
$ aws ec2 create-key-pair --key-name MyKeyPair --query 'KeyMaterial' --output text > MyKeyPair.pem
EC2 인스턴스 CLI로 생성
$ aws ec2 run-instances --profile admin \
--image-id ami-00dc207f8ba6dc919 --count 1 --instance-type t2.micro \
--key-name OfficeKeyPair --security-group-ids sg-09175e0acb5baeef8 --subnet-id subnet-04064fc6dc0785aef
CLI Cheat Sheet
$ aws ec2 describe-images --image-ids ami-00dc207f8ba6dc919 --profile admin # EC2 인스턴스 이미지 조회
$ aws ec2 describe-instances --instance-ids i-0cb2272fff1199138 --profile admin # 인스턴스 조회
$ aws ec2 describe-instance-status --instance-ids i-0cb2272fff1199138 --profile admin # 인스턴스 상태 조회
Elastic Load Balancing
Intro
- Elastic Load Balancing, ELB는 네트워크 트래픽을 여러 가용영역에 배포한다.
- 트래픽 배포 대상은 EC2 인스턴스, 컨테이너, IP 주소 등이 포함된다.
- 트래픽의 변화에 따라 로드밸런서를 확장/축소할 수 있다.
장점
- 가용성
- 여러 가용역역에 있는 컴퓨팅 리소스에 트래픽을 분산시킴
- 정상 상태의 리소스에만 트래픽을 수신하도록 함
- 리소스가 응답 불가능한 상태인 경우, 네트워크 트래픽 배포 대상에서 제외한다.
- 리전에 걸친 로드 배ㅔㄹ런싱 가능
- 99.99% 가용성 보장
- 보안
- VPC와 연동
- 인증서 관리, 사용자 인증, SSL/TLS 복호화 등의 보안기능 제공
- 컴퓨팅 리소스가 주요 작업에 집중할 수 있도록 암복호화 작업을 로드밸런서로 떠넘길 수 있다.
- 탄력성
- 네트워크 트래픽 변화에 빠르개 대처 가능ㄴ
- Auto Scaling 통합해서 자동으로 애플리케이션 리소스 확보
- 컴퓨팅 리소스의 추가/제거가 자유롭다.
- 유연성
- IP 주소를 사용해서 라우팅 할 수 있음
- 동일 인스턴스에서 많은 애플리케이션을 호스팅 할 수 있음
- 모니터링
- CloudWatch를 통해 애플리케이션 성능을 실시간으로 모니터링 가능
- 하이브리드 로드 밸런싱
- AWS와 온 프레미스 리소스에 로드밸런싱 할 수 있음
Q&A
ELB의 종류와 적용 케이스
- Application Load Balancer
- HTTP/S 트래픽 로드밸런싱에 적합
- 7 계층(Application layer)에서 작동
- Network Load Balancer
- TCP/TLS 트래픽의 로드 밸런싱에 적합
- 4 계층(Transport layer)에서 작동
- 고도의 성능이 요구되거나 지연시간이 낮아야 하는 애플리케이션에서 사용
- Classic Load Balancer
- EC2-Classic 네트워크 내에 구축된 애플리케이션을 대상으로 함 (레거시?)
퍼블릭 IP를 사용하지 않고 ELB API 액세스
- VPC 엔드포인트를 생성하여 ELB API에 비공개로 액세스 가능
ALB
- 지원 운영체제? -> EC2 서비스가 지원하는 모든 운영체제
- 지원 프로토콜? -> HTTP/HTTPS
- HTTP/2 지원
- 웹소켓 지원
- 요청 추적기능 활성화
- ALB 트래픽만 허용하도록 EC2 인스턴스 구성 가능
- ALB 앞단의 보안그룹 구성 가능
- Classic Load Balancer API를 ALB에 사용 불가
- 로드밸런서 유형은 전환 불가
- Classic Load Balancer -> ALB 마이그레이션 가능
- 4 계층 기능이 필요하다면 NLB를 사용해야 함
- 단일 ALB로 HTTP(80 포트) 및 HTTPS(443 포트) 에 대한 요청 처리 가능
- ALB API 호출 기록을 얻으려면 CloudTrail을 사용하면 됨
HTTPS 종료
를 이용하려면 SSL 인증서를 로드밸런서에 설치해야 함- SSL 인증서를 받으려면 Certificate Manager를 이용해서 프로비저닝 가능
- Certificate Manager와 통합되어, 간단하게 로드밸런서에 인증서를 연결할 수 있음
- 인증서 구매, 업로드 및 갱신 프로세스가 자동화 됨
- SNI(서버 이름 표시)는 하나 이상의 인증서를 동일 리스너에 연결하면 자동으로 활성화 됨
- 반대로 하나의 인증서만 연결한 경우 자동으로 비활성화 됨
- 동일 도메인용 인증서 여러개를 보안 리스너에 연결 가능
- ALB는 IPv6을 지원함
- ALB 규칙 설정
- 리스너에 대해 규칙을 구성할 수 있음
- 규칙은 조건과 조건이 충족될 경우 수행할 작업으로 구성됨
- 조건: 호스트 헤더, 경로, HTTP 헤더, 메서드, 쿼리 파라미터, 소스 IP CIDR
- 작업: 리다이렉션, 고정 응답, 인증 및 전달
- ALB 리소스 제한
- 리전 제한
- 리전당 로드 밸런서: 20
- 리전당 대상 그룹: 3000
- 로드 밸런서 제한
- 로드 밸런서당 리스너: 50
- 로드 밸런서당 대상: 1000
- 로드 밸런서당 가용 영역당 서브넷: 1
- 로드 밸런서당 보안 그룹: 5
- 로드 밸런서당 규칙(기본 규칙은 계산하지 않음): 100
- 로드 밸런서당 인증서(기본 인증서는 포함되지 않음): 25
- 로드 밸런서당 대상을 등록할 수 있는 횟수: 100
- 대상 그룹 제한
- 대상 그룹당 로드 밸런서: 1
- 대상 그룹당 대상(인스턴스 또는 IP 주소): 1000
- 대상 그룹당 대상(Lambda 함수): 1
- 규칙 제한
- 규칙당 일치 평가: 5
- 규칙당 와일드카드: 5
- 규칙당 작업: 2(하나는 옵션 인증 작업, 하나는 필수 작업)
- 리전 제한
- WAF와 통합하여 웹 애플리케이션의 공격으로부터 방어할 수 있음
- IP 주소, HTTP 헤더 및 사용자 정의 URI를 기반으로 규칙을 구성할 수 있음
- 로드 밸런서의 VPC 내에 있는 대상으로 로드밸런싱 가능
- ALB는 교차 영역 로드 밸런싱이 활성화 되어있음
- Cognito와 통합하여 로드밸런서의 OpenID 자격증명공급자 기본 지원
- ALB의 리디렉션유형
- HTTP to HTTP: http://foo -> http://bar
- HTTP to HTTPS: http://foo -> https://bar, https://foo:80/foz -> https://bar:8080/baz
- HTTPS to HTTPS: https://foo -> https://bar
- ALB는 모든 컨텐츠 유형을 지원
- ALB를 통해 Lambda 호출할 수 있음
- ALB의 규칙이 일치하는 경우 람다 함수가 호출됨, 요청 컨텐츠 전문은 람다 함수에 JSON 형식으로 전달
- 람다 함수의 응답은 JSON 형식
- 로드 밸런서는 Lambda Invoke API를 사용하여 람다 함수를 호출
- ELB는 람다 함수 호출 권한이 있어야 함
ALB 요금
- 실행 시간, 시간당 사용된 로드밸런서 용량 단위(LCU)에 대해 요금이 부가됨
- 로드밸런서 용량 단위(LCU)는 트래필을 처리하는 차원(새 연결, 활성 연결, 대역폭) 중 최대로 소비된 리소스를 정의함
- CloudWatch를 통해 LCU를 측정하는 네 차원의 사용량을 확인 가능함
- 부분 LCU도 과금됨
- 규칙 평가는 처리된 규칙 수와 한 시간 동안의 평균 요청 속도의 곱으로 정의됨
- 교차영역 로드밸런싱을 위한 리전 AWS 데이터 전송에 요금은 부과되지 않음
- ALB 사용자 인증기능 활성화에 별도 요금은 부과되지 않으나 Cognito와 함께 사용하는 경우 Cognito 요금이 적용됨
- Lambda를 사용하는 경우, ALB가 실행된 시간과 시간당 사용된 로드밸런서 용량단위에 대해 요금이 부과됨
- 람다에서 처리된 바이트와 다른 대상(EC2, Container, 온프레미스 서버)에서 처리된 바이트는 클라우드 지표로 확인 가능함
- Lambda -> LambdaTargetProcessedBytes
- Others -> StandardProcessedBytes
Network Load Balancer
- NLB는 TCP, UDP, TCP+UDP 리스너, TLS 리스너를 지원
- NLB는 4계층 로드밸런싱을 제함
- 초당 수백만 개의 요청과 변동성이 높은 트래픽 패턴을 처리하도록 설계됨
- 클라이언트 소스 IP를 유지함
- 지연시간이 매우 짧음
- 웹소켓 유형에 유용한 장기간 연결도 지함
- TCP, UDP 프로토콜을 동일한 포트에서 처리하려면 TCP+UDP 리스너를 사용해야 함
- NLB는 Elastic IP를 사용할 수 있음
- NLB 주소는 사용자 또는 ELB가 제어해야 함
- 이렇게 해야 Elastic IP를 사용할 때 클라이언트가 알고있는 주소가 변경되지 않음
- 각 서브넷에서 NLB는 단일 공용/인터넷 IP 주소만 지원함
- NLB가 삭제되는 경우 Elastic IP는 할당된 풀로 반환됨
- NLB 주소는 사용자 또는 ELB가 제어해야 함
- NLB는 각 서브넷에서 단일 Private IP를 지원함
- 임의의 주소로 로드밸런싱 하는 방법 (TCP만 지원함)
- 로드밸런서 VPC CIDR의 모든 주소
- 로드밸런서 외부의 AWS Direct Connect를 통해 액세스 할 수 있는 범위의 IP주소
- Private Network: RFC 1918 범위 (10.0.0.0/8, 172.16.0.0/12 및 192.168.0.0/16)
- Shared Address Space: RFC 6598 범위 (100.64.0.0/10)
- VPC와 온프레미스 위치에 배포된 애플리케이션을 로드밸런싱 하려면 각각 별개의 로드밸런서를 사용하고 DNS 가중치를 사용하여 VPC와 온프레미스 대상 간에 가중치 기반 로드밸런싱을 사용할 수 있음
DynamoDB
개요
DynamoDB는?
- NoSQL 데이터베이스 서비스
- AWS에서 서비스
- 유휴시 암호화 제공
- AWS management console을 통해 사용량 측정 가능
- 프로비저닝 가능
- 온디맨드 백업기능 제공
- 특정 시점으로 복구 가능: 최근 35일 중 원하는 시점으로 복구
- 데이터에 TTL(Time To Live) 설정 가능
- 고가용성 및 내구성
- 테이블의 데이털르 충분한 수의 서버로 자동 분산 -> 일관되게 빠른 성능 보장
- 모든 데이터가 SSD에 저장되고 여러 리전에 걸쳐 복제 -> 고가용성 및 내구성 보장
- 전역 테이블을 사용하여 여러 리전간에 테이블 동기화 가능
특징
읽기 일관성 (Consistency Read)
다이나모DB는 데이터를 읽을 때 최근에 변경내역이 반영되지 않을 수 있다. 아래 작업이 순차적으로 일어날 경우, 3번 작업에서 이름이 홍길동으로 나올 수도 있다.
- 데이터를 쓴다.
{"Key": 1, "이름": "홍길동"}
- 데이터를 변경한다.
{"Key": 1, "이름": "홍두깨"}
- Key: 1인 항목을 조회한다. ->
{"Key": 1, "이름": ?}
이를 최종적 읽기 일관성 (Eventually Consistency Read)라고 한다.
강력한 읽기 일관성 (Strongly Consistency Read)
- 이전 쓰기작업의 업데이트를 모두 반영하여 조회하고 싶은 경우, 강력한 읽기 일관성을 사용한다.
- DynamoDB 읽기 API는 기본적으로 최종적 읽기 일관성을 사용한다. 강력한 읽기 일관성을 사용하고 싶으면 ConsistencyRead 파라미터를 true로 설정한다. 이 경우 읽기 요청단위가 달라진다. (읽기/쓰기 용량모드 참고)
- GSI는 강력한 읽기 일관성을 지원하지 않는다.
읽기 쓰기 용량모드
- 읽기 요청 유닛 1개는 강력한 읽기 일관성으로 4kb 데이터를 읽을 수 있다.
- 최종적 읽기 일관성을 사용할 경우 0.5 유닛을 소비한다.
- 8kb 데이터를 읽을 경우, 강력한 읽기 일관성 모드에서 유닛 2개, 최종적 읽기 일관성 모드에서 유닛 1개를 소비한다.
- 쓰기 요청 유닛 1개는 1kb 데이터를 쓸 수 있다.
파티션 및 데이터 배포
- DynamoDB는 테이블을 생성할 때 충분한 수의 파티션을 할당한다.
- 파티션의 한도가 초과하여 테이블의 처리량을 늘려야 할때 파티션을 늘린다.
- 추가적인 스토리지 공간이 필요햔 경우 파티션을 늘린다.
- 데이터를 쓸 때, 파티션 키값으로 해시값을 구해서 데이터를 저장할 파티션을 결정한다.
- 정렬키가 있는 경우, 파티션 키 값으로 데이터를 저장한 파티션을 찾은 다음, 정렬 키 값을 이용하여 순차적으로 데이터를 스캔한다.
작동방식: 핵심 구성요소
- 다이나모DB 기능 제한에 대해서 알아보기
테이블, 항목 및 속성
- 테이블: 데이터의 집합, RDBMS의 테이블과 같다.
- 항목: 테이블은 0개 이상의 항목이 존재한다. RDBMS의 tuple, record
- 속성: 각 항목은 1개 이상의 속성을 가진다. RDBMS의 field, column
위 People 테이블에서
- 테이블은 3건의 항목을 가지고 있다.
- 첫 번째 항목은 4건의 속성을 가지고 있다.
- People 테이블은 기본 키 PersonId를 가진다.
- 속성에 대해서
- 테이블은 스키마가 없다. 속성을 미리 정의할 필요가 없다.
- 대부분의 항목은 스칼라 형식이다. -> 스칼라 형식은 하나의 값만 가질 수 있음. 문자열, 숫자 등의 형식을 말함.
- 일부 항목은 내포 속성(Address 속성)를 가진다. 다이나모DB는 32레벨까지 내포 속성을 지원한다. -> Map과 유사함
키
테이블을 생성할 때 기본 키 지정은 필수이다. 기본 키는 각 항목을 나타내는 고유 식별자이다. 복수 건의 항목이 동일한 기본 키를 가질 수 없다.
다이나모DB는 두 가지 기본 키를 지원한다.
기본 키는 스칼라 타입이여야 한다. 즉, 키는 문자열, 숫자 혹은 이진수 데이터만 가질 수 있다.
파티션 키 (해시 속성)
- 하나의 속성으로 구성되는 기본 키
- 파티션 키를 해시함수의 입력을 사용함 => 파티션 키에 따라 물리적 스토리지(파티션)이 결정됨
- 파티션 키로만 구성된 테이블에서 각 항목이 동일한 파티션 키를 가질 수 없음.
- 파티션 키로 항목 액세스가 가능함
정렬 키 (범위 속성)
- 파티션 키와 함께 정렬 키를 사용할 경우, 각 항목은 동일한 파티션 키를 가질 수 있다. 단, 정렬 키는 달라야 한다.
- 정렬 키는 유연한 쿼리를 지원한다.
- 정렬 키의 쿼리 방식은 정렬 키 조건 표현식을 참고한다.
보조 인덱스
테이블은 하나 이상의 보조인덱스를 생성할 수 있다. 보조 인덱스의 대체 키를 사용하여 쿼리를 할 수 있다. 모든 인덱스는 테이블에 속해있다. 이를 기본 테이블이라 한다. 다이나모DB는 인덱스를 자동으로 유지한다. 즉, 기본 테이블의 항목이 변경되면 인덱스의 해당 항목도 변경된다. 인덱스를 생성할 때 기본 테이블에서 인덱스로 복사하거나 프로젝션할 속성을 지정한다. 기본 키 속성은 무조건 프로젝션된다.
다이나모 스트림
- 스트림은 다니아모DB 데이터의 변경 이벤트를 캡쳐한다.
- 캡쳐한 이벤트는 스트림 레코드에서 볼 수 있다.
- 스트림 레코드는 테이블 이름, 타임스탬프, 메타 데이터 등을 가지고 있다.
- 스트림 레코드의 수명은 24시간이다.
- 스트림과 람다를 이용해서 트리거를 생성할 수 있다.
CLI
테이블 생성
aws dynamodb create-table \
--table-name Member \
--attribute-definitions \
AttributeName=name,AttributeType=S \
AttributeName=age,AttributeType=N \
--key-schema \
AttributeName=name,KeyType=HASH \
AttributeName=age,KeyType=RANGE \
--provisioned-throughput \
ReadCapacityUnits=1,WriteCapacityUnits=1 \
--endpoint-url http://localhost:8000
생성 결과
{
"TableDescription": {
"AttributeDefinitions": [
{
"AttributeName": "name",
"AttributeType": "S"
},
{
"AttributeName": "age",
"AttributeType": "N"
}
],
"TableName": "Member",
"KeySchema": [
{
"AttributeName": "name",
"KeyType": "HASH"
},
{
"AttributeName": "age",
"KeyType": "RANGE"
}
],
"TableStatus": "ACTIVE",
"CreationDateTime": 1561194380.277,
"ProvisionedThroughput": {
"LastIncreaseDateTime": 0.0,
"LastDecreaseDateTime": 0.0,
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
},
"TableSizeBytes": 0,
"ItemCount": 0,
"TableArn": "arn:aws:dynamodb:ddblocal:000000000000:table/Member",
"BillingModeSummary": {
"BillingMode": "PROVISIONED",
"LastUpdateToPayPerRequestDateTime": 0.0
}
}
}
데이터 쓰기
테이블의 항목을 추가한다. 테이블 생성할 때 없는 속성도 추가 가능하다. 만약 동일한 키가 존재하는 경우, 데이터를 덮어쓴다.
aws dynamodb put-item --endpoint-url http://localhost:8000 \
--table-name Member \
--item \
'{"name": {"S": "홍길동"}, "age": {"N": "20"}, "email": {"S": "hong@gil.dong"}, "phoneNumber": {"S": "0102224444"}}'
aws dynamodb put-item --endpoint-url http://localhost:8000 \
--table-name Member \
--item \
'{"name": {"S": "김갑수"}, "age": {"N": "30"}, "email": {"S": "kapsu@kim.com"}, "phoneNumber": {"S": "01012341234"}}'
aws dynamodb --endpoint-url http://localhost:8000 \
> put-item --table-name Member \
> --item '{
"name": {
"S": "홍길동"
},
"myOrder": {
"N": "1"
},
"phoneNumber": {
"S": "01012341234"
},
"age": {
"N": "30"
},
"email": {
"S": "gildong@daum.com"
}
}'
데이터 읽기
항목 단 건을 기본 키로 조회한다. 만약 파티션 키와 정렬 키 조합의 테이블이라면 두 키 모두 파라미터에 넣어줘야 한다.
aws dynamodb get-item --endpoint-url http://localhost:8000 \
--table-name Member \
--key '{"name": {"S": "홍길동"}, "age": {"N": "20"}}'
{
"Item": {
"name": {
"S": "홍길동"
},
"phoneNumber": {
"S": "0102224444"
},
"age": {
"N": "20"
},
"email": {
"S": "hong@gil.dong"
}
}
}
만약 강력한 읽기 일관성(Strongly Consistency Read)을 사용하려면 --consistent-read
옵션을 추가한다.
데이터 수정하기
aws dynamodb update-item --endpoint-url http://localhost:8000 \
--table-name Member \
--key '{"name": {"S": "홍길동"}, "age": {"N": "20"}}' \
--update-expression "SET age = :newage, email = :newemail ADD myOrder :o" \
--expression-attribute-values '{":newage":{"N":"21"}, ":newemail": {"S": "gildong@hong.com"}, ":o": {"N": "1"} }' \
--return-values ALL_NEW
데이터 쿼리
키 조건 표현식
- 파티션 키는 등식 조건으로 지정해야 한다.
- 정렬 키는 아래의 비교연산자를 사용할 수 있다.
표현식 | 의미 |
---|---|
a = b | 속성 a가 값 b와 같을 때 true |
a < b | 속성 a가 값 b보다 작을 때 true |
a <= b | 속성 a가 값 b보다 작거나 같을 때 true |
a > b | 속성 a가 값 b 클 때 true |
a >= b | 속성 a가 값 b보다 크거나 같을 때 true |
a between b and c | 속성 a가 값 b보다 크거나 같고 c보다 작을 때 true |
begins_with(a, substr) | 속성 a가 substr로 시작할 때 true |
aws dynamodb --endpoint-url http://localhost:8000 \
query --table-name Member \
--key-condition-expression "#nm= :qname and age= :age" \
--expression-attribute-names '{"#nm":"name"}' \
--expression-attribute-values '{":qname":{"S":"홍길동"}, ":age": {"N": "20"}}'
aws dynamodb --endpoint-url http://localhost:8000 \
query --table-name Member \
--key-condition-expression "#nm= :qname and age>= :age" \
--projection-expression "#nm, phoneNumber" --expression-attribute-names '{"#nm":"name"}' \
--expression-attribute-values '{":qname":{"S":"홍길동"}, ":age": {"N": "20"}}'
- 위 명령에서
name
은 DynamoDB 예약어이기 때문에#nm
으로 대체했다. - projection-expression 파라미터로 원하는 속성만 조회할 수 있다.
AWS certified developer associate
Udemy - Ultimate AWS Certified Developer Associate 2019 - NEW!
- Course Introduction - AWS Certified Developer Associate, 7개의 강의, 15:38
- Code & Slides Download, 1개의 강의, 00:17
- AWS Fundamentals: IAM + EC2, 19개의 강의, 01:33:46
- AWS Fundamentals: ELB + ASG + EBS, 6개의 강의, 42:47
- AWS Fundamentals: Route 53 + RDS + ElastiCache + VPC, 9개의 강의, 50:14
- AWS Fundamentals: Amazon S3, 10개의 강의, 49:44
- AWS CLI, SDK, IAM Roles & Policies, 14개의 강의, 58:54
- AWS Elastic Beanstalk, 8개의 강의, 56:16
- AWS CICD: CodeCommit, CodePipeline, CodeBuild, CodeDeploy, 13개의 강의, 01:40:35
- AWS CloudFormation, 12개의 강의, 59:37
- AWS Monitoring & Audit: CloudWatch, X-Ray and CloudTrail, 12개의 강의, 57:55
- AWS Integration & Messaging: SQS, SNS & Kinesis, 15개의 강의, 01:30:08
- AWS Serverless: Lambda, 14개의 강의, 01:19:34
- AWS Serverless: DynamoDB, 15개의 강의, 01:18:51
- AWS Serverless: API Gateway & Cognito, 12개의 강의, 53:12
- AWS Serverless: SAM - Serverless Application Model, 9개의 강의, 38:46
- ECS, ECR & Fargate - Docker in AWS, 13개의 강의, 01:23:01
- AWS Security & Encryption: KMS, Encryption SDK, SSM Parameter Store, IAM & STS, 11개의 강의, 01:10:01
- AWS Other Services, 7개의 강의, 19:14
- AWS Final Cleanup, 2개의 강의, 03:19
- Preparing for the Exam - AWS Certified Developer Associate, 5개의 강의, 21:52
- Congratulations - AWS Certified Developer Associate, 2개의 강의, 05:12
백서 목록
학습목록
- EC2 : AWS의 핵심서비스인 만큼 EC2에 대한 내용은 무척 비중있게 다루어집니다.
- VPC
- ELB
- AWS Lambda
- AWS Step Functions
- Amazon DynamoDB
- Elastic Beanstalk
- EBS
- EFS
- S3
- CloudFormation
- OpsWorks
- Amazon SNS
- Amazon SQS
- AWS Storage Gateway
- Amazon Kinesis
- Amazon EMR
- AWS Direct Connect
- AWS Import/Export
- AWS Directory Service
- Amazon Route53
- Amazon CloudFront
- Amazon ECS
- AWS KMS
- Amazon MQ
Spring Framework Overview
Version 5.2.3.RELEASE
스프링은 자바 엔터프라이즈 애플리케이션을 쉽게 만들수 있게 한다. 스프링은 엔터프라이즈 환경에서 당신이 자바 언어를 수용하기 위한 모든 것을 제공한다. 그리고 JVM 언어인 Groovy와 Kotlin도 지원한다. 그리고 애플리케이션의 요구사항에 맞는 여러 종류의 아키텍쳐를 만들 수 있는 유연성을 가지고 있다. 스프링 프레임워크 5.1 버전부터 스프링은 JDK 8 이상의 버전이 필요하고 JDK 11 LTS 버전에 대해서도 out-of-the-box support(별도 설정 필요 없는 지원)를 제공합니다. Java SE 8 update 60 버전은 Java 8 버전에 대한 최소 요구 버전으로 지원되나 최신 패치버전도 일반적으로 사용할 수 있습니다.
스프링은 다양한 애플리케이션 시나리오를 지원합니다. 대규모 엔터프라이즈 상황에서 애플리케이션은 종종 오랜 시간 유지되어야 하고 개발자의 관리범위를 넘어서는 업그레이드 주기를 가진 애플리케이션 서버를 실행해야 할 때가 있습니다. 또다른 경우는 클라우드 환경에서 임베디드 서버를 내장한 single jar를 실행해야 하는 경우입니다. 그리고 서버가 필요 없는 배치나 워크로드 통합을 위한 standalone 애플리케이션의 경우가 있습니다.
스프링은 오픈소스이다. 다양한 실제 사용 사례에 기반한 지속적인 피드백을 제공하는 크고 활성화된 커뮤니티가 있다. 이는 스프링을 오랜시간동안 성공적으로 발전시켜나가는데 도움을 주고 있다.
1. "Spring"의 의미
"Spring"이라는 용어는 다양한 상황에서 다양한 의미를 가지고 있다. 이 것은 Spring Framework 자체를 의미할 수 있다. 시간이 흐르면서, 다른 스프링 프로젝트들이 스프링 프레임워크의 위에 생겼다. 오늘날 자주 사용하는, 사람들이 말하는 "Spring"은 스프링 프로젝트의 전체를 의미하기도 한다. 이 문서는 스프링 프레임워크에 초점을 맞추고 있다.
스프링 프레임워크는 모듈들로 분리되어 있다. 애플리케이션은 필요한 모듈을 선택할 수 있다. 핵심은 configuration model과 dependency injection 메카니즘이 포함된 코어 컨테이너의 모듈이다. 이 외에도 스프링 프레임워크는 여러 애플리케이션 아키텍쳐, 메시징, 데이터 트랜잭션과 영속성, 웹 등에 대한 기초적인 지원을 제공합니다. 서블릿 기반의 Spring MVC 웹 프레임워크와 Spring WebFlux reactive 웹 프레임워크를 함께 지원합니다.
모듈에 대한 메모: 스프링의 프레임워크 jar 파일은 JDK 9의 모듈화(Jigsaw) 배포를 허용합니다. Jigsaw 활성화 애플리케이션에서 사용하기 위해 스프링 프레임워크 5 는 jar artifact 이름(e.g. "spring-core", "spring-context")에서 독립된 안정적인 언어 레벨의 모듈 이름을 정의하는 "Automatic Module-Name" 매니페스트 항목(e.g. "spring.core", "spring.context", ...)이 제공됩니다. 물론 스프링 프레임워크 jar는 JDK 8과 JDK 9+에서 동일하게 잘 동작합니다.
2. 스프링과 스프링 프레임워크의 역사
스프링은 2003년에 초기 J2EE 명세의 복잡함에 대한 대안(response)로 시작되었습니다. 일부는 Java EE와 스프링이 경쟁 관계에 있다고 생각하지만, 사실 스프링은 Java EE와 상호보완적인 관계에 있습니다. 스프링 프로그래밍 모델은 Java EE 플랫폼 명세를 포함하지 않습니다. 대신 EE 명세로부터 필요한 명세를 선택적으로 통합합니다. 아래는 그 예입니다.
- Servlet API (JSR 340)
- WebSocket API (JSR 356)
- Concurrency Utilities (JSR 236)
- JSON Binding API (JSR 367)
- Bean Validation (JSR 303)
- JPA (JSR 338)
- JMS (JSR 914)
- 트랜잭션 조정을 위한 JTA/JCA 구성 (필요하다면)
스프링 프레임워크는 Dependency Injection(JSR 330) 명세와 공통 애너테이션(JSR 250) 명세도 지원한다. 애플리케이션 개발자는 스프링 프레임워크가 제공하는 특정 메카니즘들(e.g. XML 방식의 빈 등록) 대신 사용할 수 있다.
스프링 프레임워크 5.0 기준으로 스프링은 Java EE 7 레벨 (e.g. 서블릿 3.1+, JPA 2.1+)을 최소조건으로 요구함과 동시에 Java EE 8 레벨 (e.g. 서블릿 4.0, JSON 바인딩 API)과 즉시 통합을 지원하고 있다. 이는 스프링이 Tomcat 8, 9, WebSphere 9, JBoss EAP 7 과의 완전 호환을 유지하게 한다.
시간이 지남에 따라 애플리케이션 개발을 위한 Java EE의 역할을 진화했다. 초창기의 Java EE와 스프링 애플리케이션들은 애플리케이션 서버에 배포되었다. 오늘날엔 스프링 부트의 도움으로 클라우드 친화적인 devops 환경에서 서블릿 컨테이너가 내장된 애플리케이션을 생성할 수 있다. 스프링 프레임워크 5 기준으로 웹플럭스 애플리케이션은 서블릿 API를 직접적으로 사용하지 않고 서블릿 컨테이너가 아닌 (Netty와 같은) 서버에서 동작한다.
스프링은 계속해서 혁신하고 발전하고 있다. 스프링 프레임워크를 넘어, 스프링 부트, 스프링 시큐리티, 스프링 데이터, 스프링 클라우드, 스프링 배치와 같은 다른 프로젝트들이 있다. 각 프로젝트는 고유의 소스코드 저장소, 이슈 트래커, 릴리즈 주기가 있다는걸 명심해라. spring.io/projects에서 스프링 프로젝트의 전체 리스트를 확인할 수 있다.
3. 디자인 철학
프레임워크에 대해 배울 때, 그것이 하는 일 뿐만이 아니라 그에 따르는 원칙을 아는 것도 중요하다. 아래는 스프링 프레임워크의 가이드 원칙이다:
- 모든 레벨에서의 선택을 제공한다. 스프링은 당신이 설계에 대한 결정을 가능한 늦게 미룰 수 있게 합니다. 한 예로, 코드의 변경 없이 설정만으로 영속성 제공자를 전환할 수 있다. 그외 많은 인프라스트럭쳐와 써드파티 API와의 통합에서도 마찬가지이다.
- 다양한 관점을 수용한다. 스프링은 유연성을 포용하고 문제 해결에 대한 의견을 고집하지 않는다. 다양한 관점으로 애플리케이션 요구사항에 대한 해결방법을 지원한다.
- 이전 버전과의 호환을 완벽하게 지원한다. 스프링의 발전은 매우 조심스럽게 관리되어서 버전 간의 변경 충돌이 거의 없었다. 스프링은 신중하게 선택한 JDK 버전과 써드파티 라이브러리를 지원하며 스프링에 의존하는 라이브러리와 애플리케이션들의 유지보수를 용이하게 한다.
- API 디자인에 신경쓴다. 스프링 팀은 직관적이고 오랜 시간, 다양한 버전에 걸쳐 사용할 수 있는 API를 설계하는데 많은 시간과 노력을 투입한다.
- 높은 기준의 코드 퀄리티를 가지고 있다. 스프링 프레임워크는 의미있고 정확한 javadoc에 중점을 두고 있다. 이는 패키지 간의 순환 의존관계가 없는 클린 코드 체계를 주장하는 매우 소수의 프로젝트들 중 하나이다.
4. 피드백과 기여
어떻게 해야하는지 질문하거나 이슈 디버깅/진단을 위해 StackOverFlow와 사용을 위한 태그들이 나열된 질문 페이지(역자 주: 깨져있음)를 이용하는 것을 추천한다. 만약 스프링 프레임워크에 확실한 문제가 있거나 새로운 기능을 제안하고 싶을 땐 Github Issues를 사용해라.
만약 버그 수정이나 솔루션을 염두하고 있다면, Github로 PR을 보낼 수 있다. 그러나 대부분의 사소한 이슈는, 미래에 추가될 기능을 위해 기록을 남기거나 토론중인 이슈트래커에 티켓이 채워져 있다는 것을 명심해라.
기여를 위한 가이드라인에 대해 더 알고싶다면, 탑 레벨의 프로젝트 페이지를 살펴보면 된다.
5. 시작하기
만약 빠르게 스프링을 사용하고 싶다면, 스프링 부트 기반의 프레임워크를 고려하고 있을 것이다. 스프링 부트는 빠르고 자동 설정이 포함된 프로덕션에 바로 적용 가능한 스프링 애플리케이션을 제공한다. 이는 스프링 프레임워크에 기반하고 설정을 직접 하기보다 이미 구성된 컨벤션을 선호한다. 그리고 스프링 부트는 가능한 빨리 실행할 수 있도록 설계되어 있다.
start.spring.io나 "시작하기" 가이드를 통해서 기본적인 프로젝트를 생성할 수 있다. 이런 가이드는 실제 업무에 집중되어 있기 때문에 소화하기 쉬울 뿐 아니라 대부분의 가이드가 스프링 부트 기반이다. 그리고 스프링 포트폴리오의 다른 프로젝트들 역시 특정 문제를 해결하기 위한 방안으로써 포함되어 있다.
Core technologies
Version 5.2.3.RELEASE
이 장은 스프링 프레임워크에 필수적인 모든 기술에 대해 다루고 있다.
이 중에서 가장 중요한 것은 스프링 프레임워크의 제어 역전(IoC, Inversion of Control) 컨테이너이다. 스프링 프레임워크 IoC 컨테이너의 철저하게 다룬 후 스프링 관점 지향 프로그래밍(AOP) 기술에 대해 포괄적으로 다루고 있다. 스프링 프레임워크는 고유의 AOP 프레임워크를 가지고 있고 이는 이해하기 슆고 Java Enterprise AOP 요건의 80%에 대해 참조하고 있다.
스프링과 AspectJ의 통합에 대한 범위 역시 제공하고 있다.
1. IoC 컨테이너
이 챕터는 스프링의 제어 역전 (IoC, Inversion of Control) 컨테이너에 대해 다룬다.
1.1. 스프링 IoC 컨테이너와 빈 도입부
이 챕터는 스프링 프레임워크에서 제어 역전을 적용하는 원리에 대해 다룬다. IoC는 의존성 주입(DI, Dependency Injection)이라고도 한다. 이는 오로지 생성자 인자, 팩토리 메서드에 대한 인자 혹은 팩토리 메서드로부터 반환거나 (생성자에 의해) 생성된 객체 인스턴스에 설정된 프로퍼티에 대한 의존성에 대한 프로세스이다. 컨테이너는 빈을 생성할 때 의존성을 주입한다. 이 프로세스는 근본적으로 빈 스스로 인스턴스 생성 과정이나 그들의 의존성을 직접 클래스를 생성해서 의존성을 제어하거나 서비스 로케이터 패턴과 같은 방식을 통해 생성되는 빈과 정 반대의 방식이다. (그래서 이름이 제어의 역전이다.)
org.springframework.beans
, org.springframework.context
패키지는 스프링 프레임워크의 IoC 컨테이너의 기반이다. BeanFactory
인터페이슨느 객체의 타입을 관리하는 방식에 대해 고급 구성 방식을 제공한다. ApplicationContext
는 BeanFactory
의 하위 인터페이스이고 아래와 같은 추가 기능을 가지고 있다.:
- 스프링 AOP 기능과의 통합
- (국제화 같은) 메시지 리소스 핸들링
- 이벤트 발행
- 웹 애플리케이션을 위한
WebApplicationContext
와 같은 특정 애플리케이션 레이어의 컨텍스트
단순히 BeanFactory
는 기본적인 기능과 프레임워크 구성을 제공하고, ApplicationContext
는 특정 엔터프라이즈 기능을 추가적으로 제공한다. ApplicationContext
는 BeanFactory
의 완전한 수퍼셋이고 이 챕터는 스프링 IoC 컨테이너를 다루는데 대부분을 사용한다. BeanFactory
에 대한 더 많은 정보는 1.16. BeanFactory 부분을 살펴보라.
스프링에서 애플리케이션의 기간을 구성하고 스프링 IoC 컨테이너가 관리하는 객체들을 빈(beans)이라고 부른다. 빈은 스프링 IoC 컨테이너에 의해 관리되고 인스턴스화하고 결합되는 객체이다. 또는 애플리케이션의 수많은 객체들 중 하나일 수도 있다. 그리고 객체들 간의 의존성에서 빈은 컨테이너에 의해 설정 메타데이터를 반영한다.
1.2. 컨테이너 오버뷰
org.springframework.context.ApplicationContext
인터페이스는 스프링 IoC 컨테이너를 대표하고 빈 인스턴스화, 구성, 결합에 대한 책임을 가지고 있다. 컨테이너는 설정 메타데이터(configuration metadata)를 읽어서 어떤 빈이 인스턴스화하고 구성하고 결합해야하는지에 대한 지시사항을 얻는다. 설정 메타데이터는 XML, 자바 어노테이션 혹은 자바 코드로 표현한다. 이는 객체들 사이의 수많은 상호의존성과 애플리케이션을 구성하는 객체들을 표현한다.
수많은 ApplicationContext
인터페이스의 구현체들은 스프링에 공급된다. Stand-alone 애플리케이션에서 ClassPathXmlApplicationContext
나 FileSystemXmlApplicationContext
의 인스턴스를 생성하는 일은 빈번하다. 설정 메타데이터를 정의하기 위해 전통적인 포맷인 XML을 사용하는 것 대신, XML 설정을 최소화하면서 추가 메타데이터 형식에 대한 지원을 선언적으로 활성화해서 자바 어노테이션이나 코드를 메타데이터 포맷으로 사용할 수 있다.
대부분의 애플리케이션 시나리오에서, 특정 유저 코드는 스프링 IoC 컨테이너의 하나 이상의 인스턴스를 인스턴스화하는데 필요조건이 아니다. 한 예로, 웹 애플리케이션 시나리오에서, web.xml
의 웹 디스크립터 XML 보일러 플레이트 코드로 8 줄 정도의 라인이면 충분하다 (웹 애플리케이션을 위한 편리한 애플리케이션 컨텍스트 인스턴스화 부분을 참고하라). 만약 Spring Tool Suite을 사용한다면, 몇 번의 마우스 클릭과 키보드 클릭으로 보일러플레이트 설정코드 생성할 수 있다.
다음은 스프링이 어떻게 동작하는지에 대한 고수준의 다이어그램이다. 애플리케이션 클래스들은 설정 메타데이터로 결합되어 있기 때문에 ApplicationContext
가 생성되어 초기화된 후 애플리케이션이나 실행 시스템이 완전히 구성되는 것이다.
1.2.1. 설정 메타데이터
앞선 다이어그램에서 봤듯이, 스프링 IoC 컨테이너는 설정 메타데이터를 사용한다. 이 설정 메타데이터는 애플리케이션 개발자가 스프링 컨테이너에게 애플리케이션의 객체를 인스턴스화, 구성, 결합을 지시하는 방법을 나타낸다.
XML 기반의 메타데이터는 설정 메타데이터의 유일한 형식이 아니다. 스프링 IoC 컨테이너는 스스로 어떤 설정 메타데이터 형식으로 작성되었는지와 디커플링 관계에 있다. 요즘엔 많은 개발자가 Java 기반 구성을 선택하고 있다.
스프링 컨테이너의 다른 메타데이터 형식에 대한 내용은 아래를 살펴봐라.:
- 어노테이션 기반 구성: 스프링 2.5 버전부터 어노테이션 기반 메타데이터 구성을 지원한다.
- Java 기반 컨테이너 구성: 스프링 3.0 버전부터, 스프링 JavaConfig 프로젝트가 제공하는 많은 기능들이 Core 스프링 프레임워크의 일부분으로 포함되기 시작했다. 그래서 XML 파일 대신 Java를 이용해서 애플리케이션 클래스에 대한 추가적인 빈을 정의할 수 있다. 이런 새로운 기능들을 사용하려면,
@Configuration
,@Bean
,@Import
그리고@DependsOn
어노테이션을 참고해라.
스프링 구성은 컨테이너가 관리하는 최소 하나 이상의 빈 설정으로 이루어져 있다. XML 기반 설정 메타데이터는 이런 빈들을 <beans/>
태그로 이루어져 있었다. Java 구성은 @Configuration
어노테이션이 등록된 클래스 안에 @Bean
어노테이션이 등록된 메서드를 사용한다.
이런 빈 정의는 애플리케이션을 형성하는 실제 객체들과 일치한다. 일반적으로, 서비스 레이어 객체와 데이터 엑세스 객체(DAO) 등을 정의할 것이다. 일반적으로 컨테이너에서 세밀한 도메인 객체를 구성하지 않는다. 왜냐하면 일반적으로 도메인 객체를 생성하고 로드하는 것은 비즈니스 로직과 DAO 객체의 책임이기 때문이다. 그러나 스프링과 AspectJ의 통합으로 IoC 컨테이너의 제어범위 밖에서 생성한 객체를 구성할 수 있다. 스프링에서 AspectJ를 사용해서 도메인 객체 의존성 주입하기 부분을 참고해라.
다음 예제는 XML 기반의 설정 메타데이터의 기본 구조를 나타낸다.:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> (1) (2)
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
- (1)
id
속성은 개별 빈 정의 식별자이다. - (2)
class
속성은 빈의 타입을 정의하고, 적합한 클래스 명이어야 한다.
id
속성의 값으로 협력하는 객체들을 참조한다. 협력하는 객체들을 참조하기 위한 XML 설정은 이 예제엔 없다. 의존성 부분에서 더 많은 정보를 확인할 수 있다.
1.2.2. 컨테이너 인스턴스화하기
ApplicationContext
생성자에게 제공하는 경로는 컨테이너가 다양한 외부 리소스로부터 로드한, 로컬 파일 시스템의 자바 CLASSPATH
와 같은, 설정 메타데이터를 로드한 리소스 문자열이다.
// Java
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// Kotlin
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
스프링 IoC 컨테이너에 대해 배운 후에 스프링 (Resource에 기술된)
Resource
추상화에 대해 더 알고싶어 할지도 모르겠다. 이는 URI 문법으로 정의된 위치에 대한 인풋스트림을 읽기 위한 편리한 방식을 제공한다.Resource
경로는 애플리케이션 컨텍스트를 생성하는데 사용되고, 자세한 내용은 Application Contexts and Resource Paths에 기술되어 있다.
다음 예제는 서비스 레이어 객체들에 대한 구성 파일이다.:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
다음 예제는 데이터 엑세스 객체(DAO)들에 대한 구성 파일이다.:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
앞선 예제에서, 서비스 레이어는 PetStoreServiceImpl
클래스와 두 가지 데이터 엑세스 객체 - JPA ORM 표준에 기반한 - JpaAccountDao
와 JpaItemDao
로 구성되어 있다. property name
요소는 자바 빈 속성의 이름을 참조한다. 그리고 ref
요소는 다른 빈 정의의 이름을 참조한다. id
와 ref
사이의 연결은 협력하는 객체들 사이의 의존성을 표현한다. 더 자세한 내용은 의존성 부분을 살펴봐라.
XML 기반의 설정 메타데이터 구성하기
복수의 XML 파일에 걸친 빈 정의는 유용하다. 개별 XML 설정 파일은 때때로 애플리케이션 아키텍쳐의 논리적 레이어나 모듈을 대표하기도 한다.
애플리케이션 컨텍스트 생성자를 사용해서 모든 XML 파일들로부터 빈 정의를 읽어올 수 있다. 이 생성자는 복수의 Resource
위치를 획득한다. 대안으로 하나 이상의 <import/>
요소로 다른 파일들의 빈 정의를 로드할 수 잇다. 아래 예제는 어떻게 표현하는지 보여준다.:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
부모 디렉토리에 있는 파일을 참조하기 위해 상대 경로 "../" 를 사용하는 것은 가능하지만, 추천하지 않는다. 그렇게 하면 현재 애플리케이션 외부에 있는 파일에 대한 의존성이 생기게 된다. 특정한
classpath:
로 시작하는 URL(예:classpath:../service.xml
)에 대한 참조도 지양해야 한다. 런타임 프로세스는 "가장 근접한" 클래스패스 루트를 선택한 뒤 상위 디렉토리를 찾는다. 잘못거나 다른 디렉토리를 클래스패스로 선택할 수 있다.상대 경로 대신 적합한 경로(예:
C:/config/service.xml
혹은classpath:/config/services.xml.
)를 선택할 수 있다. 그러나 애플리케이션 설정과 특정 절대 경로 위치와 결합되는 점을 유의해야 한다. 특정 절대 경로를 간접적으로 유지하는 방법 - 예를 들어 "${...}" 표현과 같은 런타임에 JVM 시스템 프로퍼티를 플레이스 홀더로 대입하는 방법 - 이 선호된다.
네임스페이스는 임포트 지시 기능을 제공한다. 게다가 평범한 빈 정의를 넘어선 설정 기능을 스프링이 제공하는 XML 네임스페이스 선택을 통해 이용할 수 있다. 예를 들어 context
와 util
네임스페이스와 같이 말이다.
그루비(Groovy)빈 정의 DSL
추가적인 설정 메타데이터의 예제로서, 빈 정의는 Grails 프레임워크로 알려진 스프링 그루비 빈 정의 DSL로 표현할 수 있다. 일반적으로 이런 설정은 ".groovy" 파일에 있고 구조는 아래와 같다.
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
1.2.3. 컨테이너 사용하기
ApplicationContext
는 다른 빈들과 의존성 레지스트리를 관리하는 기능을 가진 팩토리를 위한 인터페이스이다. T getBean(String name, Class<T> requiredType)
메서드를 사용해서, 빈 인스턴스를 검색할 수 있다.
ApplicationContext
는 빈 정의를 읽고 접근할 수 있게 해준다. 아래는 그 예제이다.
// Java
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
// Kotlin
import org.springframework.beans.factory.getBean
// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// use configured instance
var userList = service.getUsernameList()
Groovy 설정과 함께, 부트스트래핑하는 과정은 매우 유사하다. 이는 다른 컨텍스트 구현 클래스를 가지고 있다. 아래는 그루비 설정 예제이다.
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
가장 유연한 방법은 GenericApplicationContext
를 사용해서 리더 권한을 위임하도록 구성하는 것이다. 한 예로 XML 파일을 위해 XmlBeanDefinitionReader
을 사용한다.
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
GroovyBeanDefinitionReader
를 사용해서 그루비 설정을 읽어올 수도 있다.
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
이런 reader들을 혼합하거나 매칭하는 방법으로 ApplicationContext
의 권한을 위임해서 다양한 설정 소스파일로부터 빈 정의를 읽을 수 있다.
그러면 getBean
을 사용해서 빈 인스턴스를 검색할 수 있다. ApplicationContext
인터페이스는 빈 인스턴스 검색을 위한 다른 메서드를 가지고 있다. 그러나 애플리케이션 코드에서 그런 메서드를 사용해선 안된다. 게다가 애플리케이션 코드는 getBean
메서드도 호출하지 말아야 한다. 이렇게 하여 스프링 API를 호출하는 의존성을 없애야 한다. 한 예로, 스프링과 웹 프레임워크의 통합은 JSF-managed beans와 컨트롤러와 같은 여러가지 웹 프레임워크 컴포넌트들을 위한 의존성 주입을 제공한다. 그리하여 @Autowire
어노테이션과 같은 기능을 사용해서 특정 빈에 대한 의존성을 선언할 수 있다.
1.3. Bean 오버뷰
스프링 IoC 컨테이너는 하나 이상의 빈을 관리한다. 이 빈들은 컨테이너에게 제공하는 설정 메타데이터로 생성된다. XML의 <bean/>
형식의로 정의된 것들 말이다.
컨테이너 안에서, 빈 정의는 아래 메타데이터를 BeanDefinition
객체로써 표시되어며, 여기엔 아래와 같은 메타데이터가 포함된다.:
- 패키지 경로가 포함된 클래스 이름: 일반적으로 정의된 빈의 실제 구현 클래스이다.
- 컨테이너 내에서 빈이 어떻게 행동해야 하는지에 대해 기술된 빈 행동 설정 요소 (스코프, 라이프사이클 콜백 등).
- 빈이 동작하는데 필요한 다른 빈에 대한 참조정보. 이 참조는 협력자(collaborators) 혹은 의존성(dependencies)이라고도 부른다.
- 새로 생성된 객체에서 구성할 기타 설정 - 예: 커넥션 풀을 관리하는 빈에서 사용할 커넥션의 수 혹은 풀의 사이즈 제한 등
이런 메타데이터는 개별 빈 정의를 구성하는 프로퍼티의 모음을 번연ㄱ한다. 다음 테이블은 이러한 프로퍼티한 기술한 것이다.:
표 1. 빈 정의
프로퍼티 | 의미 |
---|---|
Class | 빈 인스턴스화 |
Name | 빈 네이밍 |
Scope | 빈 스코프 |
Constructor arguments | 의존성 주입 |
Properties | 의존성 주입 |
Autowiring mode | 협력자 오토와이어링 |
Lazy initialization mode | 빈 지연 인스턴스화 |
Initialization method | 빈 인스턴스화 콜백 |
Destruction method | 빈 파괴 콜백 |
특정 빈을 생성하는 방법에 대한 정보를 포함한 빈 정의 외에도, ApplicationContext
구현체는 (사용자에 의해) 컨테이너 외부에서 생성된 객체 등록도 허용한다. 이 것은 ApplicationContext의 BeanFactory를 getBeanFactory()
메서드가 반환하는 DefaultListableBeanFactory
객체를 통해 접근함으로써 처리된다. DefaultListableBeanFactory
는 registerSingleton(..)
메서드와 registerBeanDefinition(..)
메서드를 통해 빈 등록을 지원한다. 그러나 일반적으로 애플리케이션은 일반적인 빈 정의 메타데이터를 통해 정의된 빈들과 단독으로 동작한다.
빈 메타데이터와 수동으로 공급된 싱글턴 인스턴스는 컨테이너가 오토와이어링(autowiring) 단계와 기타 검사 단계에서 적절하기 추론하기 위해서 가능한 빨리 등록되어야 한다. 반면에 기존 메타데이터와 싱글턴 인스턴스를 오버라이딩한 것은 어느정도 지원되는 반면에, 런타임에서의 신규 빈 등록은 공식적으로 지원되지 않고 동시 접근 예외(concurrent access exception)와 빈 컨테이너의 모순된 상태를 유발할 수 있다.
1.3.1. 빈 네이밍
모든 빈은 하나 이상의 식별값을 가지고 있다. 이 식별값들은 빈의 호스트 컨테이너 내에서 유일해야 한다. 빈은 보통 유일한 식별값을 가지고 있다. 그렇지만 만약 하나 이상의 식별값이 필요한 경우, 추가적으로 별칭(alias)을 가질 수 있다.
XML 기반의 설정 메타데이터에서 빈 식별값으로 id
속성이나 name
속성, 혹은 두 속성 모두 사용했을 것이다. id
속성은 정확히 하나의 id만을 명시하게 한다. 관습적으로, id 값은 문자+숫자(alphanumeric, e.g. 'myBean', 'someService' 등)로 명명한다. 그러나 특별한 문자를 포함하는 것도 가능하다. 만약 빈에 대한 다른 별칭(alias)를 도입하길 원한다면, 빈의 name
속성에 명시할 수 있다. 이는 쉼표(,), 세미콜론(;) 혹은 공백문자(whitespace)로 구분한다. 스프링 3.1 버전 이전에는 id
속성은 입력 가능한 문자를 제한하는 xsd:ID
타입으로 정의되었다. 3.1 버전부터, xsd:string
타입으로 정의되었다. 더이상 XML 파서를 사용하지 않더라도 빈의 id
유일성은 컨테이너에 의해 강요된다.
빈의 id
속성이나 name
속성을 지정할 필요는 없다. 만약 명시적으로 지정하지 않는 경우, 컨테이너는 빈의 유일한 명칭을 생성한다. 그러나 만약 ref
요소나 서비스 로케이터 스타일의 룩업 방식을 사용해서 이름으로 빈을 참조하려면, 빈의 이름을 지정해야 한다. 이름을 제공하지 않는 이유는 inner beans와 autowiring collaborators와 관련이 있다.
빈 명명 규칙
빈을 이름을 명명할 때 인스턴스 필드 이름에 대해 표준 자바 규칙을 사용한다. 빈 이름은 소문자로 시작하고 카멜 케이스를 사용한다. 예를 들어
accountmanager
,accountService
,userDao
등 처럼 말이다.
빈 명명은 지속적으로 설정에 대한 가독성과 이해를 쉽게 만든다. 스프링 AOP를 사용할 때도, 이름으로 관련된 빈들에 대한 어드바이스를 적용하는데 도움을 준다.
클래스패스를 컴포넌트 스캔하면, 스프링은 이름이 없는 컴포넌트들에 대한 빈 이름을 앞서 기술한 것과 같이 생성한다.: 근본적으로, 클래스 이름을 획득하여 첫 번째 문자열을 소문자로 변환한다. 그러나 드물게 클래스 명의 첫 번째와 두 번째 문자 모두 대문자인 특별한 경우엔 원래의 케이스가 유지된다. 이는
java.beans.Introspector.decapitalize
에 의해 정의된 규칙과 동일하다. (스프링은 이를 사용한다.)
빈 정의 외부의 빈에 대한 별칭 정하기
빈을 정의할 때, 빈의 id
속성과 name
속성을 조합하여 하나 이상의 이름을 지정할 수 있다. 이 이름들은 동일한 빈에 별칭을 지정하는 것과 동등하고 특별한 상황에서 유용하다. 예를 들어 애플리케이션의 개별 컴포넌트가 컴포넌트에 명시된 빈 이름을 사용해서 공통의 의존성을 참조해야 할 때 말이다.
실제로 정의된 빈에 대한 모든 별칭을 명시하는 것은 항상 적절한 것은 아니다. 때때로 다른 곳에 빈에 대한 별칭에 대한 방식을 정의하는 것이 바람직하다. 다음은 대규모 시스템에서 흔히 사용하는, 개별 하위 시스템의 객체 정의에 대한 설정을 나누는 방법이다. XML 기반의 설정 메타데이터에서, <alias/>
요소를 사용해서 이를 적용할 수 있다. 다음 예제는 적용 방법에 대해 보여준다.:
<alias name="fromName" alias="toName"/>
이 예에서, 동일 컨테이너의 fromName
이라고 명명된 빈은, toName
이라는 별칭으로 정의되어 참조될 수 있다.
예를 들어, 하위시스템 A에 대한 설정 메타데이터는 subsystemA-dataSource
라는 이름으로 명명된 데이터소스를 참조할 수 있다. 하위 시스템 B에 대한 설정 메타데이터는 subsystemB-dataSource
라고 명명된 데이터 소스를 참조할 수 있다. 모든 하위시스템을 사용해서 메인 애플리케이션을 구성할 때, 메인 애플리케이션은 myApp-dataSource
로 명명된 데이터소스를 참조할 수 있다. 세 개의 이름들이 모두 동일한 객체를 참조하도록 하려면, 다음의 별칭 정의를 설정 메타데이터에 추가하여 사용할 수 있다.:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
이제 개별 컴포넌트와 메인 애플리케이션은 (네임스페이스를 효과적으로 작성하여) 다른 정의와 충돌하지 않도록 보장된 고유한 이름을 통해 데이터 소스를 참조할 수 있지만, 여전히 동일한 빈을 참조한다.
자바 설정
자바 설정을 사용하려면,
@Bean
어노테이션을 사용해서 별칭을 제공할 수 있다.@Bean
어노테이션 사용하기를 참고하라.
1.3.2. 빈 인스턴스화
빈 정의는 본질적으로 하나 이상의 객체를 생성하기 위한 레시피(방법)이다. 컨테이너는, 실제 객체를 생성하거나 가져오기 위한 빈 정의에 의해 캡슐화된 설정 메타데이터를 사용하거나 요청할 때, 이름을 가진 빈을 위한 레시피(빈 정의)를 본다.
만약 XML 기반의 설정 메타데이터를 사용한다면, <bean/>
요소의 class
속성으로 인스턴스화 된 객체의 타입(클래스)를 명시해야 한다. 이 class
속성 (내부적으로 BeanDefinition
인스턴스의 Class
프로퍼티이다.)은 대개 필수요소이다. (예외 경우는 인스턴스 팩토리 메서드로 인스턴스화하기와 빈 정의 상속을 살펴보라.) Class
속성은 두 가지 방법 중 하나로 사용할 수 있다.:
- 일반적으로, 컨테이너가 직접 빈의 생성자 - 자바 코드의
new
연산자와 동등한 - 를 호출(reflectively)하여 생성한, 빈 클래스를 지정한 것 - 객체를 생성하기 위해 호출되는
static
팩토리 메서드를 포함하는 실질적인 클래스를 명시한 것, 컨테이너가 빈을 생성하는 클래스의static
팩토리 메서드를 호출하는 흔하지 않은 케이스이다.static
팩토리 메서드 호출로 반환된 객체 타입은 동일한 클래스이거나 다른 클래스일 수 있다.
내부 클래스 이름
만약
static
중첩 클래스의 빈 정의를 설정하려면, 중첩 클래스의 바이너리 이름을 알아야 한다.
예를 들어, 만약com.example
패키지의Something
클래스를 가지고 있고Something
클래스가OtherThing
이란static
중첩 클래스를 가지고 있다면, 빈 정의의class
속성의 값은com.example.Something$OtherThing
이 될 것이다.
$
문자는 외부 클래스와 중첩 클래스의 이름을 구분하기 위해 사용한다.
생성자로 인스턴스화하기
생성자를 사용하여 빈을 생성할 때, 일반적인 모든 클래스들은 스프링과 호환되며 사용 가능하다. 개발중인 클래스는 특정한 인터페이스를 구현하거나 특정 방식으로 코딩할 필요가 없다는 것이다. 단순히 빈 클래스를 명시하는 것 만으로 충분하다. 그러나 사용하는 IoC 컨테이너의 타입에 따라 기본 생성자가 필요할 수도 있다.
스프링 IoC 컨테이너는 관리하고자 하는 어떤 클래스라도 관리할 수 있다. 실ㅈ레 자바빈을 관리하는 것에 제한은 없다. 대부분의 스프링 사용자는 (인자가 없는) 기본 생성자가 있고 적절한 세터/게터가 있는 자바 빈을 선호한다. 컨테이너에서 빈 스타일이 아닌 클래스를 사용할 수 있다. 예를 들어, 만약에 자바빈 규격에 맞지않는 레거시 커넥션 풀을 사용해야 한다면, 스프링은 그것을 잘 관리할 것이다.
XML 기반 설정 메타데이터에서 아래와 같이 빈 클래스를 명시할 수 있다.
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
생성자 인자를 공급하는 방식과 객체가 생성된 후에 인스턴스 프로퍼티를 구성하는 방식에 대한 자세한 내용은 의존성 주입을 참고하라.
스태틱 팩토리 메서드로 인스턴스화하기
스태틱 팩토리 메서드로 생성한 빈을 정의할 때, class
속성을 사용해서 static
팩토리 메서드를 포함하는 클래스를 명시하고, factory-method
속성으로 팩토리 메서드의 이름을 명시할 수 있다. (나중에 기술할 선택적 인자와 함께) 이 메서드를 호출할 수 있고, 생성자를 통해 생성된 것처럼 처리되는 오브젝트를 반환할 수 있다. 이런 빈 정의의 용도 중 하나는 레거시 코드에서 static
팩토리를 호출하는 것이다.
다음 빈 정의는 팩토리 메서드를 호출해서 생성된 빈에 대해 명시하는 것이다. 정의는 반환된 객체의 타입 (클래스)를 명시하지 않고, 오직 팩토리 메서드를 포함한 클래스만 알 수 있다. 이 예제에서, createInstance()
메서드는 스태틱 메서드여야 한다. 다음 예제는 팩토리 메서드를 명시하는 방법을 보여준다.:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
다음 예제는 앞선 빈 정의와 동작하는 클래스이다.:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
팩토리 메서드에 선택적 인자를 공급하고 팩토리 메서드로부터 반환된 오브젝트에 프로퍼티를 설정하는 방식은 의존성과 설정에 대한 상세내용을 참고하라.
인스턴스 팩토리 메서드를 사용해서 인스턴스화하기
스태틱 팩토리 메서드를 통해 인스턴스화하는 것과 유사하게, 인스턴스 팩토리 메서드는 컨테이너가 생성한 빈의 논 스태틱 메서드를 호출한다. 이 방식을 사용하려면, class
속성을 비우고, factory-bean
속성에 인스턴스 메서드를 포함한 빈의 이름을 명시해야 한다. factory-method
속성에 메서드 이름을 추가한다. 다음은 그 예제이다.:
<!-- createInstance() 메서드를 포함한 팩토리 빈 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 로케이터 빈이 요구하는 의존성을 주입 -->
</bean>
<!-- 팩토리 빈을 통해 생성뙨 빈 -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
다음 예제는 상응하는 클래스 예제이다.:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
팩토리 클래스는 하나 이상의 팩토리 메서드를 가질 수 있다. 다음은 그 예제이다.:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
다음은 상응하는 클래스이다.:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
이런 접근은 팩토리 빈이 의존성 주입(DI)를 통해 구성되고 관리됨을 보여준다.
스프링 문서에서, "팩토리 빈"은 스프링 컨테이너에서 구성되고 인스턴스 팩토리 메서드 혹은 스태틱 팩토리 메서드를 통해 생성한 객체를 말한다. 이와 대조적으로,
FactoryBean
(대문자 사용을 유심히 보라.)은 스프링의 특정한FactoryBean
을 말한다.
1.4. 의존성
일반적인 엔터프라이즈 애플리케이션은 하나의 객체로 구성되지 않는다. 심지어 간단한 애플리케이션도 최종 사용자가 보는 것처럼 일관성 있는 애플리케이션으로 보여주려면 서로 상호작용하는 여러 객체를 가지고 있어야 한다. 다음 섹션은 객체가 목표를 달성하기 위해 상호작용하는 완전 실질적인 스탠드-얼론 애플리케이션을 위한 수많은 빈 정의 방법을 설명한다.
1.4.1. 의존성 주입
의존성 주입은 객체가 그들과 동작하는 다른 객체에 대한 의존성을 정의하는 프로세스이다. 생성자 인자, 팩토리 메서드의 인자, 혹은 팩토리 메서드로부터 반환되거나 생성된 후 인스턴스에 주입될 수도 있다. 그 다음 컨테이너는 빈을 생성할 때 의존성을 주입한다. 이 프로세스는 근본적으로 빈 자신이 인스턴스화 과정이나 클래스의 직접 생성을 이용한 의존 혹은 서비스 로케이터 패턴 등으로 제어하는 방식에서 역전된다. 그래서 제어의 역전(Inversion of Control)이란 이름이다.
DI 원리에 의해 코드는 더 깔끔해지고, 객체가 의존성을 제공받음으로써 디커플링은 더 효과적이다. 객체는 그들의 의존성을 탐색하지 않고 의존할 클래스나 위치도 알지 못한다. 결과적으로, 클래스는 특히 의존성이 인터페이스나 추상 클래스 기반이면 더 테스트하기 쉬워지고, 유닛 테스트에서 사용되는 스텁(stub)이나 목(mock) 구현체를 허용한다.
의존성 주입엔 두 가지 방법이 있다.: 생성자 기반 의존성 주입입 세터 기반 의존성 주입
생성자 기반 의존성 주입
생성자 기반 의존성 주입은 컨테이너가 의존성을 나타내는 여러 인자를 가진 생성자를 호출함으로 써 동작한다. 빈을 생성하기 위한 특정한 인자를 가진 static
메서드를 호출하는 것과 거의 동등하고, 이 주제는 생성자의 인자와 static
팩토리 메서드를 유사하게 다룬다. 다음 예제는 생성자로 의존성 주입만이 가능한 클래스를 보여준다.:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
이 클래스엔 특별한게 없다. 이는 특정 컨테이너에 종속되거나, 특정 인터페이스, 기본 클래스, 어노테이션이 없는 POJO(Plain Old Java Object)이다.
생성자 인자 결정
생성자 인자 결정 방식은 인자의 타입을 사용함으로써 발생한다. 만약 빈 정의의 생성자 인자가 잠재적으로 모호한 경우가 아니라면, 빈 정의에 의해 정의된 생성자 인자가 정의되는 순서는 빈이 인스턴스화할 때 해당 인자가 적절한 생성자에 의해 제공되는 순서이다. 다음 클래스를 살펴보자.:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
ThingTwo
와 ThingThree
클래스가 상속과 관련된 것이 없고, 잠재적으로 모호함이 없다고 가정하자. 그럼련 다음 설정은 제대로 동작할 것이다. 그리고 특정 생성자 인자의 순서나 타입을 명시적으로 <constructor-arg/>
요소에 명시할 필요가 없다.
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
타입이 알려진 다른 빈이 참조될 때, (이전 예제의 케이스로) 매칭이 발생한다. <value>true</value>
와 같은 단순한 타입이 사용될 때, 스프링은 값의 타입을 결정할 수 없고, 도움 없인 매칭 작업을 할 수 없다. 다음 예제를 살펴보자.:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
생성자 인자 타입 매칭
이전 시나리오에서, 명시적으로 생성자의 타입을 type
속성으로 명시한다면 컨테이너는 단순한 타입으로 타입 매칭을 사용할 수 있다. 다음의 예제를 보면 알 수 있다.:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
생성자 인자의 순서
index
속성을 사용해서 생성자 인자의 순서를 명시할 수 있다.:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
추가로 여러 개의 단순한 값의 모호성을 해결하려면, 순서를 명시하여 생성자가 가진 동일한 두 개 이상의 인자의 모호성을 해결할 수 있다.
순서는 0부터 시작한다.
생성자 인자의 이름
값을 명확히 설정하기 위해 생성자 파라미터 이름을 사용할 수 있다. 다음의 예제를 보자.:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
이 작업을 즉시 수행하려면, 코드가 디버그 플래그로 컴파일되어야 스프링이 생성자로부터 파라미터 이름을 찾을 수 있다. 만약 디버그 플래그로 컴파일하길 원치 않으면, [@ConstructorProperties] JDK 어노테이션을 사용해서 명시적으로 생성자 인자에 이름을 붙일 수 있다. 샘플 클래스를 보자.:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
세터 기반 의존성 주입
세터 기반 의존성 주입은 컨테이너가 기본 생성자로 빈을 생성하거나 인자가 없는 static
팩토리 메서드로 빈 인스턴스를 생성한 후 세터 메서드를 호출하는 경우에 사용한다.
다음 예제는 순수한 세터 주입을 사용해서 의존성 주입을 해야하만 하는 클래스를 보여준다. 이 클래스는 관습적인 자바 코드이다. 아무런 의존성이 없는 POJO이다.
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext
는 생성자 기반/세터 기반 의존성 주입을 지원한다. 생성자 주입 후 세터 주입을 해야하는 경우도 지원한다. 어떤 형식에서 다른 형식으로 프로퍼티를 변환하는 PropertyEditor
인스턴스와 함께 사용해서 BeanDefinition
의 형식으로 의존성을 설정해야 한다. 그러나 대부분의 스프링 사용자는 이런 클래스들을 직접 다루기보단 XML 빈 정의, (@Controller
, @Component
등의 어노테이션이 달린) 컴포넌트 등을 다루거나 자바 기반의 @Configuration
클래스의 @Bean
메서드를 다룰 것이다. 이런 소스는 내부적으로 BeanDefinition
인스턴스로 변환되고 전체 스프링 IoC 컨테이너 인스턴스를 로드하는데 사용한다.
생성자 기반 의존성 주입과 세터 기반 의존성 주입?
생성자 주입과 세터 주입을 적절하게 섞어 사용할 수 있기 때문에, 생성자를 사용해서 필수적인 의존성을 주입하고 세터 메서드나 설정 메서드로 선택적인 의존성을 주입하는 방법은 적절하다. 세터 메서드에
@Required
어노테이션을 사용해서 프로퍼티를 필수적으로 의존받아야 함을 나타낼 수 있다. 그러나 생성자 주입이 더욱 선호된다.스프링 팀은 일반적으로 생성자 주입을 지지한다. 이는 애플리케이션 컴포넌트를 불변 객체로 구현할 수 있게 하고 필수적인 의존성을
null
이 아니게 할 수 있다. 게다가 생성자 주입 컴포넌트는 항상 (호출하는) 클라이언트 코드에게 완전히 초기화된 객체를 반환한다. 부수적으로, 너무 많은 생성자 인자는 나쁜 코드 냄새이고, 클래스가 너무 많은 책임을 가지고 있다는걸 암시한다. 그래서 적절한 관심사의 구분으로 더 나은 코드로 리팩토링해야 한다.세터 주입은 주로 선택적 인자에 한해 사용해야 한다. 이런 인자는 클래스가 적절한 기본 값을 가지고 있어야 한다. 반면에 not-null 확인은 의존성 있는 모든 코드에서 반드시 수행되어야 한다. 세터 주입의 한 가지 장점은 세터 메서드가 클래스의 객체를 차후에 재설정하거나 재 주입 가능하게 한다는 것이다. 그러므로 JMX MBeans를 통한 관리는 세터 주입의 강력한 사용 사례이다.
특정 클래스에 가장 적합한 의존성 주입 방법을 사용해야 한다. 가끔 소스를 가지고 있지 않은 써드파디 클래스를 다뤄야 할 때, 선택을 해야한다. 예를 들어, 만약 써드파티 클래스가 세터 메서드를 노출하지 않으면, 오로지 생성자 주입만이 사용 가능할 것이다.
의존성 주입의 예
다음 예제는 세터 의존성 주입을 위한 XML 기반의 설정 메타데이터이다. 스프링 XML 설정 파일의 일부분이 다음과 같다.:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
다음은 연관된 ExampleBean
클래스이다.:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
앞선 예제에서, XML 파일의 특정한 프로퍼티에 대응하여 세터를 선언했다. 다음 예는 생성자 기반 의존성 주입을 사용한다.:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
다음 예는 연관된 ExampleBean
클래스이다.:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
빈 정의에 명시된 생성자 인자는 ExampleBean
의 생성자의 인자로 사용된다.
이제 생성자를 사용하는 대신 객체의 인스턴스를 반환하는 static
팩토리 메서드를 호출하는 예제를 다룬다.:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
다음 예는 ExampleBean
클래스이다.:
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
static
팩토리 메서드의 인자는 <constructor-arg/>
요소에 의해 공급된다. 정확히는 생성자가 실제로 사용된 것과 동일하다. 팩토리 메서드에 의해 반환된 클래스 타입은 static
팩토리 메서드를 포함하는 클래스의 타입과 동일할 필요는 없다. (논-스태틱) 인스턴스 팩토리 메서드는 본질적으로 동일한 방식으로 사용할 수 있다. 그래서 여기선 이에 대해 다루진 않는다.
1.4.2. 의존성과 설정의 상세내용
이전 섹션에서 언급한 것 처럼, 빈 프로퍼티와 생성자 인자를 다른 빈(협력자)의 참조로 사용하거나, 인라인으로 정의된 값으로 사용할 수 있다. 스프링의 XML 기반 설정 메타데이터는 <property/>
와 <constructor-arg/>
요소를 제공한다.
값 (Primitive, String 등)
<property/>
요소의 value
속성은 사람이 읽을 수 있는 문자열 값으로써 프로퍼티 혹은 생성자 인자를 명시하게 한다. 스프링의 변환 서비스(conversion service)는 String
타입의 값을 실제 속성이나 인자의 타입의 값으로 변환하는데 사용한다. 다음은 다양한 값을 설정하는 예제이다.:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
다음은 p-네임스페이스를 사용해서 더욱 간단한 XML 설정하는 예제이다.:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
이전 XML은 더욱 간단하다. 그러나 IDE의 자동 완성 기능을 사용하지 않는 한, 오타가 설계 시점이 아닌 런타임 시점에 발견된다. IDE의 어시스턴스 기능을 강력히 권고한다.
다음과 같이 java.util.Properties
인스턴스를 설정할 수 있다.:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
스프링 컨테이너는 <value/>
요소의 텍스트를 자바 빈 PropertyEditor
방식을 사용하는 java.util.Properties
인스턴스로 변환한다. 이는 유용한 축약어로, 스프링 팀이 중첩된 <value/>
요소 사용보다 선호하는 몇 안되는 방법 중 하나이다.
idref
요소
idref
요소는 다른 빈의 id
문자열 값을 <constructor-arg/>
혹은 <property/>
요소로 전달하는 오류방지 방법이다. 다음 예제는 사용하는 방법에 대해 보여준다.:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
앞선 빈 정의에서 스니핏은 다음 스니핏과 완전히 동등하다.:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
전자는 후자보다 선호되는데, idref
태그를 사용함으로써 컨테이너는 배포 시점에 참조된 빈이 실제로 존재하는지 검증한다. 후자의 경우, client
빈의 targetName
프로퍼티를 전달하면서 어떤 검증도 수행하지 않는다. 오타는 client
빈을 생성할 때 발견된다. 만약 client
빈이 프로토타입 빈인 경우, 오타와 야기되는 예외는 컨테이너가 배포되고 한참 지난 후에 발견된다.
idref
요소의local
속성은 4.0 beans XSD에서 더이상 지원되지 않는다. 4.0 명세로 업그레이드할 때idref local
참조를idref bean
으로 변경하라.
스프링 2.0 버전 이전에서 <idref/>
요소가 가져오는 값들은 ProxyFactoryBean
빈 정의 안의 AOP 인터셉터 설정에 위치한다. 인터셉터 이름을 명시할 때 <idref/>
요소를 사용하는 것은 인터셉터 ID를 잘못쓰는 것을 방지하게 한다.
다른 빈 (협력자) 참조
ref
요소는 <constructor-arg/>
와 <property/>
정의 요소 내 마지막 요소이다. 빈의 특정 프로퍼티에 컨테이너가 관리하는 다른 빈(협력자)의 참조값을 설정한다. 이 참조된 빈은 프로퍼티로 설정한 빈이 의존한다. 그리고 이는 프로퍼티 값으로 설정하기 전에 필요할 때 초기화된다. (만약 협력자가 싱글턴 빈이라면, 컨테이너에 의해 이미 초기화됐을 것이다.) 모든 참조는 결국 다른 객체의 참조이다. 범위와 검증은 bean
, local
, parent
요소를 통해 어떤 객체의 ID 혹은 이름을 명시하냐에 따라 결정된다.
<ref/>
태그의 bean
요소를 통해 타겟 빈을 명시하는 것은 일반적인 형식이고 동일한 XML 파일에 있지 않더라도 동일 컨테이너나 부모 컨테이너의 어떤 참조된 빈이라도 생성할 수 있게 허용한다. bean
속성의 값은 타겟 빈의 id
속성이나 name
속성과 동일하다. 다음 예제는 ref
요소를 어떻게 사용하는지 보여준다.:
<ref bean="someBean"/>
parent
속성을 통해서 타겟 빈을 명시하는 것은 현재 컨테이너의 부모 컨테이너에 있는 빈의 참조를 생성한다. parent
속성의 값은 타겟 빈의 id
속성이나 name
속성의 값 중 하나와 같다. 타겟 빈은 부모 컨테이너에 있어야 한다. 컨테이너의 계층이 있고 부모 컨테이너에 존재하는 빈을 동일한 이름의 프록시로 감싸고 싶다면 주로 이런 빈 참조 변형을 사용해야 한다. 다음 리스트 쌍은 parent
속성을 사용하는 방법을 보여준다.:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
idref
요소의local
속성은 4.0 beans XSD에서 더이상 지원되지 않는다. 4.0 명세로 업그레이드할 때idref local
참조를idref bean
으로 변경하라.
내부 빈
<property/>
혹은 <constructor-arg/>
요소 내 <bean/>
요소는 다음ㄱ뫄 같이 내부 빈을 정의한다.:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
내부 빈 정의는 ID나 이름가 필요없다. 만약 명시한다면, 컨테이너는 식별자로써 그 값을 사용하지 않는다. 컨테이너는 scope
플래그도 생성 과정에서 무시한다. 왜냐하면 내부 빈은 항상 익명이고 오로지 외부 빈과 함께 생성하기 때문이다. 내부 빈에 독립적으로 접근하거나, 이를 포함하는 (외부) 빈 외에 협력자로 주입하는 것은 불가능하다.
코너 케이스로, 임의의 범위 - 예를들어 싱글턴 빈을 포함한 요청 범위(request-scoped)의 내부 빈 - 로부터의 소멸 콜백(destruction callback)을 받는 것도 가능하다. 내부 빈 인스턴스의 생성은 그 빈이 포함한 빈과 동일하다. 그러나 소멸 콜백은 요청 범위의 라이프사이클 안에서 참여하게 한다. 이는 일반적인 시나리오는 아니고, 내부 빈은 일반적으로 포함하는 빈의 범위를 단순하게 공유한다.
컬렉션
<list/>
, <set/>
, <map/>
그리고 <props/>
요소들은 각각 프로퍼티와 인자로써 Java의 Collection
타입 - List
, Set
, Map
, Properties
- 을 설정한다. 다음 예는 이 것들을 사용하는 방법을 보여준다.:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
Map의 Key/Value 값, 혹은 Set 값은 다음 요소 중 무엇이라도 가능하다.:
bean | ref | idref | list | set | map | props | value | null
컬렉션 병합
스프링 컨테이너는 컬렉션 병합을 지원한다. 애플리케이션 개발자는 부모 요소인 <list/>
, <set/>
, <map/>
혹은 <props/>
를 정의하고 자식 요소 <list/>
, <set/>
, <map/>
그리고 <props/>
등을 부모 컬렉션으로부터 상속받고 값을 덮어쓸 수 있다. 이는 자식 컬렉션의 값이 부모 컬렉션과 자식 컬렉션의 병합 결과가 되는 것이다. 부모 컬렉션의 특정 값을 자식 요소가 덮어쓸 수 있다.
병합에 대한 이 섹션은 부모-자식 빈 방식에 대해 다룬다. 부모-자식 빈 정의에 대해 낯선 독자는 계속 읽기 전에 관련 섹션에 대해 먼저 읽어보길 권한다.
다음 예제는 컬렉션 병합을 구현한다.:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
adminEmails
프로퍼티에 <props/>
요소에 merge=true
속성이 사용된 것을 유심히 봐라. child
빈을 생성할 때, 인스턴스는 adminEmails
라는 Properties
컬렉션을 가지고 있다. 이는 자식과 부모의 adminMails
의 병합 결과를 가지고 있다. 다음 리스트는 그 결과를 나타낸다.:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
자식 Properties
컬렉션의 값은 부모 <props/>
로부터 모든 요소를 상속받아 설정한다. 그리고 자식 컬렉션의 값의 support
프로퍼티 값은 부모 컬렉션의 값을 덮어쓰게 된다.
이 병합 행위는 <list/>
, <set/>
, <map/>
컬렉션 타입들에서 유사하게 지원한다. <list/>
의 특정한 상황에서, 리스트 컬렉션 타입과 관련된 의미(값이 정렬된 컬렉션과 같은 개념)의 경우 유지된다. 부모 값은 모든 자식 리스트의 값을 앞선다. Map
, Set
, Properties
컬렉션 타입의 경우, 순서가 존재하지 않는다. 그러므로, 순서라는 의미는 컨테이너가 내부적으로 사용하는 이 타입들의 구현체에선 사실상 없다.
컬렉션 병합의 제한
다른 컬렉션 타입간의 병합은 할 수 없다. 만약 시도한다면, 적절한 예외가 발생할 것이다. merge
속성은 자식 정의에 명시해야 한다. 부모 컬렉션 정의에 merge
속성을 명시하는 것은 불필요하고 기대하는 병합 결과가 나타나지 않을 것이다.
강타입 컬렉션
Java 5에서 제네릭 타입의 도입으로, 컬렉션의 타입을 제한할 수 있다. 이는 String
타입으로 구성된 컬렉션을 선언할 수 있다는 것이다. 만약 스프링을 사용할 때 타입이 명시된 컬렉션을 빈의 의존성으로 주입하면, 스프링 타입 변환 지원 기능의 도움을 얻을 수 있다. 다음 자바 클래스와 빈 정의는 사용 방법을 보여준다.:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
something
빈의 accounts
속성이 주입받을 준비가 됐을 때, Map<String, Float>
타입의 요소에 대한 제네릭 정보가 리플렉션에 의해 사용 가능해진다. 그러므로 스프링의 타입 변환 기능은 Float
타입이 되어야 할 다양한 값 요소와 문자열 값들 (9.99
, 2.75
등)을 실제 Float
타입으로 변환되어야 함을 인식한다.
Null과 빈 문자열 값
스프링은 비어있는 인자를 빈 문자열 ""
처럼 다룬다. 다음 스니핏은 email
프로퍼티에 빈 문자열 ("")를 설정하는 예제이다.:
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
이전 예제는 다음 자바 코드와 동일하다.:
exampleBean.setEmail("");
<null/>
요소는 null
값을 다룬다. 다음 예제가 이를 보여준다.:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
앞선 설정은 다음 자바 코드와 동등하다.:
exampleBean.setEmail(null);
p-네임스페이스 XML 축약어
p-네임스페이스는 빈 요소의 속성을 협력하는 빈 프로퍼티를 기술하는 용도로 사용하게 한다.
스프링은 XML 스키마 정의 기반의 네임스페이스로 확장성을 가진 설정 포맷을 지원한다. 이 챕터의 bean
설정 포맷은 XML 스키마 문서로 정의된다. 그러나 p-네임스페이스는 XSD 파일로 정의되지 않고 오직 스프링 코어에만 존재한다.
다음 예제는 두 가지 XML 스니핏을 보여준다. 첫 번째는 표준 XML 형식을 사용하고 두 번째는 p-네임스페이스를 사용한다. 두 경우의 결과는 같다.:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
예제는 email
이라는 p-네임스페이스의 속성을 빈 정의로 나타낸다. 이는 스프링에게 프로퍼티를 선언하라고 명령하는 것이다. 앞서 말한대로, p-네임스페이스는 스키마 정의에 없기 때문에, 속성의 이름을 프로퍼티 이름으로 설정해야 한다.
다음 예제는 다른 빈을 참조하는 두 가지 이상의 빈 정의를 포함한다.:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
이 예제는 p-네임스페이스를 사용하는 프로퍼티 값 뿐만이 아니라 프로퍼티 참조를 선언하는 특별한 형식도 사용한다. 첫 번째 빈 정의가 <property name="spouse" ref="jane" />
코드를 사용해서 john
빈이 jane
빈에 대한 참조를 생성한 반면에, 두 번째 빈 정의는 p:spouse-ref="jane"
을 속성으로 사용해서 동일한 작업을 수행한다. 이 경우에 -ref
부분은 직접 값을 주입하는게 아니라 다른 빈에 대한 참조를 가리키는 반면에, spouse
는 프로퍼티의 이름이다.
p-네임스페이스는 표준 XML 형식만큼 유연하지 않다. 한 예로, 프로퍼티 참조를 선언하는 형식은
Ref
로 끝나는 이름을 가진 프로퍼티와 충돌한다. 반면에 표준 XML 포맷은 그렇지 않다. 우린 팀 멤버와 커뮤니케이션 한뒤 조심스럽게 접근하여 모든 방법을 동시에 사용하는 XML 문서 작성을 피할 것을 추천한다.
c-네임스페이스 XML 축약어
p-네임스페이스 XML 축약어와 유사하게, c-네임스페이스는 스프링 3.1부터 도입되어, 중첩된 constructor-arg
요소 대신 생성자 인자를 인라인 속성으로 설정하는 것을 허용한다.
다음 예제는 c:
네임스페이스를 사용해서 생성자 기반 의존성 주입과 동일한 작업을 수행한다.:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
c:
네임스페이스는 생성자 인자를 이름으로 설정하는데 p:
네임스페이스와 동일한 규칙을 사용한다. 유사하게 XSD 스키마가 정의되어 있지 않지만 XML 파일에 선언되어야 한다. (스프링 코어 내부에 존재한다.)
생성자 인자의 이름이 허용되지 않는 드문 케이스에서, 인자의 순서를 사용할 수 있다.:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
XML 문법에 따라, 인덱스 표기는
_
를 먼저 써야 한다. XML 속성은 숫자로 시작할 수 없기 때문이다(몇몇 IDE에선 이를 허용함). 상응하는 익덱스 표기는<constructor-arg>
요소에서도 사용할 순 있으나 일반적인 방법으로 충분하다.
실제로 생성자 처리 방식은 인자를 매칭하는데 꽤 효율적이어서, 정말 필요하지 않으면 이름 표기법을 사용하는 것이 좋다.
복합 프로퍼티 이름
빈 프로퍼티를 설정할 때, 마지막 프로퍼티 이름을 제외한 경로 값이 null
이 아닌 모든 컴포넌트에 대해 복합되거나 중첩판 프로퍼티 이름을 사용할 수 있다. 다음 빈 정의를 고려하라.:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something
빈은 bob
프로퍼티를 가진 fred
프로퍼티를 가지고 있다. bob
은 값이 123
인 sammy
프로퍼티를 가지고 있다. 이 작업에서 fred
와 bob
프로퍼티가 null이 아니어야 한다. 아니면 NullPointerException
이 발생한다.
1.4.3. depends-on
사용하기
만약 다른 빈에서 의존하고 있는 빈이라면 대개 빈이 다른 빈의 프로퍼티로 설정되어 있다는 의미이다. 일반적으로 XML 기반 설정 메타데이터에서 <ref/>
요소를 사용해서 해결한다. 그러나 가끔 빈들 사이에 덜 직접적인 의존성이 있을 때가 있다. 데이터베이스 드라이버 등록과 같은, 트리거되어야 하는 클래스의 정적 초기화 블록이 그 예이다. depends-on
요소는 빈이 명시적으로 하나 이상의 빈이 초기화되어야 함을 강요한다. 다음 예제는 depends-on
속성을 사용해서 단일 빈의 의존성을 표현한다.:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
여러 빈에 대한 의존성을 표현하려면, depends-on
속성에 빈 이름의 리스트를 작성한다. (콤마, 공백 그리고 세미콜론이 유효한 구분자임):
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on
속성은 초기화 시점, 그리고 싱글턴 빈의 경우 상응하는 소멸 시점 의존 성의 경우 명시할 수 있다. 먼저 소멸하는 주어진 빈과depends-on
관계를 정의해서 의존하는 빈은 소멸하는 빈에 앞서 소멸한다. 그러므로depends-on
은 소멸 명령을 제어하기도 한다.
1.4.4. 지연 초기화 빈 (Lazy-initialized Beans)
기본적으로 ApplicationContext
구현체는 초기화 과정의 일부로써 싱글턴 빈을 구성하고 생성한다. 이 인스턴스화 전 단계는 바람직하다. 왜냐하면 몇 시간 혹은 며칠 뒤가 아닌 구성 혹은 주위 환경의 에러가 즉각 발생하기 때문이다. 이 행위가 바람직하지 않을 때, 빈 정의에 지연 초기화로 표시함으로써 싱글턴 빈의 인스턴스화 전 단계를 방지할 수 있다. 지연 초기화 빈은 IoC 컨테이너에게 (구동할 때가 아닌) 빈의 요청이 최초 발생할 때 빈을 생성하라고 지시한다.
XML에서 <bean/>
요소의 lazy-init
속성에 의해 행동을 제어한다. 다음 예제를 봐라.:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
앞선 설정이 ApplicationContext
에 의해 사용될 때, lazy
빈은 ApplicationContext
가 시작할 때 인스턴스화 되지 않는다. 반면에 not.lazy
빈은 인스턴스화한다.
그러나 지연 초기화 빈이 다른 빈에 의존될 때, ApplicationContext
는 지연 초기화 빈을 구동 시점에 생성한다. 왜냐하면 싱글턴의 의존성을 채워야하기 때문이다. 지연 초기화 빈은 지연 초기화 빈이 아닌 곳으로 주입된다.
<beans/>
요소의 default-lazy-init
속성을 사용해서 컨테이너 레벨에서 지연 초기화를 제어할 수 있다. 다음 예제를 보자.:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. 협력자 자동주입 (Autowiring Collaborators)
스프링 컨테이너는 협력하는 빈 사이의 관계를 자동 구성한다. ApplicationContext
의 내용을 검사해서 스프링이 자동으로 협력자(다른 빈)을 주입하게 할 수 있다. 자동 구성은 다음과 같은 장점이 있다.:
- 자동 구성을 사용해서 프로퍼티나 생성자 인자를 명시해야 하는 필요를 줄일 수 있다. (다른 곳에서 다루는 빈 템플릿과 같은 방식 역시 이와 관련하여 가치있다.)
- 자동 구성은 객체가 발전함에 따라 설정을 업데이트할 수 있다. 한 예로, 만약 클래스에 의존성을 추가해야 한다면, 설정을 수정할 필요 없이 의존성이 자동으로 충족된다. 그러므로 자동 구성은 개발 단계에서 특히 유용하다(코드가 안정적일 때 명시적으로 연결하는 옵션을 부정하지 않는).
XML 기반 설정 메타데이터를 사용할 때(의존성 주입 참고), <bean/>
요소의 autowire
속성을 통해 빈 정의에서 자동구성 모드를 명시할 수 있다. 자동 구성 모드는 네 가지가 있다. 빈마다 자동 구성 방법을 선택해서 명시할 수 있다. 다음 표는 네 가지 방법을 기술한다.:
표 2. 자동구성 모드
모드 | 설명 |
---|---|
no | (기본설정) 자동구성을 하지 않는다. 빈 참조는 ref 요소에 의해 정의되어야 한다. 대규모 배포에서 기본 설정을 바꾸는 것은 권고되지 않는다. 왜냐하면 협력자를 명시하는 것은 통제력과 명확성이 향상되기 때문이다. 이는 어느정도 시스템의 구조를 문서화한다. |
byname | 프로퍼티 이름으로 자동 구성한다. 스프링은 자동 구성해야하는 프로퍼티와 동일한 이름을 가진 빈을 탐색한다. 예를 들어 빈 정의에 master 프로퍼티를 포함하고 이름으로 자동 구성하게 설정되어 있으면, 스프링은 master 라는 이름을 가진 빈 정의를 찾고 프로퍼티를 설정할 때 그 빈을 사용한다. |
byType | 컨테이너에서 자동구성하려는 프로퍼티가 타입 당 하나의 빈만 존재할 때 사용하자. 만약 여러개으이 빈이 존재한다면, 치명적인 예외가 발생할 것이다. 만약 일치하는 빈이 존재하지 않으면, 아무 일도 일어나지 않는다. (프로퍼티는 설정되지 않음) |
constructor | byType 과 유사하지만 생성자 인자에 적용된다. 만약 생성자 인자 타입의 빈이 정확히 하나가 아니라면 치명적인 오류가 발생한다. |
자동 구성의 제약과 단점
자동 구성은 프로젝트 전체에서 일관되게 사용할 때 잘 작동한다. 만약 자동 구성이 일반적으로 사용되지 않는다면, 개발자는 몇몇 빈 정의에서 구성할 때 사용하는건지 혼란스러울 것이다.
자동 구성의 제약과 단점에 대해 고려하라.:
- 프로퍼티와 생성자 인자의 명시적 의존성 설정은 항상 자동 구성을 우선한다. 원시 타입, 스트링, 클래스, 배열 등과 같은 단순 프로퍼티는 자동 구성할 수 없다. 이는 설계상의 제약이다.
- 자동 구성은 명시적 구성보다 덜 정확하다. 앞선 표에 기술한 것 처럼 스프링이 예상치 못한 결과를 가진 모호한 경우를 피하려고 애쓴다. 스프링이 관리하는 객체들 사이의 관계는 명시적으로 문서화되지 않을 것이다.
- 스프링 컨테이너로부터 문서를 생성하는 도구는 구성이 불가능할 수 있다.
- 컨테이너의 여러 빈 정의들은 세터 메서드나 생성자 인자에 의해 타입이 자동 구성해야 할 빈과 일치해야 한다. 배열, 컬렉션 혹은 맵 인스턴스의 경우 이는 문제가 아니지만, 단일 값을 가지는 경우, 모호함이 의도한대로 해결되지 않을 수 있다. 만약 유일한 빈 정의가 없다면, 예외가 발생할 것이다.
마지막 시나리오에서 여러 옵션이 있다:
- 명시적인 구성으로 바꾼다.
- 다음 섹션에서 기술할, 자동 구성 속성을
false
로 설정하여 자동 구성을 회피한다. - 하나의 빈 정의를
<bean/>
요소의primary
속성을 true로 설정하여 우선 순위로 지정한다. - 어노테이션 기반 컨테이너 설정에서 기술할, 어노테이션 기반 설정으로 더욱 세밀한 제어가 가능하게 구현한다.
자동 구성으로부터 빈 제외하기
빈마다 자동 구성으로부터 설정을 비활성화 할 수 있다. 스프링의 XML 포맷에서 <bean/>
요소의 autowire-candidate
속성을 false
로 설정한다. 컨테이너는 특정 빈 정의를 (@Autowired
와 같은 어노테이션 스타일 설정을 포함해서) 자동 구성하지 못하도록 한다.
autowire-candidate
속성은 타입 기반 자동구성에서만 적용하도록 설계되었다. 이는, 특정 빈이 자동 구성 빈이 아니라고 표시되어 있어도, 이름에 의한 참조에선 적용되지 않는다. 그럼에도 불구하고 결과적으로 이름에 의한 자동 구성은 이름이 일치해도 주입될 것이다.
빈 이름 패턴 매칭을 기반으로 자동 구성 빈을 제한할 수 있다. 탑 레벨 <beans/>
요소는 default-autowire-candidates
속성 내에 하나 이상의 패턴을 허용한다. 예를 들어, Repository
로 끝나는 이름을 가진 빈을 자동 구성 요소에서 제외하려면, *Repository
값을 패턴에 설정한다. 여러 패턴을 사용하려면, 쉼표(,
)를 구분자로 사용한다. 빈 정의 autowire-candidate
속성애 true
나 false
같은 명시적인 값은 항상 우선한다. 특정 빈에선 패턴 매칭 규칙은 적용되지 않는다.
이런 기술은 특정 빈이 자동 구성에 의해 주입되길 원하지 않을 때 유용하다. 이는 자동 구성을 사용해서 제외된 빈 자체를 구성할 수는 없다. 빈 스스로 자동구성할 빈의 후보가 될 순 없다.
1.4.6. 메서드 주입 (Method Injection)
대개의 애플리케이션 시나리오에서, 대부분의 빈들은 싱글턴이다. 싱글턴 빈이 다른 싱글턴 빈이나 싱글턴이 아닌 빈들과 협력해야 할 때, 보통 다른 빈의 프로퍼티로 의존성을 다룰 것이다. 빈 라이프사이클이 상이할 때 문제가 발생할 것이다. 싱글턴 빈 A가 각 메서드를 호출할 때마다 프로토타입 빈 B를 사용한다고 가정하자. 컨테이너는 싱글턴 빈 A를 1회 생성한다. 그러므로 프로퍼티를 설정할 기회는 오로지 한 번이다. 컨테이너는 필요할 때 마다 새로운 빈 인스턴스 B를 제공할 수 없다.
솔루션은 어떤 제어 역전을 선행하는 것이다. ApplicationContext
인터페이스를 구현하고 getBean("B")
코드로 빈 인스턴스 B가 필요할 때마다 컨테이너에게 요청해서 빈 인스턴스 A를 만들 수 있다. 다음 예제는 이런 접근을 보여준다.:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
앞선 예제는 바람직하지 않다, 왜냐하면 비즈니스 코드가 스프링 프레임워크를 인지하고 결합되어 있기 때문이다. 스프링 IoC 컨테이너의 기능인 메서드 주입은 이런 상황을 깔끔하게 정리한다.
메서드 주입의 동기에 대해 알고 싶다면 이 블로그를 참고하라.
탐색 메서드 주입 (Lookup Method Injection)
탐색 메서드 주입은 메서드를 컨테이너가 관리하는 빈의 메서드를 다른 빈을 이름으로 탐색해서 반환하게 하는 컨테이너의 기능이다. 일반적으로 탐색은 프로토타입 빈도 포함한다. 스프링 프레임워크는 CGLIB 라이브러리의 바이트 코드 생성 기능을 사용해서 메서드 주입을 구현한다. 동적으로 메서드를 오버라이드하는 서브클래스를 생성한다.
- 동적으로 서브클래스를 생성하는 작업을 할 때,
final
클래스,final
메서드는 오버라이드 할 수 없다.abstract
메서드를 가진 클래스의 단위테스트는 클래스 자신의 서브클래스를 생성하고abstract
메서드에 대한 stub을 구현해야 한다.- 구체 메서드는 구체 클래스 탐색을 위한 컴포넌트 스캔을 위해 필요하다.
- 추가적인 제한사항으로 탐색 메서드가 팩토리 메서드에서 동작하지 않으며, 특히
@Bean
메서드에서 작동하지 않는다. 왜냐하면 이 경우에 컨테이너는 인스턴스를 생성할 책임이 없어서 런타임 시점에 서브클래스를 생성할 수 없다.
CommandManager
클래스의 경우, 스프링 컨테이너는 동적으로 createCommand()
메서드를 오버라이드해서 구현한다. CommandManager
클래스는 스프링 의존성이 없다.:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
주입될 메서드를 포함하는 클라이언트 클래스 (이 경우엔 CommandManager
클래스) 는 다음과 같은 형식의 시그니쳐가 필요하다.
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
만약 추상 메서드라면, 동적으로 생성된 서브클래스가 메서드를 구현한다. 아니면, 원래 클래스에 정의된 구체 메서드를 덮어쓴다. 다음 예를 고려해라.:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
commandManager
로 식별되는 빈은 myCommand
빈이 필요 할 때마다 createCommand()
메서드를 호출한다. 실제로 필요한 경우 myCommand
빈을 프로토타입으로 다뤄야한다. 만약 싱글턴이라면, 매번 동일한 인스턴스인 myCommand
빈이 반환될 것이다.
어노테이션 기반 컴포넌트 모델의 경우, 다음 예제와 같이 @Lookup
어노테이션을 통해 탐색 메서드를 선언할 수 있다.:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
관용적으로, 탐색 메서드의 반환 타입으로 타겟 빈을 식별할 수 있다.
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
추상 클래스가 기본적으로 무시되는 스프링 컴포넌트 스캔 규칙에 호환되려면, 일반적으로 어노테이션 탐색 메서드는 구체 스텁 구현을 통해 선언해야 한다. 이 제한은 명시적으로 등록되었거나 명시적으로 클래스 등록된 빈 클래스엔 적용하지 않는다.
상이한 범위가 적용된 타겟 빈에 접근하는 다른 방법으로 ObjectFactory
/Provider
주입 포인트가 있다. Scoped Beans as Dependencies 부분을 참고하라.
org.springframework.beans.factory.config
패키지의 ServiceLocatorFactoryBean
도 유용하다.
임의의 메서드 교체 (Arbitrary Method Replacement)
탐색 메서드 주입보다 덜 유용한 방법으로 빈의 또다른 메서드 구현을 통한 임의의 메서드 교체가 있다. 실제로 이 기능이 필요하지 않으면 나머지 부분을 건너뛰어도 된다.
XML 기반 설정 메타데이터에서, replaced-method
요소를 사용해서 배치된 빈에 존재하는 메서드 구현을 다른 것으로 교체할 수 있다. 오버라이드하려는 computeValue
메서드를 가진 다음 클래스를 살펴보아라.:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
org.springframework.beans.factory.support.MethodReplacer
인터페이스를 구현한 클래스는 새 메서드 정의를 제공한다. 다음 예를 보자.:
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
원래 클래스를 배포하고 메서드 오버라이드를 명시하는 빈 정의 예제는 다음과 같다.:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
하나 이상의 <arg-type/>
요소를 <replaced-method/>
요소 내에 사용해서 오버라이드하려는 메서드의 시그니쳐를 지정할 수 있다. 인자에 대한 시그니쳐는 클래스 내에 존재하는 오직 메서드가 오버로드되어 여러 경우가 존재할 때만 필요하다. 편의상 인자의 타입 문자열은 적절한 타입 명의 부분문자열이 될 수 있다. 예를 들어 java.lang.String
은 다음과 일치한다.:
java.lang.String
String
Str
인자의 수는 가끔 개별 가능한 선택을 구별하기에 충분하기 때문에, 이 축약어는 타이핑을 줄일 수 있다.
1.5. 빈 범위
빈 정의를 작성할 때, 빈 정의의 클래스 실제 인스턴스를 생성하는 레시피를 작성한다. 빈 정의가 레시피라는 아이디어는 중요하다, 이는 하나의 레시피로 많은 객체 인스턴스를 만들 수 있다는 의미이다.
특정 빈 정의로 생성된 객체에 연결할 의존성이나 설정 값과 같은 것뿐만이 아니라, 객체의 범위까지 제어할 수 있다. 이 접근은 강력하고 유연하다. 자바 클래스 레벨에서의 객체 범위를 다루지 않고, 작성한 설정을 통해 객체의 범위를 선택할 수 있기 때문이다. 빈은 많은 범위 중 하나를 선택하여 정의할 수 있다. 스프링 프레임워크는 여섯 종류의 범위를 제공한다, 그 중 네 가지는 웹과 관련된 ApplicationContext
에서만 사용 가능하다. 또한 커스텀 범위를 만들 수도 있다.
다음 표는 제공되는 범위를 기술한 것이다.
표 3. 빈 범위
범위 | 설명 |
---|---|
싱글턴(singleton) | (기본) 스프링 IoC 컨테이너마다 빈 정의당 하나의 객체 인스턴스만을 가진다. |
프로토타입(prototype) | 빈 정의당 여러 객체 인스턴스를 가질 수 있다. |
요청(request) | 빈 정의당 단일 HTTP 요청의 라이프사이클을 범위로 가진다. 각각의 HTTP 요청은 빈 정의로 생성된 하나의 인스턴스 빈을 가진다. 오직 웹 관련 ApplicationContext 에서만 유효하다. |
세션(session) | 빈 정의당 HTTP Session 의 라이프사이클을 범위로 가진다. 오직 웹 관련 ApplicationContext 에서만 유효하다. |
애플리케이션(application) | 빈 정의당 ServletContext 의 라이프사이클을 범위로 가진다. 오직 웹 관련 ApplicationContext 에서만 유효하다. |
웹소켓(websocket) | 빈 정의당 WebSocket 의 라이프사이클을 범위로 가진다. 오직 웹 관련 ApplicationContext 에서만 유효하다. |
스프링 3.0부터, 쓰레드 범위가 사용 가능하지만 기본적으로 등록되어있진 않다. 더 만흥ㄴ 정보는
SimpleThreadScope
문서를 살펴봐라. 이 범위를 등록하는 방법이나 다른 커스텀 범위에 대한 내용은 커스텀 범위 사용하기를 살펴봐라.
1.5.1. 싱글턴 범위
오직 싱글턴 빈 하나의 인스턴스만이 관리되고, 스프링 컨테이너에 의해 빈에 대한 모든 요청이 하나의 빈 인스턴스만 반환한다.
빈을 정의하고 범위가 싱글턴일 때, 스프링 IoC 컨테이너는 빈 정의에 의해 객체당 정확히 하나의 인스턴스만 생성한다. 이 인스턴스는 싱글턴 빈 캐시에 저장된다. 그리고 이후 빈에 대한 요청과 참조는 캐시된 객체를 반환한다. 다음은 싱글턴 범위가 동작하는 방법을 나타낸다.:
싱글턴 빈에 대한 스프링 컨셉은 GoF 디자인 패턴에 정의된 싱글턴과 다르다. GoF 싱글턴은 객체의 범위를 하드코드로 제한하고 클래스로더 당 특정 클래스의 하나의 인스턴스만 가지게 한다. 스프링 싱글턴의 범위는 컨테이너 당, 빈 당 하나로 제한된다. 이는 단일 스프링 컨테이너에서 특정 클래스의 하나의 빈을 정의한다면, 스프링 컨테이너는 빈 정의에 의해 하나의 클래스의 인스턴스를 생성한다는 것이다. 싱글턴 범위는 스프링 기본 범위이다. XML로 싱글턴 빈을 정의하려면, 다음 예제처럼 정의하면 된다.:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2. 프로토타입 범위
싱글턴이 아닌 프로토타입 범위는 특정 빈에 대한 매 요청마다 새로운 빈 인스턴스를 생성한다. 빈이 다른 빈에 주입되거나 getBean()
메서드로 컨테이너에게 호출해서 빈을 요청한다. 규칙에 따라, 상태를 가진(stateful) 빈은 프로토타입 범위를 사용해야 하고, 상태가 없는(stateless) 빈은 싱글턴 범위를 사용해야 한다.
다음 다이어그램은 스프링 프로토타입 범위를 나타낸다.:
(Data Access Object(DAO)는 보통 프로토타입으로 구성되지 않는다. 왜냐하면 DAO는 특정 상태를 가지고 있지 않기 때문이다.)
다음 예는 XML에서 프로토타입 빈을 정의한다.:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
다른 범위와 대조적으로, 스프링은 프로토타입 빈의 모든 라이프사이클을 관리하지 않는다. 컨테이너는 프로토타입 객체를 인스턴스화하고, 구성한다. 그리고 빈을 클라이언트에게 건낸다. 그리고 이 과정에서 아무런 기록이 없다. 그러므로 모든 범위의 객체에서 초기화 라이프사이클 콜백 메서드가 호출됨에도, 프로토타입의 경우 소멸(destsruction) 콜백이 호출되지 않는다. 클라이언트 코드는 프로토타입 객체에 대해 스스로 정리해야 하고, 객체가 소유한 자원을 해제해야 한다. 스프링 컨테이너가 프로토타입 빈을 해제하려면 bean post-processor를 사용해라. 이는 정리해야 할 빈의 참조를 가지고 있다.
어떤 점에서 스프링 컨테이너 프로토타입 빈의 역할은 자바 new
연산자를 대체하는 것이다.모든 생명주기는 클라이언트가 관리해야 한다. (스프링 컨테이너 빈의 생명주기는 생명주기 콜백을 참고하라.)
Work In Process
1.5.3. 프로토타입 빈을 의존하는 싱글턴 빈
1.5.4. 요청, 세션, 애플리케이션, 웹소켓 범위
1.5.5. 커스텀 범위
1.6. 빈 생태계 커스터마이징
1.7. 빈 정의 상속
1.8. 컨테이너 확장 지점
1.9. 어노테이션 기반 컨테이너 설정
1.10. 클래스패스 스캐닝과 컴포넌트 관리
1.11. JSR 330 표준 어노테이션 사용하기
1.12. Java 기반 컨테이너 설정
1.13. 환경 추상화
1.14. LoadTimeWeaver 등록하기
1.15. ApplicationContext의 추가 기능
1.16. BeanFactory
운영 체제
운영체제와 셸 명령어 위주의 내용입니다.
커널 (Kernel)
TL;DR
컴퓨터 운영 체제의 핵심이 되는 컴퓨터 프로그램으로, 시스템의 모든 것(CPU, Memory, Devices, ...) 을 통제한다.
대부분의 시스템에서 부팅할 때, 부트로더 이후에 로드하는 프로그램 중의 하나이다. 커널은 소프트웨어로부터의 입출력 요청과 같은 부팅과정을 관리한다.
역할
- 보안: 컴퓨터 하드웨어와 프로세스의 보안을 책임진다.
- 자원 관리: 한정된 시스템 자원을 효율적으로 관리하여 프로그램의 실행을 원할하게 한다.
- 추상화: 운영 체제의 복잡한 내부를 감추고 깔끔하고 일관성 있는 인터페이스를 제공하기 위해 하드웨어 추상화를 한다. 이는 프로그래머가 여러 장비에서 작동하는 프로그램을 개발하는 것을 돕는다.
종류
- 단일형 커널: 커널의 다양한 서비스 및 고수준의 하드웨어 추상화를 하나로 묶은 것, OS개발자 입장에서 유지 보수가 어렵지만 성능이 좋다.
- 단일형 커널의 예: Linux, Unix, 윈도9X 계열
- 마이크로 커널: 하드웨어 추상화에 대한 간결한 작은 집합을 제공하고, 더 많은 기능은 서버 응용 소프트웨어를 통해 제공
- 마이크로 커널의 예: 미닉스
- 하이브리드 커널: 모노리틱 커널과 마이크로 커널의 혼합형
- 나노 커널
- 커널 코드의 양이 매우 작은 커널
- OS 하위의 하이퍼바이저 레이어
- 하드웨어 추상화 계층(HAL)
참고
POSIX
TL;DR
POSIX는 서로 다른 UNIX OS의 공통 API를 정리한 IEEE 애플리케이션 인터페이스 규격이다. 목적은 이식성이 높은 유닉스 응용 프로그램 개발하는 것이다.
POSIX의 내용
POSIX엔 아래 외에 다양한 분야의 규격이 존재한다.
- 커널로의 C 언어 인터페이스 시스템 콜
- 프로세스 환경
- 파일과 디렉토리
- 시스템 데이터베이스
- tar 압축 포맷
리눅스와 포직스
리눅스는 포직스 호환 운영 체제 커널을 목표로 개발되었다.
POSIX 인증 운영 체제들
위키백과에 따르면 AIX, HP-UX, macOS, Solaris 등이 POSIX 표준을 따르는 운영 체제로 알려져있다. 대부분의 리눅스 배포판은 POSIX 호환성 인증은 없지만, 대부분 준수하는 것으로 알려져있다.
MS Windows를 위한 POSIX
- Cygwin: MS Windows를 위한 가능한 큰 POSIX 호환 개발환경과 런타임 환경을 제공한다. Linux 호환 Layer를 구성하기 때문에, MinGW에 비해 Unix 호환성이 높고 안정적으로 작동한다.
- MinGW: 쵝소한의 POSIX 환경을 제공한다. Windows Native로 동작하기 때문에 Cygwin에 비해 성능이 좋다.
- Windows Subsystem for Linux: Ubuntu 이미지를 통해 리눅스 실행환경을 제공한다. Windows 10부터 지원
Signal (IPC, Inter-Process Communication)
Signal, 유닉스 신호는 POSIX 호환 운영 체제에서 쓰이는 프로세스 간의 통신이다.
신호 보내기
Ctrl-C
(SIGINT): 기본적으로 프로세스를 종료Ctrl-Z
(SIGTSTP): 프로세스가 실행을 유예Ctrl-\
(SIGQUIT): 프로세스를 종료시킨 뒤 코어를 덤프Ctrl-T
(SIGINFO): (모든 유닉스에서 지원하진 않음) 명령에서 지원하는 경우 운영 체제가 실행 중인 명령에 대한 정보를 표시
Shell
TL;DR
운영 체제 기능과 서비스를 구현하는 인터페이스를 제공하는 프로그램이다. 사용자와 운영 체제의 커널 사이의 인터페이스를 감싸는 프로그램이기 때문에 셸이란 이름이 붙었다.
셸의 목록
CLI
- Unix
- sh - ash, bash
- csh - tsch
- ksh
- zsh
- COMMAND.COM: MS-DOS 용
- CMD.EXE: Windows NT 용
GUI
- 윈도 탐색기: MS Windows 계열
- 매킨토시 파인더
- X 윈도 시스템
참고
심볼릭 링크, 하드 링크
심볼릭 링크(symbolic link)
소프트 링크(soft link)라고도 한다. 윈도우의 바로가기와 비슷한 개념으로 파일을 복사할 필요 없이 사용 가능하고, 원본 파일이 업데이트될 경우 링크된 파일에 바로 적용된다. 하나의 파일을 여러 사용자가 사용하거나 동일한 라이브러리를 구성해야 할 경우 사용된다.
ls
명령어를 사용해서 심볼릭 링크를 보면 아래와 같이 실제 파일 혹은 프로세스가 존재하는 위치를 가리킨다.
$ ln -s /home/ubuntu/workspace/nginx /etc/nginx
$ ls /etc/ | grep nginx
lrwxrwxrwx 1 root root 28 Dec 18 06:13 nginx -> /home/ubuntu/workspace/nginx
하드 링크(hard link)
하드 링크는 링크된 파일의 내용이 변경된 경우에 원본 파일도 동일하게 변경된다. 하나의 파일을 가리키는 링크가 하나 더 생기는 것이다. 그러므로 파일이 삭제되어도 남아있는 링크가 있다면 자원에 접근할 수 있다.
$ echo hello > hello
$ cat hello
hello
$ ln hello bye
$ echo bye >> bye
$ cat bye
hello
bye
$ cat hello
hello
bye
$ rm hello
$ cat bye
hello
bye
셸 명령어 목록
chsh
기본 사용 셸을 변경할 때 사용한다.
$ chsh -s /bin/zsh
which
프로그램의 위치를 리턴한다.
$ which mdbook
/home/ubuntu/.cargo/bin/mdbook
uname
현재 시스템의 정보를 출력한다. 플래그 없이 사용하는 경우 -s(커널 명) 플래그를 사용하는 것과 같다.
$ uname -a # 모든 정보 표시
Linux a2f3d1dd-5b17-e822-f4c8-c7bc7075f1c3 3.13.0-100-generic #147-Ubuntu SMP Tue Oct 18 16:48:51 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
AIX 모니터링 명령어들
AIX 자원 모니터링을 위한 셸 스크립트들입니다.
Process
echo `ps -ef | grep "java" | wc -l`
CPU 사용량
#!/bin/ksh
#create timestamp
timestamp=`date +%Y%m%d_%H%M%S`
#echo "Gathering CPU usage : "$timestamp
#create target file name
target='./dat/cpu_'$timestamp'.out'
#echo 'file path is '$target
#bulk=`vmstat`
#count
cnt=3
#create target file to gather vmstat information
vmstat 1 $cnt > $target
user=`tail -n $cnt $target | awk '{print $14}'`
sys=`tail -n $cnt $target | awk '{print $15}'`
idle=`tail -n $cnt $target | awk '{print $16}'`
sum=0
for KK in $idle
do
# echo 'idle: '$KK
let "sum=sum+$KK"
done
let "avg=sum/$cnt"
let "avg=100-avg"
#echo 'Average idle of CPU usage: '$avg
echo $avg
#echo "Job ends..."
메모리 사용량
#!/bin/ksh
# getMEMusage.sh
# Physical Memory
#phy=`svmon -G |grep memory |perl -ane 'printf"%0.1f \n", 100 - ( ( $F[3] / $F[1] ) * 100 )'`
# Paging space
#pg=`svmon -G |grep 'pg space' |perl -ane 'printf"%0.1f \n", ( ( $F[3] / $F[2] ) * 100 )'`
#echo "Physical MEM usage: "$phy
#echo "Paging Space usage: "$pg
#echo $phy", "$pg
###########################
total=`svmon -G -O unit=MB | grep "memory" | awk '{print $2}'`
work=`svmon -G -O unit=MB | grep "in use" | awk '{print $3}'`
#echo "total: "$total
#echo "work: "$work
used=`echo "$work $total" | awk '{printf "%.2f", $1/$2*100}'`
#echo "MEM usage(%): "$used
echo $used
# Job finished...
디스크 사용량
#!/bin/ksh
# getDiskUsage.sh
#target: grep으로 검색할 경로
target=$1
#disk usage
disk=`df -gi | grep ".*"$target"$" | awk '{print $4}' | tr -d '\%'`
echo $disk
#echo "Job ends..."
Windows 커맨드 프롬프트에서 환경변수 설정하기
환경변수 조회
set
명령으로 시스템/사용자 환경변수 모두 조회할 수 있다.
set
구분하여 보려면, 아래의 명령어로
시스템 환경변수 조회
reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
사용자 환경변수 조회
reg query HKEY_CURRENT_USER\Environment
setx
명령으로 변수 설정
setx \m coffee "starbucks"
chocolatey의 RefreshEnv
명령으로 환경변수 refresh
RefreshEnv
Linux의 hostid
- hostid Man page: https://linux.die.net/man/1/hostid
Docker 시작하기
Docker 공식 문서 Get started with Docker를 번역한 내용입니다. 직접 실습하면서 결과를 적어두기 때문에 공식 웹사이트의 문서와 다를 수 있습니다.
차례
- Docker 소개 및 설치
- 컨테이너
- 서비스
- 스웜
- 스택
- 애플리케이션 배포하기
시작하기 - Part 1: Docker 소개 및 설치
환영합니다! 당신이 Docker에 대해 배우길 원해서 기쁩니다. The Docker Get Started Tutorial 튜토리얼은 아래와 같이 진행될 것입니다.
- Docker 환경 설치하기 (이번 페이지)
- 이미지를 만들고 컨테이너로 실행하기
- 다중 컨테이너에서 실행하기 위한 애플리케이션 확장하기
- 클러스터로 애플리케이션 분산하기
- 데이터베이스 추가로 서비스 쌓기
- 운영환경으로 애플리케이션 배포하기
Docker 컨셉
Docker는 개발자와 시스템 관리자를 위한 애플리케이션 개발, 배포, 실행 가능한 컨테이너 플랫폼입니다. 애플리케이션 배포를 위한 리눅스 컨테이너 사용을 containerization이라고 불렀습니다. 컨테이너 자체는 새롭지 않지만, 애플리케이션 배포를 단순화하기 위한 컨테이너는 그렇지 않습니다.
Containerization은 아래의 특징 때문에 나날이 유명해졌습니다.
- 유연함: 아무리 복잡한 애플리케이션도 컨테이너화 할 수 있습니다.
- 가벼움: 컨테이너들은 호스트 커널과 자원을 공유합니다.
- 교환 가능함: 업데이트와 업그레이드를 위한 즉시 배포가 가능합니다.
- 휴대성: 로컬 환경에서 빌드하고, 클라우드 환경으로 배포하고, 어디에서나 실행 가능합니다.
- 확장 가능함: 컨테이너를 늘리고 자동으로 분산 환경을 만들 수 있습니다.
- 적재 가능함: 서비스들을 즉시 수직적으로 쌓을 수 있습니다.
이미지와 컨테이너
컨테이너는 이미지를 통해 구동할 수 있습니다. 이미지는 , 애플리케이션을 구동하는데 필요한 코드, 실행환경, 라이브러리, 환경변수, 설정 파일 등의 필요한 모든 것들을 포함한, 실행 가능한 패키지입니다.
컨테이너는 이미지의 런타임 인스턴스입니다. 컨테이너는 이미지가 실행될 때 메모리 상에 올라간 이미지를 의미합니다. 이는 상태가 있는 이미지, 유저 프로세스 등을 의미합니다. 우리는 구동 중인 컨테이너의 목록을 docker ps
명령어로 확인할 수 있습니다.
컨테이너와 가상 머신
컨테이너는 리눅스 상에서 네이티브로 실행되고 호스트의 커널을 다른 컨테이너들과 공유합니다. 컨테이너는 별개의 프로세스로 동작하지만, 다른 실행 가능한 컨테이너나 프로그램보다 많은 메모리를 점유하지 않습니다. 이 것이 도커를 가볍게 만듭니다.
그에 반해서, 가상 머신은 하이퍼바이저를 통해 호스트 자원에 가상 접근하는 게스트 운영체제를 구동합니다. 일반적으로 가상 머신은 대부분의 애플리케이션이 필요한 것보다 많은 자원을 제공합니다.
Docker 환경 준비하기
Docker CE/EE를 설치합니다.
Docker version 확인하기
docker --version
명령어를 통해 Docker version을 확인합니다.
$ docker --version
Docker version 18.06.1-ce, build e68fc7a
docker info
혹은docker version
명령어로 도커에 대한 디테일한 정보를 확인합니다.
$ docker info
Client:
Version: 18.06.1-ce
API version: 1.38
Go version: go1.10.3
Git commit: e68fc7a
Built: Tue Aug 21 17:21:31 2018
OS/Arch: darwin/amd64
Experimental: false
Server:
Engine:
Version: 18.06.1-ce
API version: 1.38 (minimum version 1.12)
Go version: go1.10.3
Git commit: e68fc7a
Built: Tue Aug 21 17:29:02 2018
OS/Arch: linux/amd64
Experimental: true
퍼미션 에러와
sudo
명령어 사용을 위해, 사용자 계정을docker
그룹에 추가하세요.
Permisson error를 피하기 위한 docker
그룹에 사용자 추가하기
1. docker
그룹을 생성한다.
$ sudo groupadd docker
2. 사용자를 docker
그룹에 추가한다.
$ sudo usermod -aG docker $USER
3. 그룹권한을 재설정 하기위해 로그아웃 후 다시 접속한다.
4. sudo
명령어 없이 docker
를 실행하여 권한테스트를 한다.
$ docker run hello-world
Docker 설치 테스트하기
- 설치한 Docker를 간단한 도커 이미지인 hello-world를 통해 테스트합니다.
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
d1725b59e92d: Pull complete
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
- 다운로드한
hello-world
이미지를 목록에서 확인해보세요.
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
postgres latest 39dfbcb5424b 4 weeks ago 311MB
ubuntu 16.04 4a689991aa24 7 weeks ago 116MB
ubuntu latest ea4c82dcd15a 7 weeks ago 85.8MB
hello-world latest 4ab4c602aa5e 3 months ago 1.84kB
ubuntu 12.04 5b117edd0b76 20 months ago 104MB
- 이미지를 통해 생성된
hello-world
컨테이너를 목록에서 확인하세요. 만약 컨테이너가 여전히 구동 중이라면,--all
옵션은 필요하지 않습니다.
$ docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
063b9e26ba47 ubuntu "/bin/bash" 13 days ago Up 13 days brave_liskov
정리 및 치트 시트
## Docker CLI 명령어 목록
docker
$ docker container --help
## Docker 버전 및 정보 조회
$ docker --version
$ docker version
$ docker info
## Docker 이미지 실행하기
$ docker run hello-world
## Docker 이미지 목록 조회
$ docker image ls
## Docker 컨테이너 목록 조회 (running, all, all in quiet mode)
$ docker container ls
$ docker container ls --all
$ docker container ls -aq
Part 1 결론
Containerization은 CI/CD를 매끄럽게 만듭니다. 그 예로,
- 애플리케이션은 시스템 의존성이 없습니다.
- 분산 애플리케이션의 어떤 부분도 업데이트될 수 있습니다.
- 자원 밀도가 최적화될 수 있습니다.
Docker와 함께, 무거운 VM 호스트를 구동하는 것이 아닌, 애플리케이션 확장이 주된 관심사가 됩니다.
시작하기 - Part 2: 컨테이너
전제 조건
- Docker 1.13 이상 설치
- Part 1 읽기
hello-world
이미지 실행 가능한 환경인지 테스트
$ docker run hello-world
소개
이제 Docker의 방식으로 애플리케이션을 만들기 시작할 때가 되었습니다. 이번 컨테이너 페이지를 통해, 우리는 애플리케이션 계층의 밑바닥부터 시작할 겁니다. 컨테이너가 어떻게 운영환경에서 행동하는지 Part 3: Service에서 다룰 것입니다. 마지막으로, Part 5: Stack에서 모든 서비스 상호작용의 정의에 대해 다룰 것입니다.
- Stack
- Services
- Container (지금 우리는 여기에 있습니다)
개발 환경이 필요합니다
과거엔, 파이썬 애플리케이션을 작성하려고 하면, 첫 번째 해야 할 일은 파이썬 실행환경을 설치하는 것이었습니다. 하지만 로컬 개발 환경에서 애플리케이션이 의도한 대로 동작하는 것처럼, 운영 환경에서도 잘 동작하는 것이 필요합니다.
Docker와 함께라면, 우리는 파이썬 런타임을 설치할 필요 없는 이미지로 가져갈 수 있습니다. 그러면, 기본 파이썬 이미지를 우리의 애플리케이션 코드 옆에 두고 빌드할 수 있습니다.
이런 포터블 이미지를 Dockerfile
이라고 부릅니다.
Dockerfile
로 컨테이너 정의하기
Dockerfile
은 컨테이너가 어떤 환경을 가져야 하는지에 대해 정의합니다. 네트워크 인터페이스나 디스크 드라이버 같은 자원에 접근하는 것은 시스템의 나머지로부터 격리된 환경 안에서 가상화됩니다. 그래서 바깥세상과 통신할 수 있는 포트를 매핑해야 하고, 어떤 환경 파일을 복사해야 할지 구체적으로 정의해야 합니다. 그렇지만, 그 후엔 Dockerfile
에 정의한 대로 애플리케이션의 빌드가 어디에서나 동일하게 수행될 것입니다.
Dockerfile
빈 디렉터리를 만드세요. 새로 생성한 디렉터리로 경로를 변경(cd
)해서 Dockerfile
을 생성하고, 아래 내용을 복사해서 저장합니다. Dockerfile의 스크립트를 설명하는 주석을 읽어보세요.
# 파이썬 런타임을 부모 이미지로 사용합니다.
FROM python:2.7-slim
# 작업 디렉터리를 /app으로 설정합니다.
WORKDIR /app
# 현재 디렉터리의 내용을 /app의 컨테이너로 복사합니다.
COPY . /app
# requirements.txt에 기술된 패키지들을 설치합니다.
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# 컨테이너 바깥과 통신할 포트인 80번을 열어둡니다.
EXPOSE 80
# 환경 변수를 정의합니다.
ENV NAME World
# 컨테이너를 구동할 때 app.py를 실행합니다.
CMD ["python", "app.py"]
Dockerfile
은 우리가 아직 만들지 않은 app.py
나 requirements.txt
같은 파일들을 필요로 합니다. 이제 이 파일들을 만들어봅시다.
애플리케이션 그 자체
requirements.txt
와 app.py
, 두 파일을 Dockerfile
과 같은 폴더에 생성합니다. 보이는 것과 같이 쉽게 애플리케이션이 완성됐습니다. Dockerfile
이 이미지에 들어갈 때, app.py
와 requirements.txt
역시 Dockerfile
의 Copy
명령어로 인해 나타납니다. 그리고 app.py
로부터의 결과는 EXPOSE
명령으로 인해 HTTP로 접근 가능합니다.
requirements.txt
Flask
Redis
app.py
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
이제 우리는 pip install -r requirements.txt
스크립트 문장이 플라스크와 레디스 라이브러리를 설치하고 애플리케이션이 환경변수 NAME
와 socket.gethostname()
을 호출한 결과를 출력하는 것을 볼 것입니다. 마침내, 레디스가 실행 중이 아니기 때문에, 우리는 레디스를 사용하려다 실패하고 에러메시지를 뿜어내는 것을 예상할 수 있습니다.
노트: 내부 컨테이너가 컨테이너 ID를 조회할 때 호스트의 이름에 접근하면 실행 중인 프로세스 ID가 검색됩니다.
바로 이 것입니다! 파이썬이나 requirements.txt
안의 아무것도, 이미지를 빌드하거나 수행할 때 시스템에 필요로 하지 않습니다. 파이썬과 플라스크를 개발환경에 설치한 것처럼 보이지 않지만, 이미 당신은 가지고 있습니다.
애플리케이션 빌드하기
우리는 애플리케이션을 만들 준비가 됐습니다. 여전히 디렉터리 최상위에 있는지 확인해보세요. 여기 ls
명령어를 사용해서 나타내 보세요.
$ ls
total 24
drwxr-xr-x 5 sshplendid staff 170 12 15 00:18 .
drwxr-xr-x 8 sshplendid staff 272 12 14 21:54 ..
-rw-r--r-- 1 sshplendid staff 169 12 14 21:55 Dockerfile
-rw-r--r-- 1 sshplendid staff 669 12 15 00:18 app.py
-rw-r--r-- 1 sshplendid staff 12 12 14 23:54 requirements.txt
이제 빌드를 명령해보세요. 이는 도커 이미지를 생성하고 -t
태그를 사용해서 친숙한 이름을 붙일 수 있습니다.
역자+ 지금은 제 고양이 이름인
waltz
라는 이름으로 만들어 보겠습니다.
$ cd /Volumes/Mango/workspaces/kata/docker
$ docker build -t waltz .
빌드한 이미지가 어디에 있나요? 당신의 기기 로컬 Docker 이미지 저장소에 있습니다.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
waltz latest d3582b3f0b85 31 seconds ago 131MB
python 2.7-slim 0dc3d8d47241 4 weeks ago 120MB
postgres latest 39dfbcb5424b 4 weeks ago 311MB
ubuntu 16.04 4a689991aa24 8 weeks ago 116MB
ubuntu latest ea4c82dcd15a 8 weeks ago 85.8MB
hello-world latest 4ab4c602aa5e 3 months ago 1.84kB
ubuntu 12.04 5b117edd0b76 20 months ago 104MB
리눅스 사용자를 위한 문제 해결
프록시 서버 설정
%$^$%^
애플리케이션 실행하기
컨테이너의 80번 포트와 호스트 장비의 4000번 포트를 매핑해서 -p
태그와 함께 애플리케이션을 실행합니다.
$ docker run -p 4000:80 waltz
http://0.0.0.0:80
에서 파이썬으로 동작하는 애플리케이션이 있다는 메시지를 보셨을 겁니다. 그러나 메시지는 컨테이너 내부로부터 오는 것으로, 이는 올바른 URL인 http://localhost:4000
을 만들 필요가 없이 컨테이너의 80번 포트가 4000번 포트에 매핑되었다는 것을 알 필요가 없다는 의미입니다.
웹브라우저에 접속해서 위 URL을 입력하고 이동해봅시다.
노트: 만약 윈도7에서 도커 툴박스를 사용하고 있다면, Docker 장비 IP를 사용하세요. http://192.168.99.100:4000이 예입니다. IP 주소를 찾기 위해,
docker-machine ip
명령어를 사용하세요.
curl
명령어를 사용해서 동일한 내용을 볼 수 있습니다.
curl http://localhost:4000
<h3>Hello World!</h3><b>Hostname:</b> 3dbbee09f22c<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>%
윈도에서 컨테이너를 멈추는 방법
윈도에서CTRL+C
명령으로 컨테이너를 멈출 수 없습니다. 그러므로 먼저CTRL+C
를 치고 프롬프트로 돌아가거나 새로운 셸을 열어서docker container ls
를 입력해서 구동 중인 컨테이너의 리스트를 확인합니다. 그리고docker container stop <컨테이너 이름이나 ID>
를 입력해서 컨테이너를 중지시킵니다. 그렇지 않으면 다음에 컨테이너를 재구동할 때 대몬으로부터 에러 응답을 받을 수 있습니다.
이제 애플리케이션을 백그라운드에서 동작(detach mode)하도록 명령합니다.
$ docker run -d -p 4000:80 waltz
이 명령을 실행하면 컨테이너에 대한 긴 ID를 출력하고 다시 터미널 명령으로 돌아옵니다. 컨테이너는 백그라운드에서 동작합니다. 우리는 docker container ls
명령을 통해 짧은 컨테이너 ID를 확인할 수 있습니다. 그리고 두 ID 모두 명령을 실행할 때 사용할 수 있습니다.
$ docker continaer ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abf81e3f6825 waltz "python app.py" 6 seconds ago Up 4 seconds 0.0.0.0:4000->80/tcp trusting_khayyam
CONTAINER ID
는 http://localhost:4000
과 매핑됩니다.
이제 CONTAINER ID
를 사용해서 docker container stop
명령으로 프로세스를 끝내봅시다.
$ docker container stop abf81e3f6825
이미지 공유하기
방금 생성한 애플리케이션의 휴대성을 높이기 위해, 이미지를 업로드해서 다른 어딘가에서 실행해봅시다. 그 후에, 운영 환경에 컨테이너를 배포하기 원할 때, 레지스트리에 푸시하는 방법을 알아야 할 것입니다.
레지스트리는 저장소의 모음이고, 저장소는 이미지의 모음입니다. 코드가 이미 작성되었다는 사실을 제외하면 GitHub와 유사합니다. 레지스트리의 계정은 많은 저장소에서 생성할 수 있습니다. docker
CLI는 기본적으로 Docker 퍼블릭 저장소를 사용합니다.
Note: 우리는 여기서 도커 퍼블릭 저장소를 사용합니다. 왜냐하면 무료에다가 이미 설정되어 있기 때문입니다. 그러나 여러 저장소 중 하나를 선택해서 사용할 수 있고, 심지어 Docker Trusted Registry를 통해서 자신만의 개인 레지스트리를 만드는 것도 가능합니다.
Docker ID로 로그인하기
만약 Docker 계정이 없다면, hub.docker.com 에서 계정을 생성하세요. 가입한 사용자 명으로 도커 퍼블릭 레지스트리에 로그인하세요.
$ docker login
이미지 태그하기
레지스트리에서 저장소와 연계된 이미지의 표기는 사용자명/저장소:태그
입니다. 태그는 선택적이지만, 사용하길 추천합니다. 레지스트리가 도커 이미지들에게 버전을 주는 방식이기 때문입니다. 저장소와 태그에 get-started:part2
같은 의미 있는 이름을 붙여줍니다. 이는 get-started
저장소에 part2
라고 이미지에 태그 한 것입니다.
이제 이미지에 태그 하기 위해 이 것들을 함께 넣어봅시다. 의도한 목적지에 이미지를 업로드하기 위해, 사용자 명과 저장소, 태그 명을 사용해서 docker tag image
명령을 실행해봅시다.
역자+ 이전에 생성한 'waltz' 이미지를 docker hub 계정인 'sshplendid' 하위의 'get-started' 저장소에 'part2'라는 태그로 업로드할 것입니다.
$ docker tag waltz sshplendid/get-started:part2
docker image ls
명령을 실행해서 새롭게 태그된 이미지를 확인해봅시다.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
waltz latest d3582b3f0b85 2 days ago 131MB
sshplendid/get-started part2 d3582b3f0b85 2 days ago 131MB
python 2.7-slim 0dc3d8d47241 4 weeks ago 120MB
ubuntu latest ea4c82dcd15a 8 weeks ago 85.8MB
...
이미지 발행하기
태그 한 이미지를 저장소에 업로드해봅시다.
$ docker push 사용자명/저장소:태그
명령이 끝나면, 업로드한 이미지는 공개적으로 사용 가능합니다. 만약 도커 허브에 로그인했다면, 그곳에 있는 이미지를 pull 명령으로 볼 수 있습니다.
원격 저장소로부터 이미지를 끌어와 실행하기
지금부터 docker run
명령으로 어떤 장비에서든 애플리케이션을 실행할 수 있습니다.
$ docker run -p 4000:80 username/repository:tag
이미지가 로컬 장비에서 사용할 수 없는 상태라면, Docker는 저장소로부터 이미지를 끌어옵니다.
$ docker run sshplendid/get-started:part2
Unable to find image 'sshplendid/get-started:part2' locally
part2: Pulling from sshplendid/get-started
a5a6f2f73cd8: Already exists
8da2a74f37b1: Already exists
09b6f498cfd0: Already exists
f0afb4f0a079: Already exists
7be2300e1a8b: Already exists
b34ffcdf5978: Already exists
aa01dc4a3353: Already exists
Digest: sha256:0dff90a601cec25d6e7e7662d28e501758a7d02ebb40700a91a87117847a0a88
Status: Downloaded newer image for sshplendid/get-started:part2
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
어디서 docker run
명령을 실행하던, 파이썬과 마찬가지로 requirements.txt
의 모든 의존성을 함께 이미지와 끌어당겨옵니다. 그리곤 코드를 실행합니다. 가벼운 짐을 싸서 여행하는 것처럼, 호스트 장비에 어떤 것도 설치할 필요 없이 Docker를 통해 실행하면 됩니다.
Part 2의 결론
이 페이지의 내용은 이 것이 전부입니다. 다음 장에서 서비스 안에서 컨테이너를 구동함으로써 애플리케이션의 스케일링 방법에 대해 배울 것입니다.
정리 및 cheat sheet
이 장에서 사용한 기본적인 docker 명령들과 알아두면 좋을 명령어 리스트입니다.
docker build -t waltz . # 현재 디렉터리의 Dockerfile을 이용해서 이미지 생성하기
docker run -p 4000:80 waltz # 4000번 포트를 80번 포트와 매핑하여 "waltz" 실행하기
docker run -d -p 4000:80 waltz # 위 명령을 백그라운드 모드로 실행하기
docker container ls # 모든 구동 중인 컨테이너 리스트
docker container ls -a # 멈춰있는 컨테이너를 포함한 모든 컨테이너 리스트
docker container stop <hash> # 안전하게 특정 컨테이너 중지하기
docker container kill <hash> # 강제로 컨테이너 중지하기
docker container rm <hash> # 특정 컨테이너 삭제하기
docker container rm $(docker container ls -a -q) # 모든 컨테이너 삭제하기
docker image ls -a # 모든 이미지 리스트
docker image rm <image id> # 특정 이미지 삭제
docker image rm $(docker image ls -a -q) # 모든 이미지 삭제
docker login # Docker 자격증명을 이용하여 CLI 세션에서 로그인하기
docker tag <image> username/repository:tag # 레지스트리 업로드를 위한 이미지 태그
docker push username/repository:tag # 태그 한 이미지 업로드하기
docker run username/repository:tag # 레지스트리의 이미지 실행하기
시작하기 - Part 3: 서비스
전제조건
- Docker 1.13 이상 설치
- Docker Compose 준비하기. Mac/Windows 용 Docker에 이미 설치되어 있습니다. Linux의 경우 직접 설치해야 합니다. Windows 10에선 Hyper-V 없이 Docker Toolbox를 사용하세요.
- Part 1을 읽고 Part 2에서 컨테이너 생성 방법에 대해서 학습합니다.
waltz
이미지를 생성해서 레지스트리에 발행하는 것까지 해봐야 합니다. 이 장에서 공유한 이미지를 사용할 예정입니다.- 그 이미지가 배포된 컨테이너로서 작동하는지 확인합니다.
docker run -p 4000:80 사용자명/저장소:태그
명령을 실행하면,http://localhost:4000/
으로 접속되는 것을 확인해야 합니다.
소개
3장에서 애플리케이션 규모를 조절하고 로드밸런싱을 활성화할 것입니다. 이 일을 하기 위해, 서비스라는 분산 애플리케이션의 계층에 대해 알아야 합니다.
- Stack
- Service (지금 여기에 있습니다)
- Container (2장에서 다뤘습니다)
서비스에 대해
분산 애플리케이션에서 애플리케이션의 다른 조각들을 "서비스"라고 부릅니다. 한 예로, 만약 영상 공유 사이트가 있다고 가정합시다. 이 사이트는 애플리케이션 데이터를 데이터베이스에 저장하는 서비스, 사용자가 업로드한 뭔가를 백그라운드에서 변환하는 서비스, 프론트엔드를 위한 서비스 등 여러 서비스들을 포함할 것입니다.
서비스는 단지 "운영 환경의 컨테이너들"입니다. 오로지 하나의 이미지를 실행하지만, 이미지를 실행하는 방법 - 어떤 포트를 사용해야 하는지, 컨테이너의 레플리카를 수요를 충족하도록 얼마나 많이 구동해야 하는지 등 - 편성합니다. 서비스의 규모를 조절하는 것은 소프트웨어의 조각들을 실행하는 컨테이너 인스턴스의 수나 프로세스 상에서 서비스를 위한 자원을 더 할당해야 하는 것을 의미합니다.
운이 좋게도 Docker 플랫폼에서 서비스를 정의하고, 실행하고, 규모를 조절하는 것은 매우 쉽습니다. 단지 docker-compose.yml
파일을 작성하면 됩니다.
첫 docker-compose.yml
파일
docker-compose.yml
파일은 Docker container가 운영 환경에서 행동하는 방법을 정의한 YAML 파일입니다.
docker-compose.yml
docker-compose.yml
로 아래 내용을 저장하세요. 하지만 그전에 Part 2에서 레지스트리에 생성한 이미지를 발행하고 이 yml 파일을 업데이트 한 이미지를 수정해야 합니다.
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: username/repo:tag
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
networks:
webnet:
이 docker-compose.yml
파일은 도커가 아래와 같은 일을 하게 합니다.
- 레지스트리로부터 Part2에서 업로드한 이미지를 끌어오기
web
이라고 5개의 인스턴스를 구동하고 각각의 인스턴스가 최대 10%의 CPU와 50MB의 램을 사용할 수 있도록 제한하기- 컨테이너 중 하나가 동작 실패하는 경우 즉시 재시작하기
- 호스트의 4000번 포트를
web
의 80번 포트와 매핑하기 web
의 컨테이너들이webnet
이라 불리는 로드 밸런스 네트워크를 통해 80번 포트를 공유하도록 관리하기 (내부적으로 컨테이너 그 자체가web
의 80번 포트로 발행함)- webnet` 네트워크를 기본 설정(로드밸런싱으로 뒤덮은 네트워크)으로 정의하기
새로운 로드 밸런스 된 애플리케이션 실행하기
docker stack deploy
명령을 실행하기 전에 아래 명령을 먼저 실행해야 합니다.
$ docker swarm init
Swarm initialized: current node (mo9cxneiwqtmu3ewqm7q6tdwv) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-0kuhh5msy0fymymktuxwm0w0luad2jcu8nq5ldalabcckutes9-3wgp251zlg6w5moe3d9ni9ya4 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Note: 4장에서 이 명령이 뭘 의미하는지 알게 될 것입니다.
docker swarm init
을 실행하지 않으면 "this node is not a swarm manger.(이 노트는 스웜 매니저가 아닙니다.)"라는 에러를 받게 될 것입니다.
이제 실행해봅시다. 애플리케이션의 이름을 정해야 합니다. 여기선 getstartedlab
이라고 설정했습니다.
$ docker stack deploy -c docker-compose.yml getstartedlab
Creating network getstartedlab_webnet
Creating service getstartedlab_web
하나의 서비스 스택은 호스트에서 배포된 이미지의 컨테이너 인스턴스 5개를 실행합니다. 이제 살펴봅시다. 애플리케이션의 서비스 ID를 확인해봅시다.
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
a690upphrjmg getstartedlab_web replicated 2/5 username/repo:tag *:80->80/tcp
web
서비스의 출력 결과를 살펴보면, 애플리케이션 이름이 맨 앞에 붙어있습니다. 만약 위의 예처럼 이름을 붙였다면, 이름은 getstartedlab_web
이 될 것 입니다. 서비스 ID는 레플리카의 수, 이미지 이름, 노출된 포트와 함께 확인할 수 있습니다.
서비스에서 실행되는 각각의 컨테이너를 태스크라고 부릅니다. 태스크는 산술적으로 증가되는 유일한 ID를 가지고 있고, docker-compose.yml
에 정의한 레플리카의 수까지 증가합니다. 서비스의 태스크 목록을 확인해봅시다.
$ docker service ps getstartedlab_web
태스크는 시스템의 모든 컨테이너들의 목록에도 나타납니다.
$ docker container ls -q
0d339f851a17
b0eec7357bd9
212ef3c86f04
554db3ec624e
1fd1227f80cc
curl -4 http://localhost
명령을 여러 번 호출하거나 웹 브라우저에서 저 주소로 여러 번 접속해봅시다.
라운드-로빈 상태에서 각 요청을 로드밸런싱 하기 위해 5개 태스크들 중 하나를 선택합니다. 어느 쪽이던 컨테이너 ID는 바뀝니다. 컨테이너 ID들은 이전 명령인 docker container ls -q
의 결과와 일치합니다.
Windows 10 환경에서 실행하기
Windows 10 파워 셸을 이미 curl
이 사용 가능합니다. 그렇지 않으면 Git BASH나 Windows용 wget을 사용하세요.
느린 반응속도
네트워크 설정에 따라 응답은 30초까지 걸릴 수 있습니다. 이 것이 도커나 스웜 퍼포먼스를 의미하지 않지만, 나중에 나올 Redis 의존성 때문일 수도 있습니다. 지금은 방문 카운터는 아직 서비스를 추가하지 않았기 때문에 작동하지 않습니다.
애플리케이션 규모 설정하기
docker-compose.yml
의 replicas
의 값을 바꿔서 애플리케이션 규모를 조절할 수 있습니다. 변경사항을 저장하면, docker stack deploy
명령을 재실행합니다.
$ docker stack deploy -c docker-compose.yml getstartedlab
도커는 즉시 업데이트를 수행해서, 어떤 컨테이너도 멈추거나 스택을 중지할 필요가 없습니다.
이제 docker container ls -q
명령을 재실행해서 재설정된 인스턴스들을 봅시다. 만약 레플리카의 규모나 태스크를 늘렸다면, 이런 이유로 컨테이너가 더 구동될 것입니다.
애플리케이션과 스웜 내리기
docker stack rm
을 이용해서 애플리케이션 내리기
$ docker stack rm getstartedlab
- 스웜 내리기
$ docker swarm leave --force
내리는 것은 애플리케이션 규모 조절과 올리는 것만큼 쉽습니다. 이제까지 운영환경에서 컨테이너를 실행하는 방법에 대해 학습했습니다. 앞으로 도커의 클러스터 위에서 보나파르트 떼(?)로써 애플리케이션을 실행하는 방법에 대해 배울 것입니다.
Note: 이와 같은 컴포즈 파일들은 Docker 애플리케이션을 정의하는 데 사용되고 Docker Cloud를 사용해서 클라우드 프로바이더, 하드웨어 어디든, Docker Enterprise Edition과 함께 클라우드 프로바이더에게 업로드될 수 있습니다.
정리 및 cheat sheet
docker run
을 타이핑하는 것은 간단하지만. 운영환경에서 컨테이너를 실제로 적용하는 것은 서비스로서 구동하는 것이다. 서비스는 컨테이너의 행동을 컴포즈 파일에 따라 편성하고, 이 파일은 애플리케이션을 재배포하거나, 규모를 조정하거나, 제한하는 데 사용됩니다. 서비스를 변화시키는 것은 docker stack deploy
를 실행하는 것과 같이 서비스를 구동하는 명령을 사용해서 응용됩니다.
docker stack ls # 애플리케이션이나 스택의 리스트 조회
docker stack deploy -c <composefile> <appname> # 특정 컴포즈 파일 실행
docker service ls # 애플리케이션과 연관된, 실행 중인 서비스 목록 조회
docker service ps <service> # 애플리케이션과 연관된, 태스크 목록 조회
docker inspect <task or container> # 컨테이너나 태스크 검사
docker container ls -q # 컨테이너의 ID 목록 조회
docker stack rm <appname> # 애플리케이션 내리기
docker swarm leave --force # 단일 노드 스웜 내리기
시작하기 - Part 4: 스웜
전제조건
- Docker 1.13 이상 설치
- Part 3 전제조건에 기술된 Docker Compose 준비
- 도커 머신을 준비합니다. Mac & Windows에선 미리 설치되지만, 리눅스에선 직접 설치해야 합니다. Windows 10 에선 Docker Toolbox를 사용해야 합니다.
- Part 1을 읽고 Part 2에서 컨테이너 생성 방법에 대해서 학습합니다.
waltz
이미지를 생성해서 레지스트리에 발행하는 것까지 해봐야 합니다. 이 장에서 공유한 이미지를 사용할 예정입니다.- 그 이미지가 배포된 컨테이너로서 작동하는지 확인합니다.
docker run -p 4000:80 사용자명/저장소:태그
명령을 실행하면,http://localhost:4000/
으로 접속되는 것을 확인해야 합니다. - Part 3 에서의
docker-compose.yml
파일의 복사본을 준비합니다.
소개
Part 3에서, Part 2에서 작성한 애플리케이션을 얻고 어떻게 운영 환경에서 실행할지에 대해 서비스에 넣고, 규모를 다섯 배로 늘리는 방법에 대해 정의했습니다. Part 4에선, 애플리케이션을 여러 대의 장비에서 실행하는 클러스터 위에 배포할 겁니다. 멀티 컨테이너, 멀티 머신 애플리케이션은 스웜이라 불리는 "Dockerized" 클러스터 안으로 여러 대의 장비를 연결하는 것을 가능케 합니다.
스웜 클러스터 이해하기
스웜은 도커를 실행하고 클러스터에 연결하는 장비들의 모음입니다. 여태까지 Docker 명령으로 실행했다면, 이젠 스웜 매니저를 통해서 클러스터를 실행합니다. 스웜의 장비들은 물리적 혹은 가상의 장비가 될 수 있습니다. 일단 스웜에 연결되면, 장비들은 노드로 불립니다.
스웜 매니저는 "emptiest node" - 최소의 자원을 이용 중인 장비들을 컨테이너로 채우는 방법 - 이나, "global" - 각 장비가 특정 컨테이너의 정확히 하나의 인스턴스만을 갖는 방법 등의 전략을 사용하여 컨테이너를 운용합니다. 컴포즈 파일에 이런 전략들을 사용하는 지시를 작성하고 스웜 매니저를 통해 관리합니다.
스웜 매니저는 워커라 불리는 다른 장비들에 명령을 하거나, 인가하는, 스웜 내의 유일한 장비입니다. 워커는 작업 용량을 제공하기 위해 존재하고, 다른 장비들이 어떤 일을 해야하는지 명령할 권한이 없습니다.
지금까지, 로컬 장비에서 단일 호스트 모드로서 도커를 사용했습니다. 하지만 도커는 스웜 모드로 동작할 수 있습니다. 이는 스웜의 사용을 활성화시켜서 현재 장비를 즉시 스웜 매니저로 동작하도록 합니다. 그 때부터, 도커는 현재 장비 대신 관리하는 스웜 내에서 지시한 명령을 실행합니다.
스웜 설치하기
스웜은 물리 장비 혹은 가상 장비로 만들 수 있는 다중 노드로 구성됩니다. 기본 컨셉은 간단합니다. docker swarm init
명령을 실행해서 스웜모드를 활성화하고 현재 장비를 스웜 매니저로 만듭니다. 그리고 다른 장비에서 docker swarm join
명령을 실행해서 워커 역할로 스웜에 연결합니다. 아래 보이는 탭을 선택해서 여러 환경에서 동작하는 방법을 살펴봅시다. VM을 사용해서 빠르게 두 장비를 생성하고 이를 스웜으로 바꿉니다.
클러스터 생성하기
Mac, Linux, Windows 7 & 8 환경에서 VM 설치하기
VM을 생성하기 위한 하이퍼바이저가 필요합니다. 그래서 로컬 운영체제를 위한 Oracle VirtualBox를 설치합니다.
Note: 만약 윈도우즈 시스템에 Hyper-V가 설치되었다면, VirtualBox를 설치할 필요가 없고 Hyper-V를 이용하면 됩니다. 만약 Docker Toolbox를 사용하고 있다면, 이미 일부로서 설치되어 있으므로 그걸 사용하면 됩니다.
이제 docker-machine
을 사용하여 두 대의 VM을 만들어봅시다.
$ docker-machine create --driver virtualbox myvm1
$ docker-machine create --driver virtualbox myvm2
VM 목록을 조회하고 IP 주소 얻기
이제 myvm1
과 myvm2
라고 명명된 두 가상장비를 얻었습니다.
아래 명령을 사용해서 장비의 목록을 조회하고 IP 주소를 확인해봅시다.
$ docker-machine ls
그리고 이 명령의 결과를 확인합시다.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Running tcp://192.168.99.100:2376 v18.09.0
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.0
스웜 초기화하고 노드 추가하기
첫 번째 장비를 명령을 관리하고 워커들을 승인하는 매니저로 사용하고, 두 번째 장비는 워커로 사용합니다.
docker-machine ssh
명령을 사용하여 VM들엑 명령을 전송합니다. myvm1
장비를 스웜 매니저로 만들기 위해 docker swarm init
명령을 지시하고 결과값을 확인합니다.
$ docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.99.100"
Swarm initialized: current node (q4hro97lh4c6ry3wdnp9grmwp) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-68gk029aiohwlwkf3zgxfoj0f77x0jfof8wh83bkfmttyhl9ls-9jt74cqp9dzu6neow1pb47npj 192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
포트 2377과 2376
항상 2377 포트(스웜 관리 포트)로
docker swarm init
명령과docker swarm join
명령을 실행하거나 아무 포트번호 없이 실행하면 기본적으로 2377 포트를 점유합니다.
docker-machine ls
명령으로 장비 IP는 2376 포트를 포함해서 반환됩니다. 이는 도커 대몬 포트입니다. 이 포트를 사용하지 마세요. 그렇지 않으면 에러를 확인할 수 있습니다.
SSH를 사용하는데 문제가 있나요? -native-ssh 플래그를 사용해보세요.
도커 머신은 OS의 SSH를 사용할 수 있게하는 옵션이 있습니다. 어떤 이유로 스웜매니저에 명령을 전송하는데 문제가 있다면,
--native-ssh
플래그를 사용해서 ssh를 사용해보세요.docker-machine --native-ssh ssh myvm1 ...
보이는 바와 같이 docker swarm init
명령의 응답은 미리 설정된 docker swarm joing
명령을 포함합니다. 이 명령은 어떤 노트든 추가해서 실행할 수 있습니다. 이 명령을 복사해서 docker-machine ssh
명령을 통해 myvm2
에 전송합니다. 이제 myvm2
는 워커로서 스웜에 연결되었습니다.
$ docker-machine ssh myvm2 "docker swarm join \
dquote> --token SWMTKN-1-68gk029aiohwlwkf3zgxfoj0f77x0jfof8wh83bkfmttyhl9ls-9jt74cqp9dzu6neow1pb47npj \
dquote> 192.168.99.100:2377"
This node joined a swarm as a worker.
축하합니다! 이제 첫 번째 스웜을 만들었습니다!
매니저에서 docker node ls
명령으로 스웜의 노드듣을 확인해봅시다.
$docker-machine ssh myvm1 "docker node ls"
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
q4hro97lh4c6ry3wdnp9grmwp * myvm1 Ready Active Leader 18.09.0
ve4jyfs9zzq5ncinylszk1qvc myvm2 Ready Active 18.09.0
스웜 떠나기
만약 다시 시작하고 싶으면,
docker swarm leave
를 각 노드에서 실행하면 됩니다.
myvm2에서 명령을 실행하면, 아래와 같이 STATUS
가 Down
으로 바뀐다.
$ docker-machine ssh myvm2 "docker swarm leave"
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
q4hro97lh4c6ry3wdnp9grmwp * myvm1 Ready Active Leader 18.09.0
ve4jyfs9zzq5ncinylszk1qvc myvm2 Down Active 18.09.0
스웜 클러스터에 애플리케이션 배포하기
어려운 부분은 지나갔습니다. 이제 애플리케이션을 스웜에 배포하기 위해서 Part 3에서 했던 과정을 반복하면 됩니다. myvm1
처럼 하나의 스웜 매니저가 Docker 명령을 실행하는 것만 기억하세요. 워커는 작업의 수용력을 위한 것들입니다.
스웜 매니저의 docker-machine
셸 설정하기
지금까지 Docker 명령을 docker-machine ssh
으로 래핑해서 VM과 통신했습니다. 다른 방법으로 docker-machine env <장비명>
을 실행해서 VM의 도커 대몬과 통신하기 위해 현재 셸을 설정할 수 있습니다. 이는 다음 단계를 위한 더 좋은 방법입니다. 이는 로컬환경의 docker-compose.yml
파일을 사용해서 어떤 복사본 없이 어디에든지 "원격"으로 애플리케이션을 배포할 수 있게 허용하기 때문입니다.
docker-machine env myvm1
을 타이핑하고 명령을 수행한 수, 콘솔 결과를 확인하세요. 그리고 마지막 줄의 myvm1
과 통신하기 위한 셸 설정 명령을 실행해보세요.
Mac 혹은 Linux의 Docker 장비 셸 환경 설정
docker-machine env myvm1
명령을 실행해서 myvm1
과 통신하기 위한 셸 설정 명령을 확인하세요.
$ docker-machine env myvm1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/sshplendid/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell:
# eval $(docker-machine env myvm1)
myvm1
과 통신하기 위한 셸 설정 명령을 실행하세요.
$ eval $(docker-machine env myvm1)
docker-machine ls
를 실행해서 myvm1
이 *
로 표시된, 활성화된 장비임을 검증하세요.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 * virtualbox Running tcp://192.168.99.100:2376 v18.09.0
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.0
스웜 매니저 상에서 애플리케이션 배포하기
이제 myvm1
이 있으니, Part 3에서 사용했던 docker stack deploy
명령과 로컬 파일 docker-compose.yml
으로 myvm1
에 애플리케이션을 배포해야 합니다. 이 명령은 완료될 때 까지 몇 초가 걸리고 배포된 애플리케이션은 얼마가 지나야 가용상태가 됩니다. docker service ps <서비스 이름>
명령으로 모든 재배포된 서비스를 검증하세요.
docker-machine
셸 설정으로 myvm1
에 연결되었습니다. 그리고 여전히 로컬 호스트의 파일에 접근할 수 있습니다. 이전에 docker-compose.yml
파일을 작성했던 디렉토리 위치를 확인하세요. 그리고 그 위치에서 myvm1
상에 애플리케이션 배포를 위한 다음 명령을 실행하세요.
$ docker stack deploy -c docker-compose.yml getstartedlab
이제 다 됐습니다. 애플리케이션이 스웜 클러스터에 배포되었습니다!
Note: 만약 이미지가 Docker Hub 대신 개인 저장소에 저장되어 있다면,
docker login <레지스트리 명>
으로 로그인한 다음--with-registry-auth
플래그를 추가합니다. 아래처럼 하면 됩니다.docker login registry.example.com docker stack deploy --with-registry-auth -c docker-compose.yml getstartedlab
이는 암호화된 WAL 로그를 사용해서 로컬 클라이언트로부터 서비스가 배포된 스웜 노드까지 통과하게 합니다. 이 정보로, 노드는 레지스트리에 접속 가능해지고 이미지를 받아올 수 있습니다.
이제 Part 3에서 사용된 도커 명령을 사용해야 합니다. 오직 이번만 myvm1
과 myvm2
사이에 분산된 서비스를 알아차릴 것입니다.
$ docker stack ps getstartedlab
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
219e9nvteidh getstartedlab_web.1 sshplendid/get-started:part2 myvm1 Running Running 6 hours ago
uczmecew5lcc getstartedlab_web.2 sshplendid/get-started:part2 myvm3 Running Running 6 hours ago
uoc8ulrnurdk getstartedlab_web.3 sshplendid/get-started:part2 myvm2 Running Running 6 hours ago
zp1553lrlph1 getstartedlab_web.4 sshplendid/get-started:part2 myvm3 Running Running 6 hours ago
tzys637kf3hv getstartedlab_web.5 sshplendid/get-started:part2 myvm2 Running Running 6 hours ago
docker-machine env
와docker-machine ssh
로 VM에 연결하기
myvm2
처럼 다른 장비와 통신하기 위해 셸을 설정하려면, 간단하기docker-machine env
명령을 셸에서 재실행하고 주어진 명령을 실행하면 됩니다. 이건 항상 현재 셸에서만 한정되는 명령입니다. 만약 설정되지 안흥ㄴ 셸로 변경하거나 새로운 셸을 열면, 이 명령을 재실행해야 합니다.docker-machine ls
명령을 사용해서 장비들을 조회하고, 각 장비가 어떤 상태인지 확인하고, IP 주소를 확인한 후, 어떤 장비에 연결되어 있는지 확인하세요. 더 알고 싶다면, Docker Machine 시작하기 문서를 살펴보세요.- 대안으로, Docker 명령을 아래와 같은 형식으로 감싸서 사용할 수 있습니다. 이는 VM에 직접적으로 기록되지만, 로컬 호스트의 파일에 직접 접근하지 못합니다.
- Mac이나 Linux에서,
docker-machine scp <파일 명> <장비 명>:~
명령을 사용해서 장비 간 파일 복사가 가능합니다. 그러나 윈도우즈 사용자는 Git Bash 같은 리눅스 터미널 에뮬레이터가 필요합니다.모든 플랫폼에서
docker-machine
CLI를 사용할 수 있기 때문에,docker-machine ssh
와docker-machine env
튜토리얼 데모가 가능합니다.
클러스터에 접근하기
이제 myvm1
혹은 myvm2
의 IP 주소로 애플리케이션에 접근할 수 있습니다.
생성된 네트워크는 그 장비들과 로드-밸런싱 사이에서 공유됩니다. docker-machine ls
명령을 실행해서 VM 장비들의 IP주소를 확인하고 각 장비에 브라우져를 통해 접속해보세요. (간단하게 curl
을 사용할 수도 있습니다.)
여기, 로드-밸런싱 중인, 무작위로 순환하는 다섯 컨테이너가 있습니다.
두 IP 주소 모두 작동하는 이유는 스웜에 참가하는 노드가 **라우팅 메시(routing mesh)**에 진입했기 때문입니다. 이는 스웜 내 특정 포트에 배포된 서비스들이 항상 서비스 자신이 예약한 포트를 소유하고 있음을, 노드가 실제로 컨테이너를 구동 중이더라도, 반드시 보장하게 합니다. 여기 세 노드를 가진 스웜의 8080
포트에 배포된, my-web
이라고 불리는 서비스를 위한 라우팅 메시가 어떻게 작동하는지에 대한 다이어그램을 확인해보세요.
연결에 문제가 있나요?
스웜 내에서 진입 네트워크를 사용하는 것은 스웜모드를 활성화하기 전에 스웜 노드 사이에 아래 포트를 개방해야 하는 것을 명심하세요.
- 컨테이너 네트워크 탐색을 위한 7946 TCP/UDP 포트
- 컨테이너 진입 네트워크를 위한 4789 UDP 포트
애플리케이션 규모 조절하기 및 반복하기
여기부턴 Part 2와 Part 3에선 배운 모든 작업을 반복합니다.
docker-compose.yml
파일을 변경함으로서 애플리케이션의 규모를 조절합니다.
코드를 수정하고, 다시 빌드하고, 새로운 이미지를 생성함으로써, 애플리케이션의 행동을 변경합니다. (이를 위해, 이전에 배운 애플리케이션 빌드하기와 이미지 생성하기 단계를 반복합니다).
어느 쪽이던, docker stack deploy
명령으로 간단하게 변경사항을 배포할 수 있습니다.
어떤 물리적/가상화된 장비이건, docker swarm join
명령으로, 스웜에 연결할 수 있습니다. myvm2
에 사용했던 것처럼요. 그럼 클러스터의 수용력이 늘어날 것입니다. 간단하게 docker stack deploy
명령을 그 뒤에 실행하면, 애플리케이션은 새로운 자원을 활용할 수 있을 것입니다.
클린업과 리부트
스택과 스웜
docker stack rm
명령으로 스택을 내릴 수 있습니다.
docker stack rm getstartedlab
스웜을 유지하거나 삭제하기
만약 스웜을 제거하고 싶다면 매니저와 워커 각각 아래 명령으로 제거할 수 있습니다.
- 워커:
docker-machine ssh myvm2 "docker swarm leave"
- 매니저:
docker-machine ssh myvm1 "docker swarm leave --force"
하지만 Part 5에서 스웜이 필요하기 때문에, 당분간 유지해야 합니다.
docker-machine 셸 변수 설정 해제하기
아래 명령으로 docker-machine
환경 변수를 해제할 수 있습니다.
Mac과 Linux 환경
eval $(docker-machine env -u)
Windows 환경
& "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env -u | Invoke-Expression
이 명령은 docker-machine
으로 생성된 가상 장비로부터 셸 접속을 끊고, 동일한 셸에서 네이티브 docker
명령을 사용할 수 있게 합니다. 더 알고 싶다면, 환경 변수 해제하기에 대한 Machine 토픽에 대해서 알아보세요.
Docker machine 재시작하기
만약 로컬 호스트 장비를 정지시켰다면, 도커 머신 역시 정지합니다. docker-machine ls
명령으로 각 장비의 상태를 확인할 수 있습니다.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Stopped Unknown
myvm2 - virtualbox Stopped Unknown
myvm3 - virtualbox Stopped Unknown
재시작하기 위해, 아래 명령을 실행합니다.
docker-machine start <장비 명>
모든 장비를 재시작하기 위해서, 아래 명령을 실행하면 됩니다.
$ docker-machine start $(docker-machine ls -q) # 모든 장비 시작하기
Starting "myvm3"...
Starting "myvm1"...
Starting "myvm2"...
(myvm3) Check network to re-create if needed...
(myvm3) Waiting for an IP...
Machine "myvm3" was started.
Waiting for SSH to be available...
(myvm1) Check network to re-create if needed...
(myvm1) Waiting for an IP...
(myvm2) Check network to re-create if needed...
(myvm2) Waiting for an IP...
Machine "myvm1" was started.
Waiting for SSH to be available...
Machine "myvm2" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Detecting the provisioner...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.
Docker note
Ubuntu Bash 셸 실행
- run 옵션
- -it: 터미널 입력을 위한 실행 옵션
- --rm: 컨테이너 종료시 컨테이너 자동 제거
docker run -it --rm ubuntu bash
로컬 드라이브 볼륨 마운트
도커 실행(docker run
)할 때, -v 로컬호스트디렉토리:컨테이너디렉토리
로 마운트
볼륨 마운트 테스트
# 로컬 볼륨 마운트 테스트
> mkdir docker-vol # 로컬 디렉토리 생성
$ echo "테스트 파일" > test.txt # 로컬 파일 생성
$ docker run -it -v c:/Users/Administrator/docker-vol:/vol-test ubuntu bash
$ cd vol-test
$ cat test.txt
# 파일내용이 나오면 정상적으로 마운트된것
# 컨테이너 -> 로컬 파일 테스트
$ echo 'This file is created from container.' > from-container.txt
$ exit # 컨테이너 종료
> type from-container.txt # cat과 동일한 Windows 명령
개발환경 세팅
MariaDB 연결
$ docker run --name maria -v c:/dev/docker/vol-maria:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=admin -d mariadb
언어를 배우며
Java
Comparable VS Comparator
Comparable
Comparable
인터페이스는 상속받는 클래스가 생성한 객체 사이의 자연스러운 정렬 이 가능하도록 도입되었다. 인터페이스를 상속받은 클래스는 compareTo
메서드를 구현해야 하며 이는 객체 사이의 비교를 담당하며 Arrays.sort
나 Collections.sort
같은 정렬 API에서 사용한다.
아래와 같은 클래스에서 인터페이스를 상속받고 메서드를 구현했다. 이름의 오름차순으로 구현했으며, 이는 다음 테스트 코드와 같이 동작한다.
// Comparable 구현
class Fruit implements Comparable<Fruit> {
private String name;
private int price;
Fruit(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public int compareTo(Fruit o) {
Fruit f = (Fruit) o;
return this.name.compareTo(f.name);
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
}
// Comparable의 동작
public class CompareTest {
@Test
public void ObjectComparableTest() {
Fruit[] f = new Fruit[4];
f[0] = new Fruit("Orange", 10);
f[1] = new Fruit("Mango", 70);
f[2] = new Fruit("Melon", 50);
f[3] = new Fruit("Apple", 20);
String[] alphabet = {"Apple", "Mango", "Melon", "Orange"};
/**
* Fruit 클래스는 기본적으로 이름을 기준으로 비교한다.
*/
Arrays.sort(f);
for(int i = 0; i < 4; i++)
assertTrue(f[i].getName().equals(alphabet[i]));
}
}
Comparator
하지만 어떤 동작에선 이름이 아니라 가격으로 비교해야 할 경우가 있다고 가정하자. 그리고 이름으로 정렬하는 기능도 동시에 사용해야 한다. 이럴 땐 Comparator
를 사용해서 상황별 정렬 기준 변경이 가능하다.
public class CompareTest {
@Test
public void ObjectComparatorTest() {
Fruit[] f = new Fruit[4];
f[0] = new Fruit("Orange", 10);
f[1] = new Fruit("Mango", 70);
f[2] = new Fruit("Melon", 50);
f[3] = new Fruit("Apple", 20);
String[] cheap = {"Orange", "Apple", "Melon", "Mango"};
/**
* 이름 대신 가격으로 비교하고 싶다
* 그럼 비교할 때마다 비교로직을 변경해야 하는가?
* 이름과 가격 둘 다 비교해야 하는 경우는 어떻게 해야하나?
*/
Arrays.sort(f, new Comparator<Fruit>() {
@Override
public int compare(Fruit o1, Fruit o2) {
// 오름차순
return o1.getPrice() - o2.getPrice();
}
});
for(int i = 0; i < 4; i++)
assertTrue(f[i].getName().equals(cheap[i]));
}
@Test
public void ObjectComparatorDescendingTest() {
Fruit[] f = new Fruit[4];
f[0] = new Fruit("Orange", 10);
f[1] = new Fruit("Mango", 70);
f[2] = new Fruit("Melon", 50);
f[3] = new Fruit("Apple", 20);
String[] expensive = {"Mango", "Melon", "Apple", "Orange"};
Arrays.sort(f, new Comparator<Fruit>() {
@Override
public int compare(Fruit o1, Fruit o2) {
return o2.getPrice() - o1.getPrice();
}
});
for(int i = 0; i < 4; i++)
assertTrue(f[i].getName().equals(expensive[i]));
}
}
위 테스트 코드는 이미 Comparable
로 구현된 Fruit 객체를 정렬해야하는 상황 별로 price 오름차순, 내림차순으로 정렬하고 있다. 이는 클래스의 비교로직에 영향을 끼치지 않으며 익명 객체가 사용된 코드에만 영향을 미친다.
Log
와 Logger
차이, 그리고 다른 로깅 프레임워크
결론적으로 사용하는 API의 차이
Commons Logging
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
class Foo {
private static final Logger LOG = Logger.getLogger(Foo.class);
}
Log4J
import org.apache.log4j.Logger;
class Foo {
private static final Log LOGGER = LogFactory.getLog(Foo.class);
}
Slf4J
Log4J
를 만든 Ceki Gülcü가 만든 프레임워크.- Logging Facade로 LOGback, LOG4J, Commons Logging 등 구현체 프레임워크와 상관 없이 일관된 코드 작성 가능
- 참고: logback 사용해야 하는 이유 (Reasons to prefer logback over log4j)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class Foo {
private static final Logger LOGGER = LoggerFactory.getLogger(Foo.class);
}
LOGback
- LOG4J의 아키텍쳐 기반
- 메모리 점유율 및 속도 등 비약적인 성능 개선
IBM - Java Dump 분석
아래 IBM 문서를 보고 Javadump를 살펴보았다. IBM - Javadump 해석 Naver D2 - 스레드 덤프 분석하기
덤프의 종류
Heap Dump, Java Dump, Thread Dump, java core 등의 파일은 Java 어플리케이션의 비정상적인 상황(OOM, Hang, ...)에서 생성되는 파일이다. 이 파일들은 장애 발생 당시의 JVM 정보를 가지고 있다.
- Javadump: Java core 혹은 Thread dump라고 불린다. OOM같은 상황에서 JVM이 의도치않게 종료된 경우 예약된 키 조합(ex. Windows:
Ctrl + Break
, Unix:kill -3 <pid>
)으로 생성된다. 혹은 어플리케이션 내부에서com.ibm.jvm.Dump.JavaDump
API를 사용해서 생성할 수도 있다. - Heapdump: 예기치않게 JVM이 종료된 경우 사용자의 요청 혹은
Xdump:heap
옵션을 사용해서 힙 덤프 생성시점을 제어할 수 있다.
덤프가 생성되지 않을 때
일부 상황에선 덤프가 생성되지 않을 수도 있다. 이땐 시스템 덤프를 확인해보자. 자세한 내용은 아래 문서 참고
덤프가 생성되지 않는 경우의 시나리오
그래서 javadump는?
Javadump는 JVM / Java 어플리케이션 실행 중의 특정 시점에서 캡쳐된 진단정보가 포함된 파일이다. 이 파일엔 운영 체제, 어플리케이션 환경, 스레드, 스택, 메모리 등의 정보가 포함된다. 파일은 텍스트 형식으로 사용자가 읽을 수 있으며 Java Object 컨텐츠나 데이터는 포함되지 않는다.
- 스레드 ID와 플래그를 포함한 스레드 이름
- 개수와 플래그를 포함하는 클래스 로더 이름
- 클래스 및 메소드 이름
- 일부 Heap 주소
어플리케이션을 시작할 때 -Xdump:java:
플래그를 사용해서 파일 생성을 제어할 수 있다. 파일 이름은 javacore.<date>.<time>.<pid>.<sequence number>.txt
로 네이밍된다. Javacore는 시스템 덤프에서 생성하는 코어 파일 과는 다르다.
덤프파일 생성방법
- Java 옵션 사용:
XDump:java
옵션을 사용해서 덤프 생성을 제어할 수 있다.
오류 조건에 의해 생성된 JavaDump
아래 조건과 일치하는 상황이 발생하면 Javadump는 자동으로 생성된다.
- 복구할 수 없는 기본 예외: JVM이 중지되는 예외상황이다. JVM은 시스템 덤프를 생성하고 스냅 추적파일 및 Javadump를 생성한 후에 프로세스를 종료한다. Java 예외상황이 아니다.
- JVM 메모리 부족:
OOME
같은 메모리 부족 상황에서 자동 생성된다.
요청에 의한 생성
사용자의 요청 혹은 API 호출로 덤프가 생성되는 경우이다.
- Command Line에서 JVM에 신호를 송신:
kill -3 <pid>
를 사용해서 Java 프로세스에 덤프 생성 신호를 보낼 수 있다. 이 경우 JVM은 덤프파일을 생성하고 계속 실행된다. com.ibm.jvm.Dump.JavaDump
API 사용: 이 API를 호출하면 덤프파일이 생성된다. 동일하게 JVM은 계속 실행된다.- WAS Utility 에 의한 생성: Weblogic과 같은 Middleware 환경에서 미들웨어 유틸리티를 사용해서 덤프를 생성 가능하다.
덤프 해석
Javadump 태그
Dump 파일은 아래와 같이 섹션(SECTION
)으로 구분되어 있다.
0SECTION TITLE subcomponent dump routine
NULL ===============================
1TISIGINFO Dump Event "systhrow" (00040000) Detail "java/lang/OutOfMemoryError" received
1TIDATETIME Date: 2017/10/26 at 14:39:07
1TIFILENAME Javacore filename: /USER/javacore.20171026.143904.33947784.0001.txt
1TIREQFLAGS Request Flags: 0x81 (exclusive+preempt)
1TIPREPSTATE Prep State: 0x104 (exclusive_vm_access+)
NULL ------------------------------------------------------------------------
일반적인 태그는 아래와 같은 규칙을 가지고 있다.
- 태그는 최대 15자이다.(빈 부분은 공백으로 채움)
- 첫 번째 숫자는 태그 레벨을 나타낸다. 이 숫자는 항상 순서대로 오더링되진 않는다.(2 뒤에 4, 혹은 3 뒤에 1이 나올 수 있다.)
- 태그의 두 번째와 세 번째 문자는 덤프 섹션을 식별한다. 아래는 주요 섹션을 나타내고 그 외에도 다른 섹션이 존재한다.
- CI: 명령행 인터프리터
- CL: 클래스 로더
- LK: LOCK
- ST: 스토리지(메모리 관리)
- TI: 제목
- XE: 실행 엔진
- 나머지는 고유 문자열이다.
- 모든 섹션은
0SECTION
태그로 헤드가 지정된다. NULL
태그는 가독성을 위한 태그이다. 정보를 구분하는 용도로 사용한다.
TITLE, GPINFO, ENVINFO 섹션
TITLE
Javadump 파일이 생성된 이벤트 및 기본정보를 나타낸다. 위의 로그가 TITLE 부분이고 OutOfMemoryErorr
에 의한 이벤트 발생임을 알 수 있다.
GPFINO
GPF로 인해 dump가 생성되었는지 여부에 따라 컨텐츠가 달라지지만 운영체제에 대한 정보가 일부 표시된다. GPF로 인해 생성된 경우 GPF정보가 제공된다.
0SECTION GPINFO subcomponent dump routine
NULL ================================
2XHOSLEVEL OS Level : AIX 6.1
2XHCPUS Processors -
3XHCPUARCH Architecture : ppc64
3XHNUMCPUS How Many : 8
3XHNUMASUP NUMA is either not supported or has been disabled by user
NULL
1XHERROR2 Register dump section only produced for SIGSEGV, SIGILL or SIGFPE.
NULL
NULL ------------------------------------------------------------------------
ENVINFO
실패한 JRE 레벨에 대한 정보와 JVM 프로세스 및 환경을 호출한 명령에 대한 세부사항을 나타낸다.
아래 로그를 보면 AIX6.1, JRE 1.6 환경에서 어플리케이션을 실행했고 실행할 때의 자바 옵션, 클래스패스도 확인할 수 있다.
0SECTION ENVINFO subcomponent dump routine
NULL =================================
1CIJAVAVERSION JRE 1.6.0 AIX ppc64-64 build jvmap6460sr10fp1-20120202_101568 (pap6460sr10fp1-20120321_01(SR10 FP1))
1CIVMVERSION VM build 20120202_101568
1CIJITVERSION r9_20111107_21307ifx1
1CIGCVERSION GC - 20120202_AA
1CIJITMODES JIT enabled, AOT enabled, FSD disabled, HCR disabled
1CIRUNNINGAS Running as a standalone JVM
1CICMDLINE /usr/java6_64/bin/java -DDOMAIN_NAME= -DSERVER_NAME=server1 -Xms2048m -Xmx2048m -Xgcpolicy:gencon -verbosegc -Dweblogic.Name=server1 -Djava.security.policy=/wlserver_10.3/server/lib/weblogic.policy -Dweblogic.ProductionModeEnabled=true -Dweblogic.security.SSL.trustedCAKeyStore=/weblogic/wlserver_10.3/server/lib/cacerts -da -Dplatform.home=/weblogic/wlserver_10.3 -Dwls.home=/weblogic/wlserver_10.3/server -Dweblogic.home=/weblogic/wlserver_10.3/server -Dweblogic.management.discover=false -Dweblogic.management.server=t3://12.30.51.107:6060 -Dwlw.iterativeDev=false -Dwlw.testConsole=false -Dwlw.logErrorsToConsole=false -Dweblogic.ext.dirs=/weblogic/patch_wls1036/profiles/default/sysext_manifest_classpath weblogic.Server
1CIJAVAHOMEDIR Java Home Dir: /usr/java6_64/jre
1CIJAVADLLDIR Java DLL Dir: /usr/java6_64/jre/bin
2CIUSERARG -DDOMAIN_NAME=
2CIUSERARG -DSERVER_NAME=server1
2CIUSERARG -Xms2048m
2CIUSERARG -Xmx2048m
....
스토리지 관리(MEMINFO)
MEMINFO 섹션은 Memory에 대한 정보를 제공한다. 제일 먼저 아래 정보를 보면 당시 메모리 사용 현황에 대해서 나온다. 4GB 할당된 상태에서 사용가능한 메모리는 약 44MB 정도였다.
0SECTION MEMINFO subcomponent dump routine
NULL =================================
1STHEAPFREE Bytes of Heap Space Free: 2CA0EF0
1STHEAPALLOC Bytes of Heap Space Allocated: 80000000
아래 덤프는 내부 메모리 섹션(SEGTYPE)이다. 클래스 메모리, JIT 코드 캐시 및 JIT 데이터 캐시등을 포함한다.
- segment: 세그먼트 제어 데이터 구조의 주소
- start: 세그먼트의 시작 주소
- alloc: 현재 할당 주소
- end: 세그먼트의 끝 주소
- type: 세그먼트 특성을 설명하는 내부 비트 필드
- size: 세그먼트의 크기, 아래엔
bytes
로 표시
1STSEGTYPE Internal Memory
NULL segment start alloc end type bytes
1STSEGMENT 00000100207A0C18 000001002C74D948 000001002C74D948 000001002C75D948 01000040 10000
...
1STSEGTYPE Object Memory
NULL segment start alloc end type bytes
1STSEGMENT 0000010012A345F8 0700000000000000 0700000060000000 0700000060000000 00000009 60000000
NULL
1STSEGTYPE Class Memory
NULL segment start alloc end type bytes
1STSEGMENT 00000100153C9A78 0000010016306FD0 00000100163072C0 00000100163072C0 00010040 300
...
NULL
1STSEGTYPE JIT Code Cache
NULL segment start alloc end type bytes
1STSEGMENT 00000100139CBF98 000001002ACC3E48 000001002B4C3E48 000001002B4C3E48 00000068 800000
1STSEGMENT 00000100139CBED8 0000010013A143E8 00000100142143E8 00000100142143E8 00000068 800000
NULL
1STSEGTYPE JIT Data Cache
NULL segment start alloc end type bytes
1STSEGMENT 00000100139CC198 000001001424BF08 000001001486A1C0 0000010014A4BF08 00000048 800000
NULL
1STGCHTYPE GC History
3STHSTTYPE 05:39:04:720827501 GMT j9mm.100 - J9AllocateObject() returning NULL! 48 bytes requested for object of class 0000010014C5A010 from memory space 'Generational' id=0000010012A357D8
3STHSTTYPE 05:39:04:719925874 GMT j9mm.101 - J9AllocateIndexableObject() returning NULL! 232 bytes requested for object of class 0000010014C599F0 from memory space 'Generational' id=0000010012A357D8
3STHSTTYPE 05:39:04:719924530 GMT j9mm.84 - Forcing J9AllocateIndexableObject() to fail due to excessive GC
3STHSTTYPE 05:39:04:719594780 GMT j9mm.101 - J9AllocateIndexableObject() returning NULL! 48 bytes requested for object of class 0000010018DCC370 from memory space 'Generational' id=0000010012A357D8
...
LOCKS
THREADS
SHARED CLASSES
CLASSES
JMeter
JMeter는 Apache 재단에서 관리하는 Java 기반의 오픈소스 소프트웨어이다. 퍼포먼스 측정과 기능 테스트 등을 위해 설계되었다. 원래 웹 애플리케이션을 테스트하기 위해 개발되었지만 기능이 점점 추가되었다.
JMeter로 할 수 있는 것
JMeter는 애플리케이션/서버/프로토콜의 타입에 따라 다양한 테스트를 할 수 있다.
Ability to load and performance test many different applications/server/protocol types:
- Web - HTTP, HTTPS (Java, NodeJS, PHP, ASP.NET, …)
- SOAP / REST Webservices
- FTP
- Database via JDBC
- LDAP
- Message-oriented middleware (MOM) via JMS
- Mail - SMTP(S), POP3(S) and IMAP(S)
- Native commands or shell scripts
- TCP
기본 사용법
HTTP TPS 부하테스트
JavaScript
this
메서드 호출시, this는 함수를 호출한 객체를 나타낸다.
Global Context
this
는 global 영역에서 전역객체를 참조한다.
console.log(this.document === document); // true
// 웹 브라우저의 전역객체는 window
console.log(this === window); // true
this.a = 10;
console.log(window.a); // 10
Function Context
함수 안에서의 this
는 함수 호출방법에 따라 달라진다.
단순 함수 호출
아래 경우 this
는 호출에 의해 설정되지 않는다. 그리고 strict mode
가 아니기 때문에 this는 항상 전역객체에서 기본이 되는 객채여야 한다.
function foo() {
return this;
}
console.log(foo() === window); // true, global object
console.log(window.foo() === window); // true, global object
strict mode 적용시, this
는 실행 컨텍스트에 들어갈때 할당된다. 만약 정의가 되지 않았다면 undefined
가 된다.
function bar() {
"use strict"; // strict mode
return this;
}
console.log(bar() === undefined); // true, bar를 단독으로 호출했다.
console.log(window.bar() === window); // true, window객체의 메서드로서 호출
객체의 메서드
위의 window.bar()처럼 객체의 메서드로 호출되었을 때, this는 메서드를 호출한 객체를 가리킨다.
var jane = {
age: 10,
growUp: function() {
// jane 객체 하위 메서드로, 함수 호출시 this는 jane을 가리킨다.
this.age += 1;
return this.age;
}
};
console.log(jane.growUp());
var john = {
age: 10,
};
function growUp () {
this.age += 1;
return this.age;
}
john.growUp = growUp;
console.log(john.growUp()); // 11
console.log(growUp()) // NaN, window객체에 age라는 속성이 없기때문에
this.age = 1; // window 객체에 age 속성(수치형)을 생성
console.log(growUp()) // 2
객체의 prototype
메서드가 객체의 prototype 체인에 있다면, 메서드는 호출된 객체를 나타낸다.
var o = {f:function() { return this.a + this.b; }};
var p = Object.create(o);
p.a = 1;
p.b = 2;
console.log(p.f()); // 3, p 객체의 프로퍼티 a(1) + b(2) 를 리턴
생성자
생성자를 통해 생성된 객체를 가리킨다.
var age = -1; // 전역변수
function Person() {
this.age = 0;
this.getAge = function() {
return this.age;
};
this.setAge = function(a) {
this.age = a;
}
}
var jane = new Person();
console.log('Jane\'s age: ' + jane.getAge()); // 0, 새롭게 생성된 jane의 age 초기값을 리턴
jane.setAge(10);
console.log('Jane\'s age: ' + jane.getAge()); // 10
console.log('global age: ' + age); // -1, 전역변수 age는 변동없음
Anonymous function
stackoverflow: this value in JavaScript anonymous function 을 읽고 작성했다.
아래 test 함수의 첫 번째 라인을 보자. A
의 this
는 test를 호출한 객체를 가리킨다. 마지막 라인의 new MyObject() 생성자를 통해 생성된 객체다.
반면에 B
는 전혀 다른 함수 범위이고, 이 함수를 호출한 객체는 없다. 그러므로 기본 값인 window
를 가리킨다.
function MyObject() { };
MyObject.prototype.test = function () {
console.log("A", this instanceof MyObject);
(function () {
console.log("B", this instanceof MyObject);
console.log("C", this === window);
}());
}
// A true
// B false
new MyObject().test();
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this
title: "class" category: [js] tags: [javascript, class, es6]
class
class는 ES6에서 소개된 문법으로 기존의 프로토타입 기반 상속에 비해 객체 생성과 상속을 다루기 훨씬 간결하고 쉬워졌다.
class Animal {
constructor() {
this.stand = false;
}
bark() {
console.log('으르렁!');
}
}
let lion = new Animal();
lion.bark();
console.assert(!lion.stand, '사자가 직립보행한다고?');
상속을 받는 경우, 생성자를 구현할 때 반드시 super()
메서드를 추가해야 한다.
class Dog extends Animal {
bark() {
console.log('멍멍');
}
}
let jindo = new Dog();
jindo.bark();
console.assert(!jindo.stand, '진돗개가 직립보행한다고?');
class Penguin extends Animal {
constructor() {
super();
this.stand = true;
}
bark() {
console.log('펭펭..?');
}
}
let gentoo = new Penguin();
gentoo.bark();
console.assert(gentoo.stand, '젠투펭귄이 기어간다고? 그럴지도...?');
Hoisting
var
로 선언된 변수는 코드가 실행되기 전에 선언 처리하기 때문에, 코드 안에서 최상위 단에 선언한 것과 동일하다.
// case A
a = 1; // 아직 선언되지 않았지만 바로 아래 선언되었기 때문에 B와 같이 동작한다.
var a;
// case B
var a;
a = 1;
IIFE(즉시 실행 함수)
즉시 실행 함수(IIFE, Immediately Invoked Function Expression)는 정의되자마자 즉시 실행되는 자바스크립트 함수를 말한다.
이는 Self-Execution Anonymous Function으로 알려진 디자인 패턴이고 크게 두 부분으로 구성된다. 첫 번째는 괄호(()
, Grouping Operator)로 둘러싸인 익명함수(Anonymous Function)이다. 이는 전역범위를 오염시키는 것 뿐만 아니라 IIFE 내부의 변수에 접근하는 것을 방지한다.
두 번째 부분은 즉시 실행 함수를 생성하는 괄호()
이다. 이를 통해 자바스크립트 엔진은 함수를 즉시 해석해서 실행한다.
예제
아래 함수는 즉시 실행되는 함수 표현이다. 표현 내부의 변수는 외부로부터의 접근이 불가능하다.
(function() {
var aName = 'Barry';
})();
// IIFE 내부에서 정의된 변수는 외부 범위에서 접근이 불가능하다.
aName // Uncaught ReferenceError: aName is not defined
IIFE를 변수에 할당하면 IIFE 자체는 저장되지 않고, 함수가 실행된 결과만 저장된다.
var result = (function () {
var name = 'Barry';
return name;
})();
// 즉시 결과를 생성한다.
console.log(result); // Barry;
Promise
callback hell을 벗어나기 위한 방법
Promise 객체는 비동기 처리환경에서 이벤트의 처리 완료(혹은 실패) 상태, 그리고 그 결과값을 표현한다. 이는 비동기 메서드가 동기 메서드처럼 결과값을 반환하게 한다.(즉시 결과값을 반환하는 대신에, 비동기 메서드는 미래 어느 시점에서의 결과값을 가진 promise객체로 반환한다.)
상태
- pending(미결): 초기 상태, 아직 fulfilled 혹은 rejected 상태가 아님
- fulfilled(처리): 동작이 성공적으로 완료됨
- rejected(실패): 동작이 실패함
pending 상태의 promise는 결과값을 가진 fulfilled 혹은 에러 이유가 포함된 rejected 가 될 수 있다. 어떤 상태로든 변경이 일어나면, promise then
API에 처리 대기상태에 들어간 핸들러가 호출된다.
사용방법
Promise 객체 생성하기
아래와 같이 Promise 객체를 만들어보자. 비동기 상황을 표현하기 위해 setTimeout
API를 사용했다. 랜덤확률에 의해 비동기 동작의 성공, 실패 상황을 분기했다.
// new
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
var probability = Math.random();
if (probability >= 0.5)
resolve('async operation success!');
else
reject(Error('async operation failed ;('));
}, 2000);
});
promise.then(console.log, console.error);
혹은 Promise객체를 리턴하는 함수로 제공할 수 있다.
// return
var promise2 = (param) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
var probability = Math.random();
if (probability >= 0.5)
resolve('2: async operation success!');
else
reject(Error('2: async operation failed ;('));
}, 2000);
});
};
promise2().then(console.log, console.error);
Promise Chainning
then
API는 다시 pending 상태의 promise 객체를 리턴한다. 이로인해 여러 개의 프로미스를 연결하여 사용할 수 있다.
// promise Chaining
var promise3 = (param) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// var probability = Math.random();
if (true)
resolve('3: async operation success!');
else
reject(Error('3: async operation failed ;('));
}, 1000);
});
};
promise3().then((result) => {
console.log('a: ' + result);
return 10;
}).then((result) => {
console.log('b: ' + result);
return result + 10;
}).then((result) => {
console.log('c: ' + result);
});
체이닝은 아래와 같이 응용가능하다.
// promise chaining2
var userInfo = '{"name":"John", "age":10}';
function parseData(data) {
return JSON.parse(data);
}
function auth(data) {
if(data.name === 'John') {
return data;
}
throw Error(data.name + ' is not an authorized user!');
}
function display(data) {
console.log(`Welcome, ${data.name}!`);
}
function getData() {
return new Promise((resolve) =>{ return resolve(userInfo); });
}
getData().then(parseData)
.then(auth)
.then(display)
.catch((err) => {console.error(err.message);});
// Welcome, John!
Error Handling
위 promise chaining2 예제에서 이름을 Jane을 바꾸면 auth 함수에서 에러가 발생한다. 체인에서 발생한 에러는 catch
API로 처리한다.
// Error Handling
userInfo = '{"name":"Jane", "age":10}';
getData().then(parseData)
.then(auth)
.then(display)
.catch((err) => {console.error(err.message);});
// Jane is not an authorized user!
Promise.all API
2 건 이상의 비동기 동작을 처리해야할 경우 all
API를 사용해보자.
아래 코드는 셔츠, 바지, 자켓을 입는 함수이다. 자켓은 셔츠를 입은 후에 착용해야 한다. 이는 셔츠와 바지를 입는 순서는 상관 없다는 의미이다.
// all API
var body = {jacket:false, shirt:false, pants:false};
function wearAShirt(body) {
return new Promise((resolve) => {
if(body.jacket)
throw Error('셔츠를 입고 자켓을 입어야지.');
body.shirt = true;
console.log('셔츠를 입었다.');
return resolve(body);
});
}
function wearAJacket(body) {
return new Promise((resolve) => {
if(body.shirt) {
console.log('자켓을 입었다.');
body.jacket = true;
return resolve(body);
} else {
throw Error('셔츠를 입고 자켓을 입어야지.');
}
});
}
function wearPants(body) {
return new Promise((resolve) => {
console.log('바지를 입었다.');
body.pants = true;
return resolve(body);
});
}
function howDoILook(body) {
body.shirt?console.log('셔츠를 입었네.'):null;
body.jacket?console.log('자켓을 입었네.'):null;
body.pants?console.log('바지를 입었네.'):null;
}
Promise.all([wearAShirt(body), wearPants(body)])
.then(values => {return wearAJacket(values[0]);})
.then(howDoILook)
.catch(() => {console.log('잘못입었네.');});
셔츠와 바지는 all API로 처리하고 자켓은 then API로 처리한다.
Promise - MDN
빠르게 훝어 보는 node.js - promise를 이용한 node.js에서 콜백헬의 처리
Promise Patterns & Anti-Patterns
자바스크립트 Promise 쉽게 이해하기
JavaScript의 strict mode
strict mode 는 ES5에 새롭게 추가된 기능으로, strict context
에서 동작하고 기존에 그냥 넘어갔던 불안전한 코드에 대해 예외를 발생시킨다.
- 공통적으로 저지르는 코드 실수를 잡아 예외를 발생시킨다.
- (전역 객체에 접근하는 것과 같은) 비교적 불안전한 동작이 감지되는 경우를 막거나 예외를 발생시킨다.
- 혼란스럽거나 좋지 않은 기능은 비활성화한다.
strict mode 사용 방법
전역범위에서 사용하고 싶다면, 스크립트 상단에 아래와 같이 'use strict'
를 입력하면 된다.
'use strict';
var foo = 'bar';
function scope만 적용하는 것도 가능하다.
function sum(a, b) {
'use strict';
return a + b;
}
(function() {
// 즉시실행함수
'use strict';
var foo = 'bar';
})();
특징
전역 변수 할당 불가
기존에는 var
키워드 없이도 변수 선언이 가능하다. 단지 전역 변수로 선언될 뿐이었다.
foo = 'bar'; // 이 변수는 전역 변수로 선언되었습니다.
console.log(window.foo === foo); // true
strict mode에선 전역 변수 할당을 시도하는 경우, 에러가 발생한다.
'use strict';
foo = 'bar'; // Uncaught ReferenceError: foo is not defined
객체를 메모리 해제하는 것 역시 에러가 발생한다.
'use strict';
var a = 1;
delete a; // Delete of an unqualified identifier in strict mode.
function b() {return 1;};
delete b; // Delete of an unqualified identifier in strict mode.
eval과 arguments
eval
과 arguments
라는 이름을 사용하려는 시도는 에러를 발생시킨다.
// All generate errors... from John Resig - ECMAScript 5 Strict Mode, JSON, and More
obj.eval = ... // 크롬에서 실행 가능
obj.foo = eval; // 크롬에서 실행 가능
var eval = ...;
for ( var eval in ... ) {}
function eval(){}
function test(eval){}
'use strict'; var aa = function(eval){}
new Function("eval") // 크롬에서 실행 가능
arguments = 1;
(function() {arguments = 1;})();
eval
을 이용한 변수 할당도 금지되었다.
'use strict';
eval('var foo = 1;');
console.log(foo); // undefined
Function
동일한 명칭의 인자를 사용하는 것 역시 금지된다.
'use strict';
(function(a, a) { return a;})(1,2); // Duplicate parameter name not allowed in this context
John Resig - ECMAScript 5 Strict Mode, JSON, and More
Tagged Template Literals
아래와 같이 태그를 사용하면 템플릿 리터럴을 문자열과 표현식으로 나눠서 파싱할 수 있다.
var x = 3;
var y = 5;
function getArea(literals, ...expressions) {
for(let i = 0; i < literals.length; i++)
console.log('.'+literals[i]+'.');
let area = expressions[0] * expressions[1];
return area;
}
let area = getArea`나는 가로길이 ${x}, 세로길이 ${y} 인 직사각형 넓이를 알고싶다.`;
console.log(area);
출력결과
.나는 가로길이 .
., 세로길이 .
. 인 직사각형 넓이를 알고싶다..
15
Rust
Rust 설치하기
rustup을 이용한 설치
rustup 은 Rust 공식 릴리즈 채널의 다양한 버전의 컴파일러를 쉽게 관리하는 인스톨러이다. 루비의 rvm, 노드JS의 nvm과 유사한 역할을 한다. RustFmt와 같은 툴을 사용하기 위해서도 rustup이 필요하다.
Proxy 환경에서의 설치
아래와 같이 환경변수 값을 설정하고 설치파일을 실행한다.
RUSTUP_USE_REQWEST=1 # work-around: certificate verified error
https_proxy=프록시주소
Rust 직접 설치
패키지 매니저를 통한 설치는 특정 버전 설치만 가능하다. 가능하면 rustup을 이용하자.
Windows with Chocolatey
choco install rust
Mac OSX with Homebrew
brew install rust
Rust compiler viersion 확인
rustc --version
rustc 1.29.2 (17a9dc751 2018-10-05)
Cargo로 새로운 프로젝트 생성 & 빌드
Cargo는 Rust의 패키지 매니저이다.
cargo new hello
Created binary (application) `hello` project
cd hello
cargo run
Compiling hello v0.1.0 (file:///D:/DEV_HOME/workspace/kata/rust/hello)
Finished dev [unoptimized + debuginfo] target(s) in 3.32s
Running `target\debug\hello.exe`
Hello, world!
mdbook
사용법
mdbook
은 Rust 언어 기반의 커맨드라인 툴 & 라이브러리이다. 마크다운 파일을 이용해서 책을 만들 수 있다. Gitbook과 유사하지만, Rust 언어로 만들어졌다.
설치
바이너리는 릴리즈 페이지에서 다운로드 받을 수 있다. 그러나 난 이미 Rust를 PC에 설치한 상태이기 때문에 Cargo를 통해서 설치했다.
> cargo install mdbook
이외에 소스 레파지토리에서 직접 받아 빌드한 후 사용하는 방법도 있다.
프로젝트 구조
mdbook-test/
├─ book
└─ src
├─ chapter_1.md
└─ SUMMARY.md
src
디렉토리에 마크다운으로 작성된 파일들이 있어야 한다. 그리고 설정 파일 등이 들어갈 수 있다.book
디렉토리는 렌더링된 책이 있는 공간이다. 빌드된 결과물로 서버에 올리면 볼 수 있는 상태이다.SUMMARY.md
파일은 책의 목차를 작성하는 파일로 제일 중요하다. SUMMARY에 정의된 목차와 파일링크를 토대로 책이 렌더링된다.
CLI 명령어
init
: mdbook 생성
mdbook 보일러 플레이트를 생성하기 위해, init
명령어를 사용한다. .gitignore 파일을 생성여부와 책 제목을 묻는데, 책 제목은 소스 설정파일(book.toml
)에 저장된다.
> mkdir mdbook-test # mdbook 루트 디렉토리 생성
> cd mdbook-test/
> mdbook init
Do you want a .gitignore to be created? (y/n)
y
What title would you like to give the book?
mdbook demo
2018-12-15 10:16:30 [INFO] (mdbook::book::init): Creating a new book with stub content
All done, no errors...
build
: 소스를 기반으로 책을 렌더링
> mdbook build
SUMMARY.md
파일을 분석해서 책의 목차대로 렌더링한다.
기본 디렉토리는 book/
이지만 아래와 같이 사용자가 정의한 디렉토리에 빌드할 수 있다.
> mdbook build path/to/book
serve
: 렌더링 된 책 웹에서 보기
serve
명령은 localhost:3000
에서 HTTP를 통해 렌더링 된 책을 볼 수 있게한다.
> mdbook serve
2018-12-15 10:50:58 [INFO] (mdbook::book): Book building has started
2018-12-15 10:50:58 [INFO] (mdbook::book): Running the html backend
2018-12-15 10:50:58 [INFO] (mdbook::cmd::serve): Serving on: http://localhost:3000
2018-12-15 10:50:58 [INFO] (ws): Listening for new connections on [::1]:3001.
2018-12-15 10:50:58 [INFO] (mdbook::cmd::watch): Listening for changes...
test
: code block 테스팅
코드 블록의 테스트를 할 수 있다. 현재는 Rust 언어만 지원한다.
mdbook test
아래와 같이 코드 블록 3건이 있는데 각각 성공 / 무시 / 무시하는 시나리오이다.
```rust
fn main() {
println!("hello world");
}
```
```rust, ignore
fn main() {
println!("hello world");
}
```
```rust
// ignored
fn main() {
panic!("should panic!"); // should panic
}
```
결과는 아래와 같이 나온다.
> mdbook test
2018-12-15 11:01:47 [INFO] (mdbook::book): Testing file: "D:\\...\\til\\src\\log/mdbook-guide.md"
2018-12-15 11:01:49 [ERROR] (mdbook::utils): Error: Rustdoc returned an error:
running 3 tests
test C:\Users\...\mdbook-f7zZjj\log/mdbook-guide.md - mdbook::CLI_명령어::test (line 92) ... ignored
test C:\Users\...\mdbook-f7zZjj\log/mdbook-guide.md - mdbook::CLI_명령어::test (line 99) ... FAILED
test C:\Users\...\mdbook-f7zZjj\log/mdbook-guide.md - mdbook::CLI_명령어::test (line 85) ... ok
failures:
---- C:\Users\...\mdbook-f7zZjj\log/mdbook-guide.md - mdbook::CLI_명령어::test (line 99) stdout ----
thread 'C:\Users\...\mdbook-f7zZjj\log/mdbook-guide.md - mdbook::CLI_명령어::test (line 99)' panicked at 'test executable failed:
thread 'main' panicked at 'should panic!', C:\Users\...\mdbook-f7zZjj\log/mdbook-guide.md:3:3
note: Run with `RUST_BACKTRACE=1` for a backtrace.
', librustdoc\test.rs:367:17
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
C:\Users\...\mdbook-f7zZjj\log/mdbook-guide.md - mdbook::CLI_명령어::test (line 99)
test result: FAILED. 1 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out
테스트가 실패하는 경우 오류가 발생한 라인번호만 나오고 출력하진 않는다.
수식 입력을 위한 MathJax 지원
MathJax를 선택적으로 적용할 수 있다. book.toml
파일에 아래와 같은은 설정을 추가하면 사용할 수 있다.
[output.html]
mathjax-support = true
기존 MathJax에선 >> ... >>
를 사용했다면, mdbook에선 \\[ ... \\]
방식으로 표현한다.
아래는 LaTex 커맨드로 작성한 수식이다.
\\[ x = \frac {-b \pm \sqrt {b^2 - ac}} {a} \\]
\[ x = \frac {-b \pm \sqrt {b^2 - ac}} {a} \] \[ \sqrt a \]
만약 인라인으로 작성하고 싶다면, \\[ ... \\]
대신 \\( ... \\)
로 쓰면 된다. \( T_1 = 2x \) 와 같이 렌더링된다.
CI
Travis CI를 이용해서 지속적 통합을 제공한다. github와 연동하기 위해 먼저 public repository에 접근할 수 있는 access token을 발급받는다.
tooken을 발급받았으면 아래와 같이 .travis.yml
파일에 deploy 관련 설정을 추가한다. local-dir
은 travis CI에서 실제로 소스를 pull 받은 경로를 넣어주면 된다.
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: /your/local/directory/book
keep-history: false
on:
branch: master
코틀린 프로그래밍
과정 소개
- 과정 일정: 2019/01/14 ~ 2019/01/17
- 장소: 멀티캠퍼스
- 강사: 강성윤(kkang104@gmail.com)
- 강의자료: http://70.12.113.190:8080
왜 코틀린을 써야하는가
- Mobile App 개발
- iOS: Objective C, Swift
- Android: Java, Kotlin
- Java의 문제
- (비교적) 구형 문법
- 유지보수 불편함
2017 Google I/O에서 안드로이드 공식 언어로 발표
Android first class language
특징
- Java 100% 호환
- JVM 기반 언어(Run everywhere)
- Java/Kotlin 혼용 가능
- 개발 생산성: Java 10줄짜리 코드가 코틀린 2줄로...
- 함수형 프로그래밍, Null safety 등 언어 트렌드 지원
- 크로스 플랫폼 개발
- Server-side 개발 가능(Spring 5부터 코틀린 지원)
- Kotlin.js: 브라우져 개발
- Kotlin/Native (v1.3부터): without JVM
- Google의 (전폭적인) 지원
개발환경
- intelliJ
- Android studio
- Java 1.8
- Gradle
- Eclipse (굳이...?)
참고 소스코드
코틀린 기본
특징
static
예약어가 없다. Top-level에 선언하면 됨.- 클래스 선언 없이 함수, 변수를 사용 가능
- 세미콜론(
;
)을 강제하지 않는다. (enum
class 선언 제외)
코틀린 파일 정의
- 확장자는 *.kt
- 파일과 클래스를 구분
class Student {
int id;
void getId() {
return this.id;
}
}
class Student {
var id: int = 10
fun getId() {
...
}
}
클래스 없이 변수, 함수 단독으로 정의 가능하다.
코틀린은 변수, 함수도 Top-level에 선언할 수 있다.
함수 내 함수, 클래스도 선언 가능하다.
Java의 경우, class만 Top-level에 선언할 수 있다.
package com.example.student
import java.util.*
var sum = 0
fun calSum() {
for (i in 1..10) {
sum += i
}
}
class student {
var id: int = 10
fun getId() {
id
}
fun hello() {
println("hello")
}
}
fun main(args: Array<String>) {
calSum()
println(sum)
user
}
패키지
- 다른 패키지의 함수나 변수를 import하려면? 직접 import해서 사용하면 됨
package com.example.two
val threeVal = 10
fun threeFun() {
}
package com.example.one
import com.example.two.threeVal
import com.example.two.threeFun
...
- 가상패키지: 실제 위치와 다른 패키지명을 사용할 수 있다.
파일구조
src
└─one
└─file.kt
package my.package
...
- 이름 변경해서 임포트할 수 있다.
import java.util.Date as MyDate
기본 패키지
기본적으로 코틀린 파일에서 사용할 수 있는 패키지
java.lang.*
kotlin.*
kotlin.io.*
kotlin.collections.*
...
변수와 함수
변수
-
변수 타입은 생략 가능하며, 초기값으로 타입 추론이 가능하다.
-
변수는
var
val
로 시작해야 한다.var
: mutableval
: assign-once. immutable?
-
Top-level, 클래스의 멤버 변수는 모두 초기화를 해야함.
- 로컬 변수는 초기값을 지정하지 않아도 되지만, 기본으로 설정되진 않는다.
- 코틀린에선 모든 것이 객체이다.
-
Null safety: 변수 선언시 명시적(
?
)으로 nullable/ non-nullable 구분해야 함. -
const
vsval
- 값 변경은 불가
val
은 property- getter/setter를 내장하고 있음 (커스터마이징 가능함)
-
Java 스타일
class student {
int id;
String name;
void hello() {
return "Hello, " + this.name;
}
}
- Kotlin 스타일
a: Int = 10
class Student {
val id: Int = 10
val name: String = "John"
void hello() {
"hello $name"
}
}
함수
- 매개변수는 기본으로
val
- 의미있는 반환값이 없을때는
Unit
Unit
은 생략할 수 있으며 리턴타입이 선언되지 않으면 기본적으로 적용- Java의
void
는 예약어, Kotlin의Unit
은 타입
val emptyValue: Unit = Unit()
으로 사용 가능 - Single expression function
- default argument: 인자가 없는 경우
- named argument: 연산자 오버로딩의 대안
- 중위표현식(infix): 연산자를 피연산자 중간에 위치시킨다
- object 멤버함수만 가능
- 매개변수 하나만 존재해야 함
- 가변인수
- 재귀함수:
tailrec
예약어를 붙이면 효율적인 재귀함수를 만들 수 있다.tailrec
예약어는 바이트코드로 변환시 반복문으로 변환한다.- 꼬리재귀함수의 경우에만 추가 가능
// 기본 함수 형태
fun sum(a: Int, b: Int): Int {
return a + b
}
// Single expression function with return type
fun sum(a: Int, b: Int): Int = a + b
Single expression function without return type
fun sum(a: Int, b: Int) = a + b
// Default argument
fun hello(name: String = "John") {
println("Hello, " + name)
}
// Default & Named argument
fun hello(name: String = "John", id: Int) {
println("Hello, " + name)
}
...
fun main(args: Array<String>) {
hello("Jane", 10);
hello(no = 10, name = "Jane");
hello(no = 10);
}
// 중위표현식
// 함수 확장
infix fun Int.add(a: Int): Int {
return
}
// 가변인수
fun <T> printAll(a1: Int, vararg array: T) { // Array<Any>
for(a in array) {
println(a)
}
}
fun main(args: Array<String>) {
printAll(10, "Hello", "world")
printAll(10, 20, false)
}
기본 데이터 타입
숫자 타입
char
타입을 숫자로 인식하지 않음- 모든 숫자 타입 클래스는
Number
타입의 서브클래스이다 - 자동 형변형을 지원하지 않음
- 숫자 타입 데이터의 가독성을 위해서 underscore(
_
) 사용 가능.
val positive: Int = 1_000_000
문자열
- 스트링 템플릿 리터럴 지원
val name = "John" val greeting = "hello ${name}"
- raw string 지원
val greeting = """hello world"""
Any
타입
코틀린의 최상위 클래스(Java Object
보다 상위 클래스)
// Type check
fun getLength(obj: Any): Int {
if(obj is String) {
return obj.length // `length`는 String의 property, `is` 타입체크가 true일 경우, String 타입으로 스마트 캐스팅
}
return 0
}
// switch 보다 기능이 많은 when
fun cases(obj: Any): String {
when(obj) {
1 -> return "One"
"Hello" -> return "Greeting"
is Long -> return "Long"
}
}
Null
허용 타입val a: Int = null // error val b: Int? = null // OK fun parseInt(str: String): Int { return str.toIntOrNull() }
Any?
<-Any
is
,as
val myVal1: Any = 10 val myVal2: Any? = myVal1
Unit
& Noting
: 제네릭의 타입 명시를 위해서
- Unit: 명시적인 리턴이 없다.
- Nothing: 의미있는 리턴이 아니다, 코틀린의 최하위 클래스
- Unit은 타입 클래스
// OK fun myFun(): Unit { return Unit() }
- Nothing: 함수의 리턴타입?
// OK fun myFun(): Nothing { throw Exception() }
타입 확인과 캐스팅
- 타입 체크를 위해
is
예약어 이용 - 타입 체크결과가
true
이면 스마트 캐스팅
var a1: Int = 10
var a2: Double = a1 // !error
var a3: Double = a1.toDouble() // OK
배열: 배열도 클래스(Array
)이다.
fun main(args: Array<String>) {
var array = arrayOf(1, "Shawn", true) // Array<Any>
array[0] = 10
array[2] = "hello"
println("size: ${array.size}.. ${array[0]}, ${array.get(1)}, ${array.get(3)})
}
컬렉션
- mutable 클래스와 immutable 클래스로 구분
조건문
if
표현식
expression vs statement
statement: 문장이 실행됐을 때 결과값이 발생하지 않음
expression: 결과값이 발생함
- Java의
if
는 statement, Kotlin의if
는 표현식- if 표현식의 결과를 할당 가능함
val number = 5 var isEven = if(number%2 == 0) { true } else { false }
if
표현식을 변수에 할당하려면else
문을 꼭 정의해야 한다.var isEven = if(number%2 == 0) true // [!] error
- if 표현식의 결과를 할당 가능함
when
표현식
in
: 범위 지정val data = 15 when(data) { !in 1..100 -> println("invalid data") in 1..10 -> println("1 <= data <= 10") in 11..20 -> println("11 <= data <= 20") }
- 다양한 타입의 데이터에 대한 조건 지원
fun testWhen(data: Any) { when(data) { 1 -> println("data is 1") "hello" -> println("data value is 'hello'") is Boolean -> println("data value is Boolean") } }
if
-else
대체용val data = 10 when { data <= 10 -> println("data <= 10") else -> println("none") }
- 표현식
val data = 3 val result = when(data) { 1 -> "result is 1" 2 -> "result is 2" else -> "out of range" }
for
반복문
in
연산자를 사용, 전통적인for(초기값;반복조건;증감) {}
문법은 지원하지 않는다.- index 값을 획득하고자 한다면
indices
를 이용val list = arrayOf(1, 2, 3) for(i in list.indices) { println(list[i]) }
- index와 값을 함께 받고 싶으면
withIndex()
를 이용val list = listOf(1, 2, 3) for((index, value) in list.withIndx()) { println("the element at $index is $value) }
- 반복 조건
for(i in 1..100) { }
: 1 <= x <= 100for(i in 1 until 100) { }
: 1<= x < 100for(i in 1 step 100) { }
: ?for(i in 1 down to 100) { }
: ?
while
반복문: Java와 차이가 없음
break
, continue
그리고 label
label
의 사용방법aaa@ for(i in 1..3) { for(j in 1..3) { if(j > 1) break@aaa println("i: $i, j: $j") } }
연산자
- * 연산자: 배열(not list)의 원소 나열, 가변인수의 함수를 호출할때 사용하면 편함
val asis = arrayOf(10, 20, 30) val list = asList(1, 2, asis[0], asis[1], asis[3], 100, 200)
val asis = arrayOf(10, 20, 30) val list = asList(1, 2, *asis, 100, 200)
==
vs===
==
: 값===
: 레퍼런스
- Integer 타입은 -128 ~ 127까지 캐싱
Null
안전 관련 연산자?
:?:
?.
!!
- 연산자 재정의 가능
클래스
class
예약어로 선언- 클레스 몸체(
{}
)가 없다면 생략 가능 new
연산자가 없다.- 생성자
constructor
키워드로 생성- 주 생성자(primary constructor)
- 클래스 선언부분에 작성
- 하나의 클래스에 하나의 주 생성자만 정의 가능
- 보조 생성자(secondary constructor)가 있다면 주 생성자는 없어도 됨
- 보조 생성자(secondary constructor)
- 클래스 body 영역에 선언
- class 내에 무조건 1개의 생성자가 존재
- 선언하지 않으면 컴파일러가 주 생성자(매개변수 없는) 선언
- 개발자에의해 주 생성자, 보조 생성자 선언 가능
- 모두 선언되었다면, 보조 생성자에선 무조건 주 생성자 호출
- 생성자 매개변수
- 클래스의 초기화(
init
) 블록, 클래스 프로퍼티에서는 접근이 되지만, 클래스에 정의된 함수에서는 사용불가
class User(name: String, age: Int) { init { // 객체가 생성되는 순간 생성자 초기화 println("init... name: $name, age: $age") // OK } val upperName = name.toUpperCase() // OK fun sayhello() { println("hello $name") // [!] error } }
- 주 생성자 내에서
var
,val
을 이용해서 매개변수를 선언하면 클래스 멤버가 됨 (보조 생성자는 해당안됨)
class User(val name: String, val age: Int) { // name, age는 멤버변수가 됨 val upperName = name.toUpperCase() init { println("init... name: $name, age: $age") // OK } fun sayHello() { println("hello $name") // OK }
- 클래스의 초기화(
결론
- 클래스 생성자 한개만 생성(권장사항) => 주 생성자 이용
- 멤버 변수 선언하기 편하다
- 매개변수들 중 필수 매개변수만 주 생성자로 선언
- 보조 생성자에서는 주 생성자 연결
프로퍼티
property vs field
- 코틀린에서는 Top-level과 클래스의 변수를 프로퍼티(property) 라고 부름(함수 내부의 지역변수는 property가 아님)
- Java에선 field, Kotlin에선 property
- Property는 자체적으로 accessor(
getter
,setter
)를 내장 var
로 선언하면get()
추가get
,set
내의field
(배킹 필드)는 프로퍼티에 저장된 값 자체를 지칭하는 예약어val
의 경우,get()
을 정의하였다면, 초기값을 명시하지 않아 도 된다.- 주 생성자의
var
,val
은 프로퍼티 커스터마이징이 불가능- 클래스 바디에 새로운 프로퍼티를 생성해서 커스터마이징
class User(val name: String) { val myName = name get() = field set(Value) { field = value } }
- 클래스 바디에 새로운 프로퍼티를 생성해서 커스터마이징
기본 형태
val name: String = "Shawn"
get() = field
set(value) = { field = value }
캡슐화
클래스의 멤버변수는 외부에서 접근을 못하게 해야한다.
private
접근자로 변수 접근을 제한하고 다른 방법(method)을 통해서 핸들링하자.
프로퍼티 초기화: 선언시 할당하지 않아도 되는 경우
init
블록에서 초기화
class User {
var name: String
val id: Int
init {
name = "Shawn"
id = 0
}
}
Null
허용으로 선언
class User {
var name: String? = null
var age: Int? = null
constructor()
}
늦은 초기화(late init)
var
에서만 사용 가능하다- 주 생성자에서 사용할 수 없다
- custom getter/setter를 사용하지 않은 프로퍼티에만 사용할 수 있다.
nullable
프로퍼티에서 사용할 수 없다.- 기초 타입에서 사용할 수 없다.
- Dependency Injection 사용성 증대
- Nullable 없이 DI 사용 가능
class User {
lateinit var lateAge: Int
}
fun main(args: Array<String>) {
val user = User()
user.lateAge = 10
println(user.lateAge)
}
초기화 미루기(lazy)
- 프로퍼티를 이용할 때로 초기화를 미룬다
- lazy는 일종의 실행 영역
by lazy { }
로 선언- 퍼포먼스 이슈가 있을 때 사용
val foo: String by lazy {
println("lazy foo...")
"hello"
}
프로퍼티 값 변경 감지
class User {
var name: String by Delegates.observable("초기값", {props, old, new -> println("$old .. $new")})
}
상속
Any
클래스
- 코틀린의 최상위클래스
- 모든 코틀린의 클래스는 Any의 서브클래스이다.
상속을 통한 클래스 정의
- 코틀린의 모든 클래스는 기본적으로
final
open
예약어로 선언한 클래스만 상속 가능하다extends
예약어가 없음, 콜론(:
)으로 상속관계를 표현
class Rect: Shape() {
var width: Int = 0
set(value) {
if(value < 0) field = 0
else field = value
}
var height: Int = 0
set(value) {
if(value < 0) field = 0
else field = value
}
}
오버라이드
함수
- 함수를 선언하면 기본적으로 final
- 함수 오버라이드를 허용하려면
open
으로 명시 override
예약어로 상위 함수 재정의한 것을 명시override
된 함수는 자동으로open
상태이다
프로퍼티
- 프로퍼티는 기본적으로
final
- 상위 클래스의 프로퍼티와 이름, 타입이 동일함
- 규칙
- 상위
val
로 선언된 프로퍼티 -> 하위에서val
,var
재정의 가능 - 상위
var
선언 프로퍼티는 -> 하위에서var
재정의 가능,val
불가 - 상위 Nullable 선언 -> 하위 Non-nullable로 재정의 가능
- 상위 Non-nullable 선언 -> 하위 Nullable 재정의 불가
- 상위
생성자
- 주 생성자가 선언되어 있다면 보조 생성자에선 주 생성자와 연결하기 위한
this()
구문이 추가되어야 함 - 객체 생성시, 어떤 식으로든 상위 클래스의 생성자가 호출되어야 함
- 생성자 수행흐름
this()
orsuper()
init
블록 실행- 생성자 블록 실행
캐스팅
- 기초 데이터 타입은 자동 형변환 안되고 함수에 의해서 변환 가능
스마트 캐스팅
is
예약어 이용 시: is 연산 결과가 true이면 타입 변환- 하위 -> 상위 타입으로: 암시적 캐스팅
as
를 이용한 캐스팅: 상위 -> 하위로 변환할 때
접근제한자
public
,internal
,protected
,private
public
: 접근제한자 명시가 없으면 기본으로 적용protected
: top-level에서 사용 불가private
: 동일 file 내에서만 사용 가능internal
: 동일 모듈 내에서 사용 가능
프로퍼티 접근제한자
get()
: 프로퍼티 접근 제한자와 항상 동일하게 적용set()
: 프로퍼티 접근제한자보다 범위를 넓혀서 설정할 수 없다.
상속과 접근제한
open
과private
은 같이 사용할 수 없음- 하위 클래스에서 상위 멤버를
override
할 때 접근범위를 줄일 수 없음
추상클래스
선언
abstract class Super {
val no: Int = 10
abstract val message: String
fun foo() {
println("abstract foo... $no")
}
abstract fun bar()
}
class Sub: Super() {
override val message: String = "hello"
override fun bar() {
println("sub bar... ${message}")
}
}
인터페이스
- 객체 생성 불가
- 추상 함수 선언이 주 목적 (has-a)
- 함수 기능 구현 가능(is-a...?)
interface Walkable {
var numberOfLegs: Int
fun walk();
fun getShoes(): String { // 함수 구현 가능
return "Nike"
}
}
interface Eatable {
fun eat(food: String)
}
class Animal {
}
class Human: Walkable, Animal(), Eatable { // 인터페이스는 생성자를 호출하지 않는다.
override fun walk() {
println("human is walking...")
}
override fun eat(food: String) {
println("human is eating ${food}...")
}
프로퍼티
- 인터페이스에 프로퍼티 추가 가능
- 추상형으로 선언되어 있거나, getter/setter를 정의해야 한다.
- 추상 프로퍼티가 아니라면
val
은 getter를 선언해야 한다.var
는 getter/setter를 선언해야 한다
- 인터페이스의 프로퍼티 getter/setter는
field
를 사용할 수 없다.- 컴파일할 때, 인터페이스의 프로퍼티는 실제 필드로 정의하지 않는다.
11. 다양한 클래스
data
클래스
- Value Object, Data Trasfer Object, ...
data
키워드로 선언- 주 생성자가 선언되야 하며, 매개변수 하나 이상 선언해야 함
- 주 생성자의 모든 매개변수는
var
혹은val
로 선언해야 함
- 주 생성자의 모든 매개변수는
abstract
,open
,sealed
,inner
등 키워드를 사용할 수 없음
data class User() // error, 주 생성자에 매개변수가 없음
data class User2(name: String) // error, val이나 var로 매개변수 선언해야 함
data abstract class User3(val name: String) // error, abstract와 같은 키워드를 사용할 수 없음
data class User4(val name: String, no: Int) // error, 모든 매개변수는 var 혹은 val로 선언해야 함
data class User(Val name: String, val age: Int) // OK
equals()
- 객체 비교가 아닌, 객체의 데이터 비교를 도와주는 함수이다.
- 동일한 클래스, 주 생성자에 선언된 프로퍼티만 비교
data class User(val name: String, val age: Int) {
var email: String = "your@email.com"
}
fun main(args: Array<String>) {
val shawn: User = User("Shawn", 30)
val jane: User = User("Shawn", 30)
jane.email = "jane@gmail.com"
println("${shawn.equals(jane)}) // true
}
toString()
- 데이터 클래스의 데이터를 문자열로 반환
- 일반 클래스는 인스턴스의 주소값을 반환
- 주 생성자의 프로퍼티만 반환
componentN()
- 데이터 클래스 프로퍼티 값을 획득
- 비구조화 할당(destructing assignment)
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
var user = User("Shawn", 30)
println("name: ${user.component1()}") // Shawn
println("age: ${user.component2()}") // 30
val (name, age) = user // 비구조화 할당
}
copy()
- 객체를 복사
enum
클래스
- 열거형 타입
enum
예약어로 생성name
,ordinal
내장 프로퍼티가 존재
기본형태
enum class Direction {
NORTH, SOUTH WEST, EAST
}
fun main(args: Array<String>) {
val direction: Direction = Direction.NORTH
println("${direction.name} ... ${direction.ordinal}")
}
개발자 임의 데이터 타입
enum class Direction(val no: Int) {
NORTH(0), SOUTH(1), WEST(2), EAST(3)
}
익명 클래스 이용
- 각 원소는 enum 클래스의 서브타입
- 열거상수는 객체
- 이름 없는 클래스를 직접 정의
- enum 상수 영역과 클래스 바디영역을 구분하기 위해 세미콜론(
;
)을 사용해야 함
enum class Direction(val no: Int) {
NORTH {
override val bar: Int = 10
override fun foo() { println("North foo...") }
},
SOUTH {
override val bar: Int = 20
override fun foo() { println("South foo...") }
}; // <-- 세미콜론으로 구분
absract val bar: Int
abstract fun foo()
}
sealed
클래스
sealed
예약어로 선언- enum 클래스와 유사함
- vs class: sealed class는 인스턴스 생성이 불가능
- vs abstract class
- abstract: 어디에서나 선언 가능
- sealed: sealed class가 선언된 그 file 내에서만 선언 가능
- 내가 선언한 클래스만 사용해라!
enum
vs sealed
- enum: data, function 추가 가능
- enum class에서 정의한 data, function만...
- 모든 enum 상수가 동일 변수, 함수를 가지고 있음
- sealed: 상수(하위 class) 별 다양한 변수, 함수 가능
기본 형태
sealed class Shape {
class Circle(val radius: Double): Shape()
class Rect(val width: Int, val height: Int): Shape()
}
class Triangle(val bottom: Int, val height: Int): Shape()
Nested 클래스
- 특정 클래스 내에 선언된 클래스를 지칭 -> 예약어가 없다
- Outer 클래스의 멤버에 접근할 수 없다.
- Nested 클래스에서 Outer 클래스의 멤버에 접근하려면
inner
클래스를 선언해야 한다.- inner 클래스는 외부에서 객체 생성이 불가
- 외부에서 이용하려면 outer 클래스에서 인스턴스를 생성해줘야 함
- Nested 클래스에서 Outer 클래스의 멤버에 접근하려면
inner
클래스
object
클래스
- 기본적으로 anonymous 클래스
object Foo { }
로 선언시,Foo
라는 이름을 가진 객체가 생성
companion
- Java class의 static 멤버와 동일
클래스 별 특징
특징 | nested | inner | object | companion object |
---|---|---|---|---|
예약어 | x | o | o | o |
outer 멤버 접근 | x | o | o | - |
outer에서 접근 | o | o | △private | - |
외부 접근 | o | x | △ 상속(Type 명시) | - |
타입 | class | class | Any | - |
12 함수형 프로그래밍과 람다
함수형 프로그래밍의 정의
변수(할당) 없는 프로그래밍
순수 함수로 정의되는 함수
- 순수함수는 Side-effect가 발생하지 않는다
- 함수 외부의 다른 값을 변경하지 않음
- 내부에서 별도의 I/O가 발생하지 않음
- 동일 입력 동일 출력
프로그래밍 패러다임
-
절차지향
- 알고리즘과 로직 중심으로 문제 해결
-
객체지향 프로그래밍
- 기능과 데이터를 묶어 객체를 만들고 조합하는 프로그래밍
-
함수형 프로그래밍
- 함수의 선언과 선언된 함수의 유기적인 흐름
-
왜 함수형 프록래밍이 대두되는가
- 복잡성 증가
- 디버깅
- 동시성 프로그래밍
함수형에서의 데이터
- 데이터는 변경되지 않으며 프로그램의 상태만 표현(immutable state data)
- 데이터는 변경하는 것이 아니라 새로운 데이터를 만들어 리턴
장점
- 간결한 코드 -> 유지보수성 증대
- 안전한 동시성 프로그래밍 -> 부수효과가 없음
원칙
일급객체(First class citizen)
- 프로그램 top-level에 함수를 정의
- 모든 구성요소를 함수 안에 작성
- 함수를 변수처럼 이용
코틀린에서
다양한 구성요소를 포함
함수 내에 변수, class, 함수 등 모든 요소를 포함 가능
변수처럼 이용하는 함수
// 람다 표현식
val foo = { bar: Int ->
println("hello world")
bar + 10
}
foo(5)
// reflection
val foo = { bar: Int ->
println("hello world")
bar + 10
}
val bar = ::foo
람다 표현식
- 익명함수(anonymous function)
- 마지막 표현식을 리턴
fun sum(a: Int, b: Int): Int {
return a + b
}
val sum = {a: Int, b: Int -> a + b }
즉시실행함수
{ println("hello") }()
run { println("hello") }
매개변수가 없는 람다함수
val foo = { -> 10 + 20 }
val bar = { 10 + 20 }
함수 타입
- 타입을 명시하면 파라미터 타입을 명시하지 않아도 됨
fun sum(a: Int, b: Int): Int {
return a + b
}
val foo: (Int, Int) -> Int = { a: Int, b: Int -> a + b }
val bar: (Int, Int) -> Int = { a, b -> a + b }
typealias
를 이용한 타입 정의
typealias MyType = (Int) -> Boolean
val foo: MyType = { it > 10 }
it
을 이용한 매개변수 이용
- 매개변수가 하나인 경우, it으로 매개변수 지칭
val foo: (Int) -> Int = { x -> x + 2 }
val bar: (Int) -> Int = { it + 2 }
val bax = { it + 2 } // error, it의 타입이 명시되지 않아서
멤버 참조
- Member reference는 (
::
)로 표현
class User(val name: String, val age: Int)
val bar: (User) -> String = { u: User -> u.name }
val foo: (User) -> String = User::name
고차함수 (high-order function)
함수의 매개변수로 함수를 전달받거나, 리턴할 수 있는 함수
fun foo
함수타입 매개변수
함수 반환
익명함수
코틀린 API의 고차함수
run()
- 람다함수를 실행하고 그 결과값을 획득
- 객체의 멤버에 접근
- 반복적으로 인스턴스명을 입력하지 않아도 된다?
val user = User() val runResult = user.run { name = "Kim" age = 10 true }
- 반복적으로 인스턴스명을 입력하지 않아도 된다?
apply()
- 대입한 객체를 다시 리턴받음
let()
let
을 이용하는 객체를 매개변수로 전달
with()
- 객체를 매개변수로 전달받아 멤버들에 접근
인라인 함수
- 고차함수의 런타임 시, 성능이슈 문제 (JVM)
inline
예약어를 함수 선언 앞에 추가하면 컴파일 단계에서 정적으로 포함
고차함수를 컴파일 할 때
fun foo(calc: (a: Int, b: Int) -> Int) {
calc(10, 20)
}
fun main(args: Array<String>) {
foo { x, y -> x + y }
}
// Java decompile
// 성능이슈 발생
public final class Ch12Kt {
public static final void foo(@NotNull Function2 calc) {
Intrinsics.checkParameterIsNotNull(calc, "calc");
calc.invoke(10, 20);
}
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
foo((Function2)null.INSTANCE);
}
}
noinline
noinline
예약어로 inline에 포함하지 않아도 되는 매개변수를 명시적으로 선언
논 로컬 반환
crossinline
label
을 통한 람다함수 반환
- 임의의 label로 람다함수 반환이 가능
- 하지만 고차함수 이름으로도 반환 가능
- 모든 고차함수는 고차함수 이름으로 label이 지정되어 있음
클로저
14 컬렉션
forEach()
, forEachIndexed()
all()
, any()
count()
, find()
reduce()
, reduceRight()
, fold()
, foldRight()
max()
maxby()
min()
minby()
none()
sumBy()
filter()
, filterNot()
, filterNotNull()
drop()
take()
map()
groupBy()
Null 안정성
The billion dollor mistake
값이 아무것도 대입되지 않은 상태
null 허용과 불허
Null 확인 연산자 ?.
val foo: String? = "hello"
var length: Int? = if(foo != null) {
foo.length
} else {
null
}
val foo: String? = "hello"
var length: Int? = foo?.length
println(length)
val bar: String? = null
var length: Int? = bar?.length
println(length)
엘비스 연산자 ?:
Null인 경우에 처리를 명시
val foo = "hello"
var length: Int = if(foo != null) {
foo.length
} else {
-1
}
예외 발생 연산자 !!
- Null인 경우 Exception을 발생시키기 위한 연산자
- Java와 함께 사용할때
안전한 캐스팅 as?
as
연산자 이용시ClassCastException
발생as?
연산자는 null 리턴
예외처리
try
-catch
-final
구문으로 예외 처리try
-catch
구문의 마지막 표현식은 반환 값으로 사용throw
는 표현식- Java의
throws
예약어가 코틀린에선 없음- 이게 꼭 필요한가?
클래스 확장
- 일반적인 클래스 확장은 상속을 이용
- 클래스 내부에 선언된 함수와 프로퍼티 이외에 다른 프로퍼티나 함수를 추가
- 확장 함수는 기존 클래스 내에 정적으로 추가되지는 않음
- OOP 다형성 불가
- super 클래스의 함수 호출 불가ㅐ
프로퍼티 확장
- 프로퍼티 확장 가능
- getter에 의해 초기화해야 함
확장 구문의 위치에 따른 이용
- Top-level에 작성
- 확장 대상 클래스와 확장 프로퍼티or 함수를 따로 import해야 함
- 다른 클래스 내에서 작성
- extension receiver: 확장 대상이 되는 클래스
- dispatch receiver: 확장 구문이 작성된 클래스
- dispatch receiver 클래스 안에서만 사용 가능
제네릭
- 제네릭은 형식타입
- 타입을 예측할 수 없거나
- 하나의 타입으로 고정할 수 없거나
- 제네릭으로 형식 타입을 선언하고 실제 사용할 때 정확한 타입 부여
- 타입 유추에 의해 이용 가능
선언 및 이용
class MyClass<T> {
var foo: T? = null
}
fun <T> bar(arg: T): T? {
...
}
타입 제약
- 제네릭을 선언하면서 특정 타입만 대입되도록 제약
class MathUtil<T: Number> {
fun plus(a: T, b: T): Double {
return a.toDouble() + b.toDouble()
}
}
여러 타입의 제약도 가능하다
interface foo
interface bar
class MyClass1: Foo, Bar
class MyClass2: Foo
class MyClass<T> where T: Foo, T: Bar {
// ...
}
fun main(args: Array<String>) {
var obj = MyClass<MyClass1>() // OK
var obj1 = MyClass<MyClass2>() // error
}
Null 불허 제약
- 제네릭의 형식타입은 기본적으로 Nullable
- 기본적으로
<T: Any?>
로 선언한 것과 같음
- 기본적으로
Variance
제네릭에서 variance(공변)는 상하위관계에서 타입 변형과 관련되어 있다.
- 제네릭은 타입이지 클래스가 아니다: invariance
- 기본적으로 invariance(불공변)
- 공변을 지원하기 위해
out
과in
어노테이션 필요 - out 어노테이션을 이ㅛㅇ하여 하위 타입으로 선언된 객체를 상위 타입에 대입
open class Super
class Sub: Super()
class Foo<T>
fun testVariance() {
Super obj = Sub() // OK, variance
}
fun testInvariance() {
val obj = Foo<Sub>()
val obj2: Foo<Super> = obj // error, invariance
}
open class Super
class Sub: Super()
class Foo<out T> // 하위타입으로 선언된 객체를 상위 타입에 대입
fun testVariance() {
Super obj = Sub() // OK, variance
}
fun testInvariance() {
val obj = Foo<Sub>()
val obj1 = Foo<Super>()
val obj2: Foo<Super> = obj // OK
val obj3: Foo<Sub> = obj1 // error
}
out
어노테이션을 사용하는 규칙
- 하위 제네릭 타입이 상위 제네릭 타입에 대입 가능
- 상위 제네릭 타입이 하위 제네릭 타입에 대입 불가능
- 함수 리턴 타입으로 선언 가능
- 함수 매개변수 타입으로 선언 불가능
val
프로퍼티에 선언 가능var
프로퍼티에 선언 불가능
Covariance
in
어노테이션
상위 제네릭 타입이 하위 제네릭 타입에 대입되어 사용
- 하위 제네릭 타입이 상위 제네릭 타입에 대입 불가능
- 상위 제네릭 타입이 하위 제네릭 타입에 대입 가능
- 함수 리턴 타입으로 선언 불가
- 함수 매개변수 타입으로 선언 가능
val
,var
프로퍼티에 선언 불가
특징 | in | out |
---|---|---|
하위->상위 대입 | x | o |
상위->하위 대입 | o | x |
함수 리턴타입 | x | o |
함수 매개변수 타입 | o | x |
val 프로퍼티 사용 | x | o |
var 프로퍼티 사용 | x | x |
이용측 variance
실제 제네릭을 사용하는 곳에서 in, out을 사용할 ㅅ ㅜ있다.
스타(*) 프로젝션
- 제네릭 타입을
<*>
로 표현하는 것을 의미 - 선언위치에선 불가, 사용위치에서만 가능
- 제네릭 타입을 모른다는 의미
<Any?>
는 정확한 타입을 명시한 것이고,<*>
은 타입을 모른다는 것
제네릭과 as
, is
- 제네릭 정보는 컴파일러를 위한 정보
- 컴파일이 끝나면 제네릭 정보는 모두 사라짐
- 런타임 시에 타입 체크 or 타입 캐스팅이 유지되어야 하는 상황이라면?
인라인 함수와 reinfied
이용
- 제네릭 타입을 런타임까지 유지
Unit
타입
- Java의
void
와 유사하지만 void
는 예약어Unit
은 객체 타입
fun foo() {
return Unit // OK
}
val bar: Unit = Unit
제네릭에서의 Unit
이용
- 상위 타입의 구현 함수의 리턴타입을 사용하지 않고 싶으면? Unit을 사용
interface Foo<T> {
fun some(): T
}
Nothing
타입
- Nothing 타입이 선언되면 null만 대입
- 값이 없다는 것을 명시적으로 표현
- Null이나 Exception 리턴할 때
제네릭에서의 Nothing
이용
- Nothing의 코틀린의 최하위 타입이다.
- 어떤 타입의 프로퍼티에도 대입 가능
in
(반공변)을 사용할 때 사용
18 Reflection
런타임시 프로그램의 구조를 분석해내는 기법
클래스 타입과 레퍼런스
- 런타임 시 동적인 클래스 분석
- 클래스에 대한 정보를 클래스 reference, 클래스 reference가 대입되는 곳은 클래스 타입
- 클래스 타입은
KClass<*>
로 표현하며, 클래스 reference는클래스명::class
로 표현
코틀린 리플렉션 타입에 Java 클래스를 넘길땐
클래스명::class.java
로 표현
val foo: KClass<*> = String::class
fun bar(arg: KClass<*>) {
// ...
}
클래스 정보 분석
val isAbstract: Boolean
: 클래스 reference가 abstract로 선언되었는가?
클래스 생성자 정보 분석
클래스 프로퍼티 분석
클래스 함수 분석
레퍼런스 분석
- 함수 레퍼런스는
::함수명
을 이용 - 함수는
KFunction<*>
으로 사용
고차함수 호출시 이용
fun isOdd(x: Int): Boolean = x%2 != 0
프로퍼티 레퍼런스 분석
- 타입은
KProperty<*>
와KMutableProperty<*>
어노테이션
- 몸통을 가질 수 없음
- 용도
- 컴파일러에게 코드 문법에러를 체크:
@override
- 개발 툴이나 빌더에게 코드 자동 추가: Lombok(
@getter
, ...) - 실행시 특정 기능을 실행:
@Autowired
- 컴파일러에게 코드 문법에러를 체크:
annotation
예약어로 만드렁진다- 인스턴스 생성 불가
- 실행영역을 가질 수 없다
어노테이션 설정
데이터 설정
- 주 생성자를 이용해 데이터 설정
- val만 허용
- 허용 타입
- Primitive type
- String
- class 리플렉션 정보
- enum, annotations, ... -> 개발자가 생성한 클래스를 제외하고
어노테이션 선언 옵션
@Target
: 어노테이션 허용 위치 설정@Retention
: 런타임에 접근할 수 있는지,@Repeatable
: 한 곳에서 반복사용 가능여부@MustBeDocumented
: api 문서에 포함시켜야 하는지
어노테이션 적용대상 지정
19 다양한 기법
Delegation, 위임 패턴
코틀린에선 위임을 by
예약어를 사용
- 인터페이스가 있어야됨
- 인터페이스를 구현한 delegatee가 존재
- delegator도 동일 인터페이스를 상속
- 구현은 하지 않고
by
로 표현
- 구현은 하지 않고
Delegation property
- 프로퍼티 위임도 가능
- 위임을 위한 클래스는
getValue()
와setvalue()
가 있어야 함 - 프로퍼티
get()
에 의해getValue() 호출,
set()에 의해
setValue()` 호출
SAM(Single Abstract Method) 전환
- 한개의 함수만 정의된 클래스를 구현해야 하는 경우, 람다표현식으로 짧게 작성 가능
Type alias
- inner 타입도 정의 가능
Calling Kotlin from Java
어노테이션을 열심히 붙이자!
Property
Top-level function / property
파일명+Kt 클래스의 스태틱 멤버
어노테이션을 이용해서 클래스명을 명명 가능
@file:JvmName("KotlinClass")
동일 패키지 내의 여러 파일을 동일 이름으로 지정하려면 multiclass
어쩌고를 등록해야 한다.
인스턴스 필드
코틀린 프로퍼티는 private 변수로 선언되지만 @JvmField
를 붙이면 일반 필드로 선언된다,
함수명 바꿔서 공개
@JvmName
으로 함수명을 바꿀수 있다. 코틀린에는 전혀 영향 없음
overload 생성
코틀린에선 오버로드가 없다. default argument가 존재한다. 자바로 변환할땐 full signature로 공개한다. @JvmOverloads
로 오버로드 가능
checked exception
어노테이션 붙이자
Null Safety
개발자 몫이다
확장함수
이건 애매
Calling Java with Kotlin
Java API를 코틀린에서 사용 가능한가?
얼마든지
// Java
public class Foo {
void bar() {
/...
}
}
// Kotlin
val obj = Foo()
obj.bar
Object, Collections, primitive type 등은 코틀린에서 API 제공하기 때문에 Kotlin API 사용을 권장한다.
// kotlin
val obj: java.lang.Object = Object()
자바의 타입을 어떻게 매핑할까
- 코틀린에서는 모든 것이 객체
- 코틀린의 타입은 nullable, non-nullable로 구분
primitive type
자바의 기초타입은 null을 대입할 수 없다. 그래서 모틀린에서는 non-nullable로 매핑된다.
Java | Kotlin |
---|---|
int | kotlin.Int |
Boxed-primitive type
Boxing된 primitive type은 객체이기 때문에 null 대입 가능, 그러므로 platform type으로 매핑한다.
Java | Kotlin |
---|---|
java.lang.Integer | kotlin.Int! |
Platform type?
개발자가 명시적으로 선언할 수 있는 타입은 아니다. nullablity 정보가 없는 타입을 지칭한다. null 가능성에 대해 아무 정보가 없기 때문에 NPE 가능성이 높아진다. 개발자가 대응해야 한다.
Non-primitive type
Java | Kotlin |
---|---|
java.lang.Object | kotlin.Any! |
java.lang.String | kotlin.String! |
Collection type
코틀린에선 mutable/imumutable 타입으로 구분한다.
- 기본적으로 platform type으로 처리
- 기본적으로 mutable로 처리
Array
Java | Kotlin |
---|---|
int[] | IntArray! |
String[] | Array<(out) STring>! |
Null Safety
@Nullable
, @NotNull
어노테이션이 추가된 경우
@Nullable
이 있으면 Nullable로 유추@NotNull
이 있으면 non-nullable로 유추
Getter/Setter
코틀린에서는 변수는 프로퍼티(not field)이다. 자바의 getter/setter도 프로퍼티로 이용 가능하다.
- 프로퍼티 변환은 편의성 증대를 위해서,
- 게터 세터를 직접 호출할 수 있다.
return void
Unit 타입으로 변환한다.
// java
class Foo {
public void bar() {
// ...
}
}
// kotlin
val obj = Foo()
val result: Unit = obj.bar()
이스케이프
Java 배열 사용하기
코틀린의 배열은 invariant하다. 즉 Array<Any>
에 Array<String>
을 할당할 수 없다.
자바의 Integer[]
를 Array<Int>
로 사용하는데 없다. variant 문제 발생하지 않음
int[]
를 Array<Int>
로 표현 불가, primitive 타입.
vararg
문법적인 차이,
operator
자바 메서드를 연산자 재정의해서 호출 가능하다.
checked exception
코틀린에선 이 개념이 없다.
Object
메서드
Any
는 JVM만을 위한 타입이 아니다. (JS, Native...)
wait() / notify()
Object로 캐스팅해서 사용
getClass()
::class.java
로 사용
static member
final variable
val로 적용
초기화되지 않은 클래스 멤버 이용
런타임시 에러 발생 가능성 있음
자바 클래스 확장
가능하다.
디자인 패턴
제어 역전 (IoC, Inversion of Control)
제어 역전은 사용자가 작성한 프로그램이 다른 라이브러리 혹은 프레임워크의 제어를 받는 패턴을 의미한다. 일반적으로 작성한 프로그램이 외부 API나 라이브러리를 호출하지만, 제어 역전 패턴에선, 사용자가 작성한 프로그램이 다른 라이브러리 혹은 프레임워크의 제어를 받게 된다.
대표적으로 스프링 프레임워크가 있다. 제어 역전과 의존성 주입(Dependency Injection) 패턴을 이용하여 사용자가 작성한 프로그램을 인스턴스화하고 ApplicationContext에서 인스턴스들을 필요한 곳에 주입한다.
스프링 프레임워크는 개발자가 MVC 패턴 코드로 작성하게끔 강제하고, 작성한 프로그램은 인스턴스를 생성하지 않는다. 프레임워크가 생성하고 관리하기 때문이다.
Controller
, Service
, Repository
등의 클래스를 new
키워드로 생성한 적이 있는지 생각해보자. 보통 어노테이션을 클래스 선언부 상단에 추가하고 어노테이션 드리븐(annotation-driven) 설정으로 모든 클래스를 인스턴스화 한다.
목적
위키피디아의 제어 역전 목적
- 작업을 구현하는 방식과 작업 수행 자체를 분리한다.
- 모듈을 제작할 때, 모듈과 외부 프로그램의 결합에 대해 고민할 필요 없이 모듈의 목적에 집중할 수 있다.
- 다른 시스템이 어떻게 동작할지에 대해 고민할 필요 없이, 미리 정해진 협약대로만 동작하게 하면 된다.
- 모듈을 바꾸어도 다른 시스템에 부작용을 일으키지 않는다.
MVC 패턴과 Spring 프레임워크
사실과 다른 내용이 있으면 피드백 부탁드립니다.
MVC 패턴
- MVC 패턴이 무엇인가?
- 왜 MVC 패턴을 써야하는가?
- 비즈니스가 복잡해지니까
- 모델/뷰/컨트롤러를 분리할 필요성 제기
- 하나의 복잡한 프로그램보단 단순한 여러개의 프로그램의 모음이 유지보수하기 용이하다.
MVC 패턴이 왜 나왔을까?
정적 웹페이지
- 말 그대로 정적 웹사이트
- Q. 정적 웹페이지의 단점?
동적 웹페이지 (MVC 1도 포함)
- WWW은 문서 이상의 기능을 수행한다.
- 쇼핑몰, 게임, 비디오, ...
- 이제 다이나믹 웹 페이지를 만들 수 있음
- 데이터 저장소도 따로 있음 (DBMS)
- 서블릿: Java 소스코드 안에 HTML이
- JSP: HTML 안에 Java 코드가
- 프로그램이 커지면 코드 가독성이 떨어진다.
- 뷰(HTML) 코드와 서버(Java) 코드를 분리하고 싶다
- 1 source/20,000 lines vs 200 sources/100 lines
Q. MVC 패턴인 좋은건 알겠는데 어떻게 구현하지?
Spring 프레임워크
- IoC, Inversion of Control
- Q1. 프레임워크와 라이브러리의 차이?
- Q2. 인스턴스 생성 주체
- 스프링 IOC 컨테이너
- 인스턴스의 생명주기를 관리: 객체 생성은 누가?
- 스프링 Bean
- Lifecycle of Spring Beans
- Singleton, Stateless?
- Lifecycle of Spring Beans
- DI, Dependency Injection
- @Autowired @Inject
- Q. 주입받는 인스턴스가 생성되지 않는다면?
- 인스턴스를 생성하는데 필요한 또다른 인스턴스들
- Injection 방법들
- Field Injection: Autowired
- Constructor Injection: Contructor argument
- @Autowired @Inject
- AOP, Aspect Oriented Programming
- 비즈니스와 별개의 동작: 로깅, 보안, ...
- 원하는 부분에 Point-cut을 추가
- AOP를 Controller 레이어에서 구현 가능?:
@ControllerAdvice
- Spring MVC
- Dispatcher Servlet
- Q. 이미지, html 같은 정적(static) 파일들의 처리는?
- Handler
- AnnotationMethodHandlerAdapter
- Controller
- Model (include business logic)
- View Resolver
- JSP, XML, JSON, Velocity, ...
- Content Negotiation
- Dispatcher Servlet
- 어노테이션
- 어노테이션 종류
@Controller
@Service
@Repository
@Component
@Bean
@Autowired
@Configuration
@SpringBootApplication
- Q. 컨트롤러 클래스에 @Controller 대신 @Service를 붙이면 어떻게 될까?
- Q. Component와 Bean의 차이?
- 어노테이션만 붙이면 다 알아서 되나?
- 어노테이션 종류
- Spring Boot: https://start.spring.io/
- Spring XML 설정
- Default 설정
...
59.9 Use Jetty instead of Tomcat The Spring Boot starters (spring-boot-starter-web in particular) use Tomcat as an embedded container by default. You need to exclude those dependencies and include the Jetty one instead. Spring Boot provides Tomcat and Jetty dependencies bundled together as separate starters to help make this process as easy as possible.
...
참고자료
- 생활코딩 - MVC 패턴
- [JSP/Servlet] CGI 그리고 Servlet과 JSP와의 관계
- spring - 1주차 IoC와 DI
- 백기선 - 더 나은 개발자로 성장하는 팁 "어! 에러가 났네? 어떡하지?"
- 백기선 - 더 나은 개발자로 성장하는 팁 "개발자라면 디버거 사용법은 꼭 알아야죠"
- 백기선 - 예제로 배우는 스프링 입문
- Spring Docs
- 그 외 본문 내 링크들을 참고하세요.
DB
Oracle
Oracle Flashback
Oracle Flashback 기술은 특정 과거 시점의 데이터 상태를 조회하거나 복원할 수 있는 기술을 제공한다.
하지만 너무 이전 시점의 데이터를 조회하면 ORA-01555
같은 오류메시지를 받을 수 있다.
타임스탬프를 이용한 데이터 복원
1. 정확한 시간을 설정하여 데이터 복원
SELECT *
FROM EMP
AS OF TIMESTAMP SYSTIMESTAMP TO_TIMESTAMP('2018-12-27 10:00:00', 'yyyy-mm-dd hh24:mi:ss');
2. 현재로부터 특정 시간 전의 데이터 복원
SELECT *
FROM EMP
AS OF TIMESTAMP SYSTIMESTAMP - INTERVAL '3' HOUR -- DAY, HOUR, MINUTE, SECOND, ...
Block
DBMS의 I/O 단위이다. DBMS에 따라 페이지(page)라고 부르기도 한다.
특징
- I/O의 단위이다.
- 컬럼 단위 I/O를 지원하는 DBMS도 있다.
- 오라클의 허용 블록 크기는 2k, 4k, 8k(default), 16k, 32k 이다.
- 테이블 스페이스별 블록 사이즈와 버퍼풀을 구성할 수 있다.
Oracle에서 테이블 스페이스의 블록 사이즈 조회
-- 오라클 블록의 사이즈 조회
SELECT tablespace_name, block_size/1024 as block_size_kb FROM dba_tablespaces;
참고
HWM, High Water Mark
테이블이 사용한 block과 사용하지 않은 block의 경계점을 나타낸다.
Block의 상태
block을 사용하려면 먼저 할당과 포맷이 이루어져야 한다.
- Used: 사용 중, 포맷 됨
- Unknown: 사용 된 적이 있으나 현재 사용 중인지는 모름, 포맷 됨
- Never used(Unformatted): 사용된 적이 없음, 포맷되지 않음
만약 로우 1000개를 삽입한 후, 500개를 삭제한다면, 삭제한 만큼의 블록이 Unknown
상태가 될 것이다.
Low HWM
Used 상태의 block 영역을 나타낸다.
High HWM
Used + Unknown 영역, 즉 High HWM 이후의 block은 포맷되지 않음을 의미한다.
HWM 재조정을 위한 쿼리
Oracle 10g 이후 ASSM 적용된 테이블 스페이스만 지원된다.
ALTER TABLE 테이블_이름 ENABLE ROW MOVEMENT;
ALTER TABLE 테이블_이름 SHRINK SPACE;
ALTER TABLE 테이블_이름 DEALLOCATE UNUSED;
ALTER TABLE 테이블_이름 DISABLE ROW MOVEMENT;
참고
REDIS 튜토리얼
레디스는 key
:value
저장방식의 NoSQL 데이터베이스이다.
환경 구성
docker
$ docker run -p 6379:6379 redis # 6379 포트로 redis 서버 구동
redis-cli (by npm)
$ npm i redis-cli
$ rdcli # 127.0.0.1:6379로 connection
기본 저장/조회 명령
- set: 키 값에 데이터 저장, 값이 존재하면 덮어쓴다.
- get: 값 조회, 없으면 nil 반환
- del: 키 삭제
- incr: 값 증가
- expire: 키 유효효기간 설정(단위: 초)
- ttl: 키 유효기간 확인
$ SET server:name "fido"
$ GET server:name
"fido"
$ DEL server:name
(integer) 1
foo 키의 값에 10을 넣고, incr 명령어로 값 1 증가시킨다. 데이터가 정수형인 경우에만 가능하다.
$ set bar 10
$ incr bar => 11
$ get bar => 11
expire 명령어로 데이터의 유효기간을 설정할 수 있다. 아래는 특정 값을 추가한 뒤에 60초 후에 만료되는 키를 생성하는 코드이다.
$ SET foo "60초 후에 삭제된다."
$ expire foo 60
다양한 데이터 타입
리스트
데이터 추가: LPUSH, RPUSH
LPUSH는 리스트의 맨 앞에, RPUSH는 리스트의 맨 끝에 추가한다. lpop, rpop 명령어로 리스트 양쪽의 값을 순차적으로 제거한다.
$ lpush friends "bob"
$ lpush friends "jane"
$ lpush friends "shawn"
$ lrange friends 0 -1 =>
1) shawn
2) jane
3) bob
$ llen friends => 3
셋(set)
셋은 리스트와 유사하지만, 비순차적이고 요소값이 유일하다. sadd, srem으로 값 추가/삭제를 하고,
- sadd: 요소 추가
- srem: 요소값 삭제
- smembers: 요소값 조회
- sismember: 값이 존재하는지 확인
- sunion: 데이터 병합(중복되는 데이터는 없다. 왜? set이니까)
Sorted set
set 자료형과 유사하지만, 순서가 존재한다.
- zadd: 요소 추가
- zrange: 요소 조회
Hash
Hash는 필드:값으로 구성된 자료형이다.
hset user name "john"
: user의 name 필드에 john이라는 값을 추가hgetall user
: user의 모든 데이터 조회hget user name
: user의 name 필드값 조회hmset user name "Mary" age "30" password "s3cret"
: 복수필드 값 추가hdel user age
: age 필드 삭제hincrby user age 1
: age 필드 1만큼 증가
참고자료
- https://goodgid.github.io/Redis/
구체 수학
구체 수학이 뭘까
구체 수학의 'Concrete'는 연속수학(Continuos mathematics)와 이산수학(discrete mathematics)를 섞은 것이다. 수학 공식들을 일단 문제 해결 기법들을 이용해서 통제된 방식으로 조작하는 것이다.
1. 재귀적인 문제들
1.1 하노이의 탑
하노이의 탑은 원반 여덟 개로 된 탑으로 시작한다. 원반들은 세 개의 기둥 중 하나에 큰 것부터 크기순으로 쌓여 있다. 목표는 원반을 하나씩 이동해서 탑 전체를 다른 기둥으로 옮기는 것이다. 단, 작은 원반 위에 큰 원반을 놓아서는 안된다.
문제를 해결하는 방법
1. 작은 사례들을 살펴본다.
- 원반 이동 횟수
- 원반이 1개일 때, 1번
- 원반이 2개일 때, 3번
- 원반이 3개일 때, 7번
2. 적절한 표기법을 도입한다.(명명정복, name and conquer)
1개의 원반을 옮길 때의 총 이동횟수를 (( T ))라고 명명하자. 그러면 아래와 같이 표현할 수 있다.
\[ T_0 = 0 \] \[ T_1 = 1 \] \[ T_2 = 3 \] \[ T_3 = 7 \]
3. 크게 생각해보자.
n개의 원반이 있을 때, 모든 원반을 옮기는 최소 이동횟수는 아래와 같은 수식으로 표현할 수 있다.
\[ T_0 = 0 \] \[ T_n = 2T_{n-1} + 1, n \geq 0 \]
코드
function tower_of_hanoi_recursive(n) {
if(n == 0) {
return 0;
}
return 2 * tower_of_hanoi_recursive(n-1) + 1
}
console.time('하노이의탑-재귀:n=3');
console.log(tower_of_hanoi_recursive(3));
console.timeEnd('하노이의탑-재귀:n=3');
점화식 (recurrence formula or recurrence relation)
- 구성요소
- 경곗값 (boundary value)
- 등식: 일반항의 값을 이전 값들로 서술
- 경곗값이 있어야 점화식이 완성된다.
- 점화식이 있으면 임의의 \( n \)에 대해 \( T_n \)을 계산할 수 있다.
- \( n \)이 크면 점화식으로 값을 계산하는데 시간이 많이 걸린다.
- 점화식의 해가 있다면 \( T_n \)을 빠르게 계산할 수 있다.
- 닫힌 형식(clsoed form) 의 공식
수학적 귀납법 (mathematical induction)
정수 \( n \) 에 관한 어떤 명제가 모든 \( n \geq n_0 \) 에 대해 참임을 증명하는 일반적인 방법
- 기초(basic): 명제를 \( n \)의 가장 작은 값 \( n_0 \)에 대해 증명
- 귀납(induction): 명제가 \( n_0 \)에서 \( n-1 \)까지 증명되었다는 가정 하에 \( n \geq n_0 \)에 대해 명제를 증명
계속해서 작은 사례를 살펴보자.
\[ T_4 = T_3 + 8 = 15 \] \[ T_5 = T_4 + 16 = 31 \] \[ ... \] \[ T_n = 2^n - 1, n > 0 \]
코드
function tower_of_hanoi_closed(n) {
return Math.pow(2, n) -1;
}
console.time('하노이의탑-닫힌 형식:n=3');
console.log(tower_of_hanoi_closed(3));
console.timeEnd('하노이의탑-닫힌 형식:n=3');
1.2. 평면의 선들
칼로 피자를 \( n \) 번 자른다고 할 때, 피자 조각이 최대 몇개나 나올까?
작은 사례 살펴보기
평면의 선의 개수 \( n \)으로 정의되는 영역을 \( L_n \)이라 할 때, \( L_0 = 1 \)이다.
\[ L_0 = 1 \] \[ L_1 = 2 \] \[ L_2 = 4 \] \[ L_3 = 7 \] \[ L_4 = 11 \] \[ L_5 = 16 \]
규모를 확장해보기
\( L_n = 2^n \)이라고 생각할 수 있지만, \( L_3 = 7 \)이므로 공식은 성립하지 않는다. 새로 놓일 직선이 기존에 놓인 다른 모든 직선을 가로질러 간다면, 아래와 같은 공식이 성립된다.
\[ L_0 = 1 \] \[ L_n = L_{n-1} + n, n > 0 \]
\( n \) 번째 선이 분할하는 기존 영역이 \( k \) 개이면, 영역의 수는 \( k \) 만큼 증가한다. \( n \) 번째 선은 최대 \( n-1 \) 개의 선과 최대 \( n-1 \) 개의 점에서 만난다.
이는 기존에 구한 1 ~ 3의 값과도 일치한다. 점화식을 풀어보면 1부터 n까지의 수를 더하는 것을 관찰할 수 있다. 그렇다면 아래와 같이 쓸 수 있다.
\[ L_5 = L_0 + ( 1 + 2 + 3 + 4 + 5 ) \] \[ L_{10} = L_0 + ( 1 + 2 + 3 + ... + 9 + 10 ) \]
좀 더 일반적으료 표현하면, 아래와 같은 공식으로 풀 수 있다.
\[ L_n = L_0 + ( 1 + 2 + 3 + ... + (n-1) + n) \] \[ L_n = L_0 + \frac{n (n + 1)}{2} \] \[ L_n = 1 + \frac{n (n + 1)}{2} \]
닫힌 형식
점화식이 아닌, 명시적인 표준연산방식으로 계산할 수 있는 형식
- 닫힌 형식의 해는 유한하다.
- 닫힌 형식은 간단하다.
- 닫힌 형식의 해가 없는 점화식도 존재한다.
1.3. 요세푸스 문제
1에서 \( n \)까지의 번호가 매겨진 \( n \)명의 사람이 원을 형성하며, 오직 한 사람이 남을 때까지 매 두 번째 사람이 죽는다.
작은 사례
1부터 \( n \)까지의 번호가 매겨진 사람들이 둘러앉아 1부터 시작할 때, 마지막까지 남은 사람의 번호를 \( J_n \)이라고 하자.
\[ J_1 = 1 \] \[ J_2 = 1 \] \[ J_3 = 3 \] \[ J_4 = 1 \] \[ J_5 = 3 \]
확장해보기
작은 사례를 관찰하면 값에 어떤 규칙이 있음을 알 수 있다. 아래의 표를 보자.
\(n\) | \(n+1\) | \(n+2\) | ... | |||||
---|---|---|---|---|---|---|---|---|
1 | 1 | |||||||
2 | 1 | 3 | ||||||
4 | 1 | 3 | 5 | 7 | ||||
8 | 1 | 3 | 5 | 7 | 9 | 11 | 13 | 15 |
2의 지수를 기준으로 \(n\)이 증가할 때마다 2를 더한 값이 반복된다.
위 결과 값에 모두 1을 더해주면 아래와 같이 2의 배수임을 확인할 수 있다.
\(n\) | \(n+1\) | \(n+2\) | ... | |||||
---|---|---|---|---|---|---|---|---|
1 | 2 | |||||||
2 | 2 | 4 | ||||||
4 | 2 | 4 | 6 | 8 | ||||
8 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 |
그리고 2를 나누면 1씩 증가하는 순열이 된다.
\(n\) | \(n+1\) | \(n+2\) | ... | |||||
---|---|---|---|---|---|---|---|---|
1 | 1 | |||||||
2 | 1 | 2 | ||||||
4 | 1 | 2 | 3 | 4 | ||||
8 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
이를 수식으로 표현하면 아래와 같이 나타날 수 있을 것이다.
\[ J_n = 2(n-m) + 1, m = n보다 작거나 같은 2의 지수 \]
\(m\)은 n의 구간마다 값이 달라지기 때문에 아래 코드와 같이 따로 함수를 만들었다.
function max_of_n(n) {
var x = 0;
while(n >> ++x) {
}
return Math.pow(2, --x);
}
function j(n) {
return 2*(n-max_of_n(n)) + 1;
}
console.log(j(1));
console.log(j(2));
console.log(j(3));
console.log(j(4));
console.log(j(5));
console.log(j(6));
console.log(j(7));
console.log(j(8));
console.log(j(9));
console.log(j(16));
console.log(j(15));
패턴을 기반으로 점화식의 해를 찾았는데, 책에선 점화식을 정의하는 부분도 있었다.
오브젝트 - 조영호
- 도서정보: 오브젝트 - 알라딘
패러다임?
- 모델, 패턴, 예를 의미하는 그리스어 파라데이그마(paradeigma)에서 유래
- 과거엔 표준적인 모델을 따르거나 모방하는 상황을 가리키는 제한적인 상황에서 사용했음
- 현대엔 한 시대의 사회 전체가 공유하는 이론이나 방법 등의 체계를 의미함
패러다임 전환?
과거의 패러다임이 새로운 패러다임에 의해 대체됨으로써 방향과 성격이 변하는 것을 의미함.
- 예시: 천동설 → 지동설
프로그래밍에서의 패러다임
프로그래밍 패러다임
이란 용어를 처음 사용한건 로버트 플로이드
특정 시대의 어느 성숙한 개발자 공동체에 의해 수용된 프로그래밍 방법과 문제 해결 방법,일프로그래밍 스타일
-
어떤 프로그래밍 패러다임을 사용하느냐에 따라 해결해야할 문제를 바라보는 방식과 프로그램 작성방법이 달라짐
-
개발자 공동체가 동일한 프로그램링 스타일과 모델을 공유할 수 있게 함으로써 불필요한 부분에 대한 의견 충돌을 방지함
-
프로그래밍 패러다임을 교육시킴으로써 동일한 규칙과 방법을 공유하는 개발자로 성장할 수 있도록 준비시킴
-
프로그래밍 언어와 프로그래밍 패러다임은 분리할 수 없음
- C언어는 절차형 패러다임
- Java는 객체지향 패러다임
- LISP는 함수형 패러다임
프로그래밍 패러다임과 쿤의 패러다임이 다른점
프로그램 패러다임은 상이한 패러다임이 공존할 수 있다.
- 절차형 패러다임과 객체지향 패러다임을 접목한 C++
- 함수형 패러다임과 객체지향 패러다임을 접목한 Scala
→ 이러한 언어를 다중패러다임 언어라고 부름
프로그래밍 패러다임은 비교 가능하다.
- 프로그래밍 패러다임은 기존 개념의 단점을 보완해서 새로운 패러다임이 생겨난다.
- 절차형 패러다임을 보완한 객체지향 패러다임
- 기존 패러다임을 폐기시키는 혁명적인 과정을 거치지 않는다. 오히려 발전적이다.
- 다른 프로그래밍 패러다임을 배우는 것은 도움이 된다.
No silver Bullet.
01 _ 객체, 설계
소프트웨어 모듈의 목적
- 실행 중에 제대로 동작한다.
- 변경을 위해 존재한다.
- 코드를 읽는 사람과 의사소통 할 수 있어야 한다. - 클린 소프트웨어: 애자일 원칙과 패턴, 그리고 실천방법 로버트 마틴
03 설계 개선하기
-
CQRS? 명령/질의 분리
sellTo()
-
코드 변경 용이
결합도와 자율성의 트레이드오프: 이건 결정하기 나름
시그니쳐가 변경된다면/.... 그것은 지옥
- 무생물도 살아있는 것처럼 대한다.
Bag
, TicketOffice
의 사례
미녀와 야수의 '주전자' 처럼
Encapsulation의 의미가 모호한 이유 감추면 다른 시그니쳐가 필요한데 사실 그렇게 코딩하는걸 못봄 모든게 getter & setter
2. 객체지향 프로그래밍
2.1. 영화예매 프로그래밍
→ skip
2.2. 객체지향 프로그래밍을 향해
협력, 객체, 클래스
객체지향 프로그램을 작성할 때 가장 먼저 고려하는 것은 무엇인가? C++, 자비 루비, C#과 같이 클래스 기반의 객체지향 언어에 익숙한 사람이리면 가장 먼저 어떤 클래스(class)가 필요한지 고민할 것이다. 대부분의 사람들은 클래스를 결정한 후에 클래스에 어 떤 속성과 메서드가 펼요한지 고민한다. 안타깝게도 이것은 객체지향의 본질과는 거리가 멀다. 객체지향은 말 그대로 객체를 지향하는 것이다. - p. 40
→ 클래스와 객체의 차이점은 뭘까? 보통 둘을 동일한 의미로 사용하고 있지 않나?
→ Class and Object definition in Java
object
: An object stores its state in fields (variables in some programming languages) and exposes its behavior through methods (functions in some programming languages).class
: A class is the blueprint from which individual objects are created.
객체에 초점을 맞추라
- 클래스보단 어떤 객체가 필요한지 고민하라.
- 클래스는 특정 상태와 행동을 공유하는 객체를 추상화 한 것
- 클래스를 정의하려면 특정 객체의 상태와 행동을 먼저 정의해야 함
- 객체는 기능 구현을 위해 협력하는 공동체의 일원으로 봐야함
- 객체는 다른 객체와 상호작용하는 협력적인 존재임
- 공동체의 일원으로 바라보면 설계가 유연해지고 확장 가능해진다 ??
- 객체의 윤곽이 잡히면 클래스를 구현하라 ??
→ 클래스를 정의하는 것이 객체 정의와 다른 점이 뭘까?
도메인의 구조를 따르는 프로그램 구조
도메인: 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야(지식)
- 객체지향 패러다임은 프로그램의 전 라이프사이클에서 객체 라는 동일한 추상화 기법을 사용할 수 있다.
- 클래스의 이름은 도메인의 이름과 유사하게 명명해야 한다.
- 프로그램의 구조를 이해하고 예상하기 쉽다.
클래스 구현하기
- 인스턴스 가시성: 변수는
private
으로, 메서드는public
으로 - 클래스의 경계 구분짓기
- 객체의 자율성을 보장
- 프로그래머에게 구현의 자유를 제공: 인터페이스는 지키되 구현은 프로그래머의 판단 하에!
자율적인 객체
- 객체는 상태(state) 와 행동(behavior) 을 가지는 복합적인 존재
- 객체가 스스로 판단하고 행동하는 자율적인 존재
- 절차지향 패러다임에선 데이터와 기능을 엮어 프로그램을 구성
- 데이터와 기능은 하나의 단위로 묶이지 않는다.
- 객체와는 다르다. 데이터와 기능은 개별적인 존재
- 객체지향은 데이터와 기능을 객체 라는 단위로 묶는다 → 캡슐화
- 객체지향 패러다임의 언어는 캡슐화에 더 나은 기능을 제공한다. (e.g. Java의 접근 수정자)
- 접근 통제의 이유: 객체를 자율적인 존재로 만들기 위해
- 객체 스스로 상태를 관리하기 위해 외부 간섭을 최소화(외부에서 임의로 상태변경 할 수 없게)
- 캡슐화는 객체를 두 부분으로 나눈다.
- Public Interface: 외부에서 접근 가능한 부분
- Implementation: 내부에서만 접근 가능
- 인터페이스와 구현의 분리 (Seperation of Interface and Implementation)
→ Spring MVC 패턴에 따라 정의된 객체는 저자가 말한 객체라고 볼 수 있을까?
- 상태가 없다. 있으면 안된다.
프로그래머의 자유
- 프로그래머의 역할을 둘로 나눔
- 클래스 작성자: 새로운 데이터 타입을 프로그램에 추가
- 클라이언트 프로그래머에게 필요한 부분만 공개하고 나머지는 감춘다
- 클라이언트 프로그래머: 클래스 작성자가 추가한 데이터를 사용
- 필요한 클래스들을 엮어 애플리케이션을 빠르게 구축
- 클래스 작성자: 새로운 데이터 타입을 프로그램에 추가
- 왜 숨길까?
- 클라이언트 프로그래머에 대한 영향도를 신경쓰지 않아도 된다. (영향이 없으니까)
- 숨겼으니 내부 구현을 마음대로 변경할 수 있다. (퍼블릭 인터페이스만 그대로 동작한다면!)
- → 구현의 은닉 (Implementation Hiding)
→ Java 프로그래머는 Logback의 내부 구현을 알지 못해도 사용할 수 있다. 스펙(인터페이스)대로 동작한다고 기대하고 필요한 인터페이스가 잘 동작하면 된다.
- 설계는 변경을 관리하기 위해 필요하다.
협력하는 객체들의 공동체
- 협력: 기능을 구현하기 위해 객체들 사이에 이뤄지는 상호작용
협력에 관한 짧은 이야기
- 객체는 다른 객체의 공개된 행동(public interface)을 수행하도록 요청 한다.
- 메시지를 전송 (send a message)
- 요청받은 객체는 자율적으로 요청을 처리한 후 응답 한다.
- 메시지를 수신 (receive a message)
- 메서드: 수신한 메시지를 처리하기 위한 객체 자신의 방법
- 메시지와 메서드를 구분하는 것은 중요하다. 왜??
- 메시지와 메서드의 구분에서 다형성 이 출발한다.
- 동적타입 언어에선 시그니쳐가 다른 메서드를 통해서 메시지에 응답할 수 있다.
2.3. 할인요금 구하기
할인요금 계산을 위한 협력 시작하기
calculateMovieFee()
의 이상한 점- 할인 정책(
discountPolicy
)을 결정하는 코드가 없다.
- 할인 정책(
- 상속(Inheritance)과 다형성(Polymorphism), 그리고 추상화(Abstraction)
할인 정책과 할인 조건
- 추상 클래스
DiscountPolicy
는 할인 여부/요금 계산의 전체 프로세스를 정의하지만 요금 계산하는 부분은 추상 메서드인getDiscountAmount()
에게 위임 → Template Method 패턴
할인 정책 구성하기
- 클래스 생성자로 도메인의 제약을 구현
- 올바른 상태를 가진 객체 생성을 보장한다
→ Dependency Injection 얘기는 Out of Scope?
2.4. 상속과 다형성
컴파일 시간 의존성과 실행 시간 의존성
-
Movie
클래스는DiscountPolicy
추상클래스에 의존하고 있다. -
실제 필요한 클래스는 구현체
-
실행(runtime) 시에 구현체 인스턴스에 의존해야 함
-
런타임에 협력가능한 이유?
-
코드 의존성(compile time)과 실행시점 의존성(runtime)은 다를 수 있다. → 객체지향의 특성
- 유연하고 쉽게 재사용 가능
- 코드를 이해하기 어려워진다.
- → 코드를 이해하기 위해 구현체를 헤맨적이 없는지?
-
설계의 trade off
- 설계가 유연해지면 코드 디버깅이 어려워 진다.
- 유연성이 억제되면 재사용성과 확장성이 낮아진다.
차이에 의한 프로그래밍
- 상속: 코드를 재상용하기 위해 널리 사용되는 방법
- 관계 설정으로 부모 클래스의 속성과 행동을 포함시킬 수 있다.
- 부모 클래스의 구현은 공유하되 다른 행동을 정의할 수 있다. (overriding)
→ 차이에 의한 프로그래밍 (programming by difference)
상속과 인터페이스
- 상속의 장점: 부모의 모든 인터페이스를 물려받을 수 있다.
- 사람들은 메서드나 프로퍼티 재사용 때문이라는데?
- 인터페이스는 객체가 이해할 수 있는 메시지의 목록
- 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있다.
- 동일한 타입으로 간주할 수 있다!!!
- 생성자 인자가 추상 클래스임에도 런타임에서 구현체를 전달할 수 있는 이유 (Upcasting)
다형성
- 메시지와 메서드는 다르다
- 요청자는
calculateDiscountAmount
메시지를 보내지만 연결된 객체에 따라 실행하는 메서드는 달라진다.- 추상클래스와 구현 클래스의
calculateDiscountAmount
메서드는 모두 다르다!
- 추상클래스와 구현 클래스의
- 다형성
- 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미한다.
- 컴파일 타임과 런타임의 의존성이 다르다는 것에 기반한다.
- 다형적인 협력에 참여하는 객체들은 모두 같은 메시지를 이해할 수 있어야 한다. → 인터페이스가 동일하다
- 메시지와 메서드를 실행시점에 바인딩 → Lazy Binding, Dynamic Binding
- 클래스 상속이 다형성의 전부가 아니다.
- 구현 상속과 인터페이스 상속
- 구현 상속: 코드 재사용을 위해
- 인터페이스 상속: 다형적인 협력을 위해
- 구현 상속은 변경에 취약한 코드를 생성한다. → 가격변경이력...
인터페이스와 다형성
- 순수하게 인터페이스만 공유하고 싶을 때? Java는
interface
를 사용
2.5. 추상화와 유연성
추상화의 힘
- 인터페이스에 초점을 맞춘다
- 추상화 계층만 보면 요구사항을 높은 수준에서 서술할 수 있다.
- 설계가 유연해진다.
유연한 설계
- 예외케이스를 최소화하고 일관성을 유지할 수 있는 방법을 고려하라
- 유연성이 필요한 곳에 추상화를사용하라.
추상 클래스와 인터페이스 트레이드오프
- 설계의 트레이드 오프를 항상 염두해야 한다.
코드의 재사용
- 상속과 합성
상속
- 코드를 재사용할 수 있다.
- 캡슐화를 위반한다.
- 설계 유연성이 떨어진다. (변경이 어려워진다.)
합성
- 인터페이스를 통해 약하게 결합된다.
질문거리
- 클래스와 객체의 차이점은 뭘까? 보통 둘을 동일한 의미로 사용하고 있지 않나?
- 클래스를 정의하는 것이 객체 정의와 다른 점이 뭘까?
- Spring MVC 패턴으로 정의된 인스턴스는 저자가 말한 객체라고 볼 수 있을까?
- 내부 상태가 없다. (있으면 안된다)
3. 역할, 책임, 협력
- 객체지향 패러다임의 핵심
- 역할(role)
- 책임(responsibility)
- 협력(collaboration)
- 객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것
- 협력을 구성하기 위해 적절한 객체를 찾고 적절한 책임을 할당
- 클래스와 상속은 구현 메커니즘일뿐
- 구현에 치우치면 변경이 어렵고 유연하지 못한 코드를 낳는다
3.1. 협력
영화예매시스템 돌아보기
- 객체지향 애플리케이션의 제어 흐름은 다양한 객체들에 분배되어 있음
- 협력: 객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용
- 책임: 객체가 협력에 참여하기 위해 수행하는 로직
- 역할: 객체들이 협력 안에서 수행하는 책임들의 모음
협력
- 객체는 사회적인 존재
- 협력은 한 객체가 다른 객체에게 도움을 요청할 때 시작된다
- 메시지 전송(send a message)은 객체간의 유일한 커뮤니케이션 수단
- 객체는 다른 객체의 세부 구현에 직접 접근할 수 없다
- 메시지 수신 객체는 메서드를 실행해 요청에 응답 (스스로 메시지를 처리)
- 캡슐화: 객체를 자율적으로 만드는 기본적인 방법
- 내 상태는 내가 알아서 한다
- 변경하기 쉬워진다
협력이 설계를 위한 문맥을 결정한다
- 애플리케이션에 어떤 객체가 필요하다면 그 이유는 단 하나여야 한다. (SRP)
- 객체가 협력에 참여하고 있기 때문
- 협력에 필요한 적절한 행동(상태)를 보유했기 때문
- 협력은 객체가 필요한 이유와 객체의 행동 동기를 제공
- 협력은 객체의 행동을 결정, 행동은 객체의 상태를 결정
- 자율적인 객체는 행동에 필요한 상태를 가지고 있어야 한다.
→ 데이터 관점에서 설계하면 자율적인 객체를 설계하기 힘든 것 같다.
- 협력은 객체 설계에 필요한 문맥(context)을 제공
3.2. 책임
책임이란 무엇인가
- 협력이 갖춰지면 협력에 필요한 행동을 수행할 객체를 찾아야 한다.
- 책임: 협력하는 객체의 행동
- 객체에 의해 정의되는 응집도 있는 행위의 집합
- 무엇을 알고있는가? 무엇을 할 수 있는가?
- doing
- 객체 생성, 계산 수행 (constructor, method)
- 다른 객체의 행동을 시작 (send a message?)
- 다른 객체의 활동을 제어, 조절
- knowing
- 사적인 정보에 대해 아는 것 (state?)
- 관련된 객체에 관해 아는 것 (dependency?)
- 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것 (state?)
- 영화시스템의 책임 예
- Screening: 영화 예매 (doing), 상영할 영화를 기억 (knowing)
- Movie: 요금 계산 (doing), 가격과 할인정책을 기억 (knowing)
- 아는 것과 하는 것은 밀접하게 연관되어 있음
- 책임을 적절하게 할당하는 것은 객체지향 설계의 핵심!!
→ CRC 카드의 첫 번째 C도 Class였으니 워드 커닝햄도 클래스 기반 설계에 초점을 맞춘게 아닐까? 적어도 처음엔
책임 할당
- 자율적인 객체를 만드려면 필요한 정보를 가장 잘 알고 있는 전문가에게 책임을 할당해야 한다. → 정보 전문가 패턴(Information Expert pattern)
- 책임을 할당하는 방법
- 시스템이 사용자에게 제공하는 기능을 하나의 책임으로 바라본다
- 시스템의 책임을 완료하는데 필요한 더 작은 책임을 찾는다. (책임 쪼개기)
- 객체에게 책임을 할당
- 2~3 반복
책임 주도 설계
- 책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법
- 구현이 아닌 책임에 집중할 수 있다
메시지가 객체를 결정한다
- 메시지를 식별한 후에 메시지를 처리할 객체를 선택한다
- 객체가 메시지를 선택하지 않는다
- 객체는 최소한의 인터페이스만 가질 수 있다.
- 추상적인 인터페이스를 가질 수 있다.
→ 메시지 처리라는 필요에 의해 설계하게 되니 자연스럽게 SRP, ISP를 지킬수 있겠다.
→ 내가 경험했던 데이터 정의 >> 구현은 이렇게 생각하기가 어렵게 느껴진다.
행동이 상태를 결정한다
- 객체는 협력에 참여하기 위해 존재한다
- 객체는 다른 객체에게 제공하는 행동이 중요한다
→ getter를 남발하지 말고 직접 처리하자
- 데이터 주도 설계를 지양하자
- 협력이라는 문맥 안에서 객체를 생각해야 한다
- 협력에 초점을 맞춰야 응집도가 높고 결합도가 낮은 객체를 설계할 수 있다
- 상태는 행동에 필요한 재료일뿐
- 협력이 객체의 행동을 결정, 행동이 상태를 결정, 행동이 객체의 책임
2.3. 역할
역할과 협력
- 객체의 목적? 협력 안에서 객체가 맡게 되는 책임의 집합 → 역할
- 객체의 목적이 곧 역할
- 적절한 역할을 찾고
- 역할을 수행할 객체를 선택
유연하고 재사용 가능한 협력
- 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있다
→ 역할은 인터페이스??
역할은 다른 것으로 교체할 수 있는 책임의 집합이다.
- 역할은 구체적인 객체를 포괄하는 추상화
- 역할을 이용하면 불필요한 중복 코드를 제거할 수 있다. (추상화의 이점)
- 새로운 할인정책을 위해 협력을 추가할 필요가 없어짐 (이미 추상화했기 때문에)
객체 대 역할
- 역할은 객체가 참여할 수 있는 일종의 슬롯
- 한 종류의 객체만 존재하는데 역할(추상클래스/인터페이스)이 필요할까?
- 그럴 필요없다
- 어떤게 역할이고 객체인지 뚜렷하게 드러나지 않음
- 특히 설계 초반에는 더더욱
- 설계 초반에는 책임과 협력을 탐색하는 것이 중요한 목표
- 책임과 협력을 정제해가면서 필요한 순간에 객체로부터 역할을 분리하자
역할과 추상화
- 역할은 객체의 추상화
- 세부 사항에 억눌리지 않고 상위 수준의 정책을 쉽고 간단하게 표현할 수 있다
- 설계를 유연하게 만든다
- 역할은 슬롯
- 프레임워크나 디자인 패턴의 핵심 요소가 역할!!
배우와 배역
- 배우(객체) - 배역(역할)
- 역할은 협력 안에서의 일시적인 개념
- 객체는 다양한 역할을 가질 수 있다
기록
2019년 기록
책
영화, 드라마, 그외
- 스파이더맨 - 뉴 유니버스: 약간 정신없긴 하지만 이만큼 재밌을 수 있을까?
- 루터
2019년 16주차 TIL
2019/04/15
도커 볼륨과 세션에 대해 알아보았다.
TYPE: 커맨드 프롬프트 명렁어
윈도우즈 커맨드 프롬프트의 type 명령어는 리눅스 cat 명령어와 유사한 동작을 수행한다.
> type some.txt
## 텍스트 파일 출력
2019/04/16
AWS Summit Seoul 2019
타 게임사의 경험으로 본 AWS 핵심 모범 사례 한방에 배우기
- Tips for Cost Optimization
- RI 구매방법 및 고려사항
- RI: 예약 인스턴스
- AWS에서 RI 최적 플랜 제공
- AWS 비용 탐색기를 활용 (Cost Explorer)
- RI 구매방법 및 고려사항
- Tips for Support case
- 호주 현지국인 엔지니어 대응 호주 현지시간 9-5
- 인스턴스 강제 재시작 사례
- 원인: 노후화 장비 정비 등의 이유
- 인스턴스 다중화 ?@??!! 이건 하나마나한 소리가 아닌지...?
AWS System Manager: Parameter Store를 사용한 AWS 구성 데이터 관리 기법
-
정창훈, 당근마켓
-
Parameter store?
- AWS System manager - parameter store
- EC2 서비스에서도 바로 접근 가능
-
System manager
- 특징
- Resource groups:
- Insight
- Actions
- Shared REsources
- 특징
-
Parameter store
- 비밀번호나 구성 데이터 관리
- 중앙 집중식 저장/관리
- 버전관리:
aws ssm put-parameter ~~ --overwrite
- 암복호화:
--decryption
스폰서 발표 세션 | e커머스 통합운영 자동화 사례 및 보안강화 방안, 삼성SDS
- SRE?
서버리스 아키텍처 패턴 및 로그 처리를 위한 파이프라인 구축기
- 패턴2: 오퍼레이션 자동화
- 람다 리눅스 API
- 다양한 런타임 환경 지원: 심지어 코볼도 지원함
- 다양한 AWS 서비스와 연계
- 코드: 코드커밋, ...
- DB: Dynamo, ...
- ///////////////////////////
- 패턴3: 데이터레이크
- S3가 데이터레이크의 중앙저장소 역할을 함
- 패턴4: IOT 데이터 처리 패턴
AWS 클라우드 핵심 서비스로 클라우드 기반 아키텍처 빠르게 구성하기
- EC2 인스턴스 선택
- 사용유형을 고려한 인스턴스 선택
- 프로세서 및 아키텍쳐 선택 가능
- ELB
- ALB(HTTP/S), NLB(TCP), Classic(Deprecated)
- Storage
- Instance Store: 휘발성
- EBS: Network로 마운트 (비용발생)
- 동일 가용영역 내 인스턴스에 Attach/detach
- 하나의 EC2에만 attach 가능
- 암호화
- 증분백업 -> S3
- Block/File/Object type
- S3: REST 통신 사용
- 리소스 당 5TB 제한
- 버킷/리소스(파일)단위로 접근제어 설정 가능
- Athenaa를 사용해서 대화형 쿼리 서비스 사용 가능 -> 파일 업로드 후 데이터 분석!
- 비용절감도구: S3 Intelligent Tiering
- 라이프사이클 관리정책을 적용하면 알아서 리소스의 저장소 유형을 변경
- SFTP 서비스 제공
- EFS: 매니지드 NAS 서비스
- On-premise 서버에서도 사용 가능
- 사용량에 따라 과금 / 초기 용량 산정이 없음
- S3: REST 통신 사용
- VPC: 클라우드 VPN 서비스
- 시큐리티 그룹 체이닝: 특정 시큐리티 그룹을 통과한 요청에 대해 접근권한 설정 가능 ELB~WEB~API~DB
- DB
- RDS
- Read Replica 구성 가능
- 백업관리 -> S3
- RDS
- Cloud Front
IE 10 이하 환경에서의 테스트 환경 구성
MicroSoft는 IE 10 이하의 환경을 제공하는 Virtual Machine 제공한다. IE 11의 에뮬레이션 기능은 원래 버전의 렌더링과 차이가 날 수 있기때문에 꼭 각 버전별 브라우져에서 확인해야 한다.
https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/
미분류
VIM 인코딩 변환
++enc
VIM에서 명령모드 진입 후 e ++enc={인코딩 타입}
을 입력
utf-8
방식으로 리로딩하는 경우
:e ++enc=utf-8
euc-kr
방식으로 리로딩하는 경우
:e ++enc=euckr
MTU, Maximum Transmission Unit, 최대전송단위
네트워크에서 프로토콜이 최대로 전송할 수 있는 데이터 단위를 말한다. MTU가 높을수록 효율적으로 커뮤니케이션 할 수 있다. 헤더같은 프로토콜 오버헤드는 동일하지만 더 많은 데이터를 전송할 수 있기 때문이다.
mtu 조절 명령어
Windows 계열
> netsh interface ipv4 show interfaces
> netsh interface ipv4 set subinterface "네트워크장비인덱스" mtu=200 store=persistent
MacOS
> ifconfig # 네트워크 인터페이스 탐색
> ifconfig 장비명 mtu 200
참고자료
VIM Cheat Sheet
동작
이동
- 기본이동(←↑↓→):
h, j, k, l
- 문장
- 시작:
^,0
- 끝:
$, shift + 0
- 시작:
- 단어
- 다음 단어:
w
- 다음 단어 끝:
e
- 이전 단어:
b
- 다음 단어:
- 페이지
- 다음 페이지:
ctrl + f
- 이전 페이지:
ctrl + b
- 다음 페이지(1/2):
ctrl + d
- 이전 페이지(1/2):
ctrl + u
- 다음 페이지:
편집
치환
561~562 라인의 th
로 시작하는 문자열을 7h
로 시작하는 문자열로 치환한다.
- 라인 내에서 치환:
s/old/new/g
- 범위 내의 일치하는 조건 모두 치환:
#,#s/old/new/g
- 전체 파일범위 치환:
%s/old/new/g
- 전체 파일범위 치환여부 프롬프트 제공:
%s/old/new/gc
예제 th* 문자를 7h* 문자로 변환
- 명령어:
561,562s/\(th\(\w\+\)\)/7h\2/g
- 변경 전 문장
561 2. Type :s/thee/the <ENTER> . Note that this command only changes the
562 first occurrence of "thee" in the line.
- 변경 후 문장
561 2. Type :s/7hee/7he <ENTER> . Note 7hat 7his command only changes 7he
562 first occurrence of "7hee" in 7he line.
윈도우
- 새로운 윈도우 생성:
split
(수평) /vsplit
(수직) - 윈도우 간 이동:
C-w
+h, j, k, l
(기본이동)
버퍼
- 파일 이동:
bnext
,bprev
일렉트론 프로젝트를 electron-packager를 사용해서 패키징하기
아래 명령은 일렉트론 기반의 레디스 클라이언트인 메디스를 패키징하는 명령어이다.
$ electron-packager . medis --platform=darwin --overwrite --out dist2/ --icon resources/icns/MyIcon.icns
잡다한 macos 관련 내용
시동음 살리기
$ sudo nvram StartupMute=%00 # 시동음 켜기
$ sudo nvram StartupMute=%01 # 시동음 끄기
출처: 트위터
2016년 이후에 출시된 맥에서 예전의 시동음을 되살리는 법:
— 김정현 (@gluebyte) February 27, 2020
1. 터미널을 열고 다음을 입력: sudo nvram StartupMute=%00
2. 재시동
3. 감격의 😭😭😭
다시 끌 때는 %00 대신 %01https://t.co/rKK6YLrjWn