1. RDS 연결

 1) 데이터베이스 생성

  • AWS RDS를 검색하여 이동한다
  • 좌측의 데이터베이스를 클릭한다
  • 우측의 '데이터베이스 생성'을 클릭한다

  • 데이터베이스 생성방식은 표준으로 한다
  • 엔진 옵션은 사용하고 있는 엔진을 선택한다
    - 작성자는 MySQL을 선택하였다
  • 엔진의 버젼을 컴퓨터에 설치되어 있는 버젼을 확인 후 설정한다

  • 템플릿은 '프리티어'를 선택한다

  • DB 인스턴스 식별자는 사용자가 임의로 설정해도 된다
  • 마스터 사용자 이름은 사용자가 임의로 설정해도 된다
  • 마스터 암호는 자동생성할 수도 있으며 직접 설정해도 된다

  • DB인스턴스 클래스는 '버스터블 클래스'로 자동 선택된다
  • db.t2를 선택한다(필요에 따라 변경할 수 있다)
  • 스토리지는 범용 SSD(gp2)를 선택한다(필요에 따라 변경 가능하다)
  • 할당 스토리지는 20GiB이다
  • 스토리지 자동조정은 필요에 따라 할 수 있지만 요금이 부과되는 점을 상기한다

  • 컴퓨팅 리소스는 연결하지 않는다(고정으로 하고 싶다면 연결을 설정해도 된다)
    - 연결을 설정하면 VPC등의 설정이 자동으로 제공된다
  • 퍼블릭 엑서스는 '예'로 선택한다
  • VPC보안그룹은 기본으로 선택한다
  • 데이터베이스 포트는 기본 '3306'으로 설정한다

  • 추가구성을 연다
  • 초기 데이터베이스 이름을 임의로 설정한다(지정하지 않으면 데이터베이스가 생성되지 않는다)

 

  • 나머지 설정은 기본 설정된 상태로 두고 데이터베이스 생성을 클릭한다

  • 데이터베이스 생성에 약간의 시간이 소요된다
    - '생성 중' 문구가 '사용 가능'으로 변경될 때까지 기다린다

  • 생성된 데이터베이스 이름을 클릭한다
  • 연결&보안에서 엔드포인트를 확인하고 복사한다

  • powershell을 연다
  • MySQL 을 통해서 DB 인스턴스에 접속해 본다
    - 'mysql -u [마스터 이름] --host [엔드 포인트 주소] -P 13306(포트 번호) -p' 명령어를 입력한다
'mysql -u [마스터 이름] --host [엔드 포인트 주소] -P 3306(포트 번호) -p'

[작성 예시]
mysql -u main_002 --host main-002-db.cnudh2fbavc7.ap-northeast-2.rds.amazonaws.com -P 3306 -p
  • 비밀번호를 입력한다
  • mysql> 이 출력되면 연결된 것이다 

1. 파이프라인 구축(환경 설정)

  1) EC2 인스턴스에 태그 생성

  • EC2 대시보드로 이동한다
  • 인스턴스로 이동한다

 

  •  파이프라인 구축 단계에서 인스턴스를 잘 식별하기 위하여 태그를 추가한다
    - 인스턴스를 선택한다
    - 작업 → 인스턴스 설정 → 태그 관리 순서로 클릭해 이동한다

  • 새로운 태그 추가를 클릭한다
  • 태그의 키 와 값은 자유롭게 작성한다
  • 저장 버튼을 눌러 태그를 생성한다
    - 현재는 권한이 없어서 생성이 안되지만 나중에 개인 코드로 해 본다

 


 2)  IAM 서비스를 이용해서 EC2 인스턴스에 역할 부여

  • 역할(Role)은 AWS의 개체(서비스, 사용자 등)가 다른 서비스에 접근하게 할 수 있도록 해 준다
    - EC2 인스턴스에 역할을 부여하면 다른 AWS 서비스를 호출할 수 있는 권한을 가진다
  • 인스턴스를 선택한다
  • 작업 → 보안 → IAM 역할 수정 순서대로 클릭해 이동한다

  • IAM 역할을 생성해둔 것이 없으면 새로 생성한다
    - '새 IAM 역할 생성'을 클릭해 AWS IAM 서비스로 이동한다

  • AWS 서비스, EC2를 선택 후 '다음' 버튼을 클릭한다
  • '다음'을 클릭한다

  • 검색창에 권한을 검색 후  선택한다
    - S3를 입력하여 AmazonS3FullAccess를 선택한다
    - EC2Role을 입력하여 AmazonEC2RoleforAWSCodeDeploy를 선택한다
    - SSM을 입력하여 AmazonSSMFullAccess를 선택한다
  • '다음'을 클릭한다

  • 역할에 이름을 입력한다

  • 태그 추가를 클릭한다
  • 키에 "Name"을, 값(선택 사항)에 "EC2forCodeDeploy"를 입력한다
  • '다음: 검토'를 클릭한다

  • '역할 만들기'를 클릭한다
  • 역할이 생성된다

 

  • IAM 메인 화면으로 이동한다
  • 역활 -> EC2를 검색한다
  • EC2forCodeDeploy 자체를 선택한다

  • 신뢰 관계를 편집한다
    - 해당 역할을 취할 수 있는 서비스나 사용자를 명시한다
    - 역활에서 생성한 access 할 수 있는 서비스를 신뢰 관계에서 명시함으로써 역할이 명확하게 완성된는 것이다

  • 처음 편집 화면으로 들어가면 "Service"의 값으로 "ec2.amazonaws.com"만 할당되어 있다
    - 화면과 같이 대괄호를 작성하고 내부에 "codedeploy.ap-northeast-2.amazonaws.com" 값을 추가한다
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "ec2.amazonaws.com",
                    "codedeploy.ap-northeast-2.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
  • EC2 인스턴스 화면으로 돌아와서 IAM에 생성한 역할을 저장한다
    - 이제 EC2는 S3, CodeDeploy에 접근할 수 있다

 


  • EC2 인스턴스가 적절한 보안 그룹을 갖고 있는지 확인한다
  • EC2 대시 보드에서 인스턴스로 이동한다
  • 보안 탭을 클릭한다
  • 보안 그룹을 클릭해 이동한다

 

  • 보안그룹의 인바운드 규칙에 8080과 443 포트가 포함되어 있는지 확인한다
    - 포함되어있지 않다면, [ 인바운드 규칙 편집 ] 버튼을 클릭해 수정 화면으로 이동하여 추가한다
    - 8080번 포트는 서버 배포를 위해 필요한다
    - 443 포트는 CodeDeploy-Agent의 정상적인 작동을 위해 필요하다

  • 사용자 지정 TCP에
    - 포트범위를 8080으로
    - 소스를 위치 무관으로 추가한다
  • HTTPS 유형에서
    - 소스를 위치 무관으로 추가한다
  • '규칙 저장' 을 클릭한다

  • 저장 후 22, 8080, 443 포트가 포함되어 있는지 확인한다
  • EC2 인스턴스의 세팅이 완료되었다

 

 


2. 부여받은 IAM User 권한과 리소스를 활용하여 파이프라인 구축

1) 배포 자동화를 위한 EC2 인스턴스의 역할(IAM Role) 확인

  • EC2 인스턴스에는 태그와 IAM Role이 설정되어 있다

  • 역할이 가지고 있는 권한은 아래와 같다
    - EC2 인스턴스의 Name은 해당 인스턴스에 Name을 키로 갖는 태그 값과 동일하다
    - 별도의 태그를 추가하지 않고, 이후에도 이 인스턴스의 Name 태그 값을 이용해 검색한다

  • EC2 인스턴스에 연결되어있는 IAM 역할은 아래 이미지와 같은 권한을 가지고 있다
    - 추가되어있는 권한과 신뢰관계가 'EC2 인스턴스에 태그와 역할 부여'에서 진행한 내용과 동일한지 확인한다

  • 발급받은 EC2 인스턴스를 통해 연결되어있는 보안 그룹을 클릭해 이동한다
  • 모든 인스턴스에 동일한 보안그룹이 적용되어 있다
    - 보안그룹 인바운드 규칙이 'EC2 인스턴스에 태그와 역할 부여'에서 진행한 내용과 동일한지 확인한다

 

 


3. EC2를 활용한 파이프라인 구축

 1) Local 환경 설정

  • 구축에 활용할 로컬 환경의 'be-sprint-deployment/DeployServer' 경로에 appspec.yml 파일을 추가한다
    - appspec.yml은 배포 자동화를 도와주는 CodeDeploy-Agent가 인식하는 파일이다
version: 0.0
os: linux

files:
  - source: /
    destination: /home/ubuntu/build

hooks:
  BeforeInstall:
    - location: server_clear.sh
      timeout: 3000
      runas: root
  AfterInstall:
    - location: initialize.sh
      timeout: 3000
      runas: root
  ApplicationStart:
    - location: server_start.sh
      timeout: 3000
      runas: root
  ApplicationStop:
    - location: server_stop.sh
      timeout: 3000
      runas: root

  • 로컬 환경의 'be-sprint-deployment/DeployServer' 경로에 buildspec.yml 파일을 추가한다
    - buildspec.yml은 배포 자동화에서 빌드를 담당하는 CodeBuild-Agent가 인식하는 파일이다
version: 0.2

phases:
  install:
    runtime-versions:
      java: corretto11
  build:
    commands:
      - echo Build Starting on `date`
      - cd DeployServer
      - chmod +x ./gradlew
      - ./gradlew build
  post_build:
    commands:
      - echo $(basename ./DeployServer/build/libs/*.jar)
artifacts:
  files:
    - DeployServer/build/libs/*.jar
    - DeployServer/scripts/**
    - DeployServer/appspec.yml
  discard-paths: yes

 
  • be-sprint-deployment > DeployServer 아래에 scripts 디렉토리를 생성한다
    - 4개의 파일을 scripts 아래에 생성한다
     :  각 파일은 appspec.yml 파일이 구성하고 있는 배포 수명 주기에 따라서 실행될 예정이다

    - initialize.sh 파일을 생성한다
#!/usr/bin/env bash
chmod +x /home/ubuntu/build/**

 

       - server_clear.sh 파일을 생성한다

#!/usr/bin/env bash
rm -rf /home/ubuntu/build

 

      - server_start.sh 파일을 생성한다

#!/usr/bin/env bash
cd /home/ubuntu/build
sudo nohup java -jar DeployServer-0.0.1-SNAPSHOT.jar > /dev/null 2> /dev/null < /dev/null &

 

      - server_stop.sh 파일을 생성한다

#!/usr/bin/env bash
sudo pkill -f 'java -jar'

 


 2) AWS CodeDeploy 설정

  • AWS CodeDeploy 대시보드로 이동한다
    - 애플리케이션으로 이동한다 
  • 애플리케이션 생성 버튼을 클릭한다

 

  • 애플리케이션의 이름을 규칙에 맞게 입력한다
  • 컴퓨팅 플랫폼을 'EC2/온프레미스'로 선택한다
  • 애플리케이션 생성 버튼을 클릭한다

 

  • 애플리케이션이 생성된다
    - 애플리케이션은 배포그룹을 생성해야 한다
    - 애플리케이션의 배포 그룹 > 배포 그룹 생성 버튼을 클릭한다 

 

  • 배포 그룹의 이름을 규칙에 맞게 입력한다
  • 서비스 역할을 클릭하여 EC2 인스턴스에 연결되어있는 'BE_EC2RoleForSSM'을 선택한다 

 

  • 환경 구성은 'Amazon EC2 인스턴스'를 선택한다
  • 태그 그룹에는 EC2 인스턴스의 이름인 Name 태그 키와 값을 선택한다

 

  • 로드 밸런서에서 '로드 밸런싱 활성화' 체크를 해제한다
  • 배포 그룹 생성 버튼을 클릭한다
  • AWS CodeDeploy 세팅이 완료되었다

 


 3) AWS CodePipeline을 이용한 서버 배포 자동화 파이프라인 구축

  • CodePipeline 대시보드로 이동한다
  • 파이프라인 생성 버튼을 클릭한다
  • 파이프라인 이름을 규칙에 맞게 입력한다
  • 다음 버튼을 클릭한다
  • 소스 스테이지를 추가한다
    - 소스 코드로 사용할 'be-sprint-pratice-deploy' 레포지토리가 GitHub에 저장되어 있다
    - 소스 스테이지에서 GitHub를 사용할 수 있으므로, GitHub(버전 2)를 소스 공급자로 선택한다
  • 추가 옵션이 나온다
  • GitHub에 연결 버튼을 클릭한다
  • 별도의 팝업창이 나타난다
  • 연결 이름을 임의로 입력한다
  • GitHub에 연결 버튼을 클릭한다
  • 'Amazon Web Services 의 GitHub용 AWS 커넥터는 다음 권한을 요청합니다.'의 팝업창이 나타난다
    - 상황에 맞게 선택한다
  • 승인을 하면 GiHub 연결 창이 나타난다
  • 새 앱 설치 버튼을 클릭한다
  • 자신의 GitHub 계정을 클릭한다
  • 자신의 계정으로 로그인 된 GitHub 창이 나타난다
  • GitHub 화면에서 'Only select repositories(저장소만 선택)'를 선택한다
  • 소스 코드로 이용할 'be-sprint-deployment' 리포지토리를 선택하고 Save(설치) 버튼을 클릭합니다.
  • 자신 계정의 암호를 입력한다
  • 링크가 생성되었다
  • 연결 버튼을 클릭한다
 
  • 리포지토리 이름을 조금 전 연결한 'be-sprint-deployment'로 지정한다
  • 브랜치 이름은 main로 지정한다
  • 출력 아티팩트 형식은 'CodePipeline 기본값'으로 지정한다
  • 다음 버튼을 클릭한다
 
  • 빌드 스테이지 추가 창으로 이동된다
  • 빌드 공급자에서 AWS CodeBuild를 클릭한다
    - 추가 옵션이 아래에 나타난다
  • 프로젝트 생성 버튼을 클릭한다
 
 
 
  • 빌드 프로젝트 생성 창이 생긴다
  • 프로젝트 이름을 규칙에 맞게 입력한다
  • 운영체제는 Amazon Linux 2를 선택한다
  • 런타임은 Standart를 선택한다
  • 이미지는 aws/codebuild/amazonlinux2-x86_64-standart:3.0을 선택한다
 
  • Buildspec 이름에 "DeployServer/buildspec.yml"을 작성한다
  • CodePipeline으로 계속 버튼을 클릭한다
 
 
  • 'CodeBuild에서 be-pair-18을(를) 생성했습니다.'는 문구가 표시된다
  • 다음 버튼을 클릭한다
  • 배포 스테이지 화면으로 이동한다
  • 배포 공급자는 'AWS CodeDeploy'를 선택한다
  • 리전은 '아시아 태평양(서울)'을 선택한다
  • 애플리케이션 이름은 생성해 둔 애플리케이션의 이름을 선택한다
  • 배포 그룹은 생성해 둔 배포 그룹으로 선택한다
  • 모든 단계의 설정을 잘 확인한다
  • 오른쪽 하단의 파이프라인 생성 버튼을 클릭한다
 
  • 파이프라인이 생성되었다
  • 파이프라인 생성과 동시에 소스 코드의 배포가 자동으로 실행된다
 
 
 
  • 구축한 파이프라인 중 deploy 스테이지에서 실패가 발생하는 경우에는 관련 로그를 확인한다
  • CodeDeploy-Agent는 파이프라인 실행 때마다 로그를 해당 EC2 instance에 저장한다
  • 터미널에 'cd /opt/codedeploy-agent/deployment-root/deployment-logs' 명령어를 입력한다
    - 로그 파일이 저장된 경로로 이동한다
  • ls 명령어를 입력하여 어떤 파일이 존재하고 있는지 확인한다
  • codedeploy-agent-deployments.log 라는 파일이 존재하는 것을 확인할 수 있다
  • nano를 이용해 해당 파일을 열어본다
  • lifecycle을 돌면서 sh 파일을 실행시킨 로그를 확인할 수 있다
    - stderr는 standard error 이며
    - stdout은 standard output 이다
  • 프로세스가 실행중인지 확인하려면, sudo lsof -i :(실행한 포트번호) 명령어로 확인이 가능하다

 

※ 참조 링크


- linux의 standard stream : https://www.putorius.net/linux-io-file-descriptors-and-redirection.html 

 

Linux Fundamentals - I/O, Standard Streams, and Redirection.

An introduction to Linux IO, standard streams, file descriptors, and redirection. Learn the basic operators and how to control stdin, stdout, and stderr.

www.putorius.net

      - Bash의 표준 스트림 : https://www.delftstack.com/howto/linux/standard-streams-in-bash/  

 

Standard Streams in Bash

This tutorial explains the standard streams and redirecting output and input to and from a file, respectively.

www.delftstack.com

  • 프로세스가 실행중인지 확인하려면, sudo lsof -i :(실행한 포트번호) 명령어로 확인이 가능합니다.
 
 

 

'클라우드' 카테고리의 다른 글

Cloud - AWS(4) - RDS  (0) 2022.09.27
Cloud - AWS(2) - EC2 인스턴스 생성  (0) 2022.09.27
Cloud - AWS(1) - 계정 생성  (0) 2022.09.27
Cloud - 배포자동화(Automated Deployment)  (0) 2022.08.07
Cloud - 배포 컨테이너 - Docker Image  (0) 2022.08.07

1. AWS Management Console에 IAM 사용자 또는 루트 사용자로 로그인

 1) 루트 사용자 로그인

  • AWS 계정을 생성하는 데 사용되는 이메일 주소와 루트 사용자의 암호를 준비한다
  • https://console.aws.amazon.com/을 오픈한다
  • 처음 오픈한 경우 기본 로그인 페이지가 나타난다
  • 루트 사용자(Root user)를 선택한다
    - 계정과 연결된 이메일 주소를 입력한 후 다음을 선택한다
  •  비밀번호를 입력하고 다음을 클릭한다

  • 보안문자를 입력하고 제출을 클릭한다

  • 로그인 화면이 출력된다

 

2. EC2 인스턴스 생성

 

  • Elastic Compute Cloud(EC2)는 컴퓨터를 빌려주는 임대 서비스라고 할 수 있다
    - 원격으로 컴퓨터를 빌려 사용하고 필요가 없어지면 언제든 삭제할 수 있다
  • 로그인 화면에서 가상머신(EC2) 시작을 클릭한다
  • 인스턴스 이름과 애플리케이션을 선택한다

  • 인스턴스 유형을 선택한다

  • 새 키 페어 생성을 클릭하면 팝업창이 나타난다
  • 이름과 유형을 선택 후 생성 버튼을 클릭한다

  • 보안그룹을 선택한다

  • 스토리지를 설정한다

  • 볼륨 유형별 요금 안내
    - 진행하는 프로젝트에 맞는 요금을 선택한다

 

AWS EBS(Elastic Block Storage) 비용 최적화 | Popit

[원본글] https://ericygkim.wordpress.com/2019/07/11/aws-ebselastic-block-storage-의-비용-최적화/ 애플리케이션 개발 후 AWS에 애플리케이션을 설치할 때 성능, 가용성, 확장성, 비용, 서비스 특성등 많은 요구사항

www.popit.kr

  • 최종 설정 정보를 확인한다
  • 인스턴스를 시작한다

  • 약간의 시간이 경과되면 성공 화면이 출력된다
  • 로그 시작 버튼을 필히 클릭한다

3. EC2 인스턴스 시작

  • 검색창에서 EC2를 검색한다
  • EC2 대시보드를 열고 생성한 리소스에 대해 살펴본다

  • 인스턴스를 클릭하면 생성된 인스턴스에 대한 보안내용이 출력된다

 

4. EC2 인스턴스 연결

  • EC2인스턴스를 선택한다
  • 오른쪽 상단의 연결을 선택하여 이동한다

  • SSH클라이언트를 클릭한다
  • .pem 파일이 있는 경로를 확인한다
  • window powershell을 열고 아래 명령문과 같은 형식으로 작성 후 실행한다
ssh -i d:\main_project\main_002.pem ubuntu@ec2-3-36-5-78.ap-northeast-2.compute.amazonaws.com
ssh -i+      경로 위치             + 퍼블릭 DNS

  • 사용자@IP주소:~$ 표시가 나오면 EC2 서버와 연결이 성공한 것이다

1. AWS 계정 생성

 1) 홈페이지에서 오른쪽 상단의 AWS 계정 생성을 버튼을 클릭한다

 2) 이메일, 계정 이름을 작성하고 확인을 클릭한다

 3) 본인 이메일로 전송된 확인 번호를 입력 후 다음으로 넘어간다

 4) 암호 생성

  • 대문자, 소문자, 숫자, 특수문자 중 3개 이상을 포함한 8자 이상의 암호를 입력한다
  • 구글에서 제공하는 암호가 팝업으로 뜨는데 그걸 사용해도 된다
    - 구글 암호는 구글 계정에 저장되어 기억할 필요가 없다라고 안내하고 있다
  • 암홀를 한번 더 확인하고 계속으로 넘어간다

 5) 연락처 정보를 작성한다

 6) 결제 정보를 입력한 후 계속을 클릭한다

    • 카드 확인으로 잠시 멈추었다가 카드 정보입력 창이 나온다
    • 참고로 시간이 오래 경과되면 다시 결제 정보 입력 창으로 이동한다
    • 본인이 등록한 카드에서 100원을 출금했다가 다시 취소하는 방식으로 확인한다

 7) 자격 증명 확인

  • 휴대폰 메시지나 음성통화로 본인을 확인한다(영어일테니 문자로...^^) 

  • 휴대폰으로 전송된 4자리 숫자를 입력하고 계속을 클릭한다

 8) Support 플랜 선택

  • 언제든지 AWS Managrment Console에서 변경이 가능하다고 안내되어 있다
  • 기본지원을 선택한다

 9) 가입 완료

  • 계정 생성에 약간의 시간이 소요되며, 완료 시 등록된 메일로 다량의 메일 폭탄이 날라 온다...^^
  • 생성 전에 AWS Managrment Console로 이동해도 무관하다

 10) 메일 내용 확인

  •   안내 메일

  • AWS Support (Basic) 등록 확인 메일

  • AWS 계정 준비 - 지금 시작 메일

 

계정 생성 과정이 끝났다


 

1. DTO 유효성 검증(Validation)

 

 1) DTO 유효성 검증(Validation) 필요성

  • 애플리케이션과 REST API 통신을 하는 프런트엔드(Frontend) 쪽 웹 앱이 아래 그림과 같다
  • 일반적으로 프런트엔드의 웹 사이트에서는 자바스크립트를 통해서 사용자가 입력하는 입력 폼 필드의 값에 대한 유효성 검증을 진행한다
  • 프런트엔드에서 검증에 통과되었다고 그 값이 반드시 유효한 값이라고 보장할 수는 없다
    - 유효성 검사를 통과한 뒤 [등록] 버튼을 누르면 서버 쪽으로 HTTP Request 요청이 전송된다
    - 자바스크립트로 전송되는 데이터는 브라우저의 개발자 도구를 사용하면 브레이크 포인트(break point)를 이용해서 값을 조작할 수 있다
    - 프런트엔드에서 유효성 검사를 진행해도 서버에서 한번 더 유효성 검사를 진행해야 한다
    - 프런트엔드에서 진행하는 유효성 검사는 사용자 편의성을 위해 진행하는 절차이다
 

2. DTO 클래스에 유효성 검증 적용

  • MemberController에서 사용된 MemberPostDto 클래스와 MemberPatchDto 클래스에 유효성 검증을 적용해 본다

 1) 유효성 검증을 위한 의존 라이브러리 추가

DTO 클래스에 유효성 검증을 적용하기 위해서는 Spring Boot에서 지원하는 Starter가 필요하다

build.gradle 파일의 dependencies 항목에 'org.springframework.boot:spring-boot-starter-validation’을 추가한다

//Spring boot 2.3 version 이상부터는 spring-boot-starter-web 의존성이 분리되서 @validation 사용을 하기 위해 추가 해줘야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'

 

 2) MemberPostDto 유효성 검증 제약 사항

  • email (이메일 주소)
    - 값이 비어있지 않거나 공백이 아니어야 한다
    - 유효한 이메일 주소 형식이어야 한다
  • name (이름)
    - 값이 비어있지 않거나 공백이 아니어야 한다
  • phone (휴대폰 번호)
    - 값이 비어있지 않거나 공백이 아니어야 한다
    - 010으로 시작하는 11자리 숫자와 ‘-’로 구성된 문자열이어야 한다
      : 010-1234-5678
  • 아래는 유효성 검증을 위해 제약사항을 적용한 코드이다
    - 회원 등록을 위해 클라이언트에서 전달 받는 Request Body의 데이터인 emil, name, phone 정보에 유효성 검증을 위한 애너테이션을 추가하였다
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

public class MemberPostDto {
    @NotBlank(message = "공백없이 작성해 주시기 바랍니다")
    @Email
    private String email;
    @NotBlank(message = "공백없이 작성해 주시기 바랍니다")
    private String name;

    @NotBlank(message = "공백없이 작성해 주시기 바랍니다")
    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$", message = "010으로 시작하는 11자리 숫자를 '-'을 넣어서 작성해 주시기 바랍니다")
    private String phone;
    private long memberId;

    public String getEmail() {
        return email;
    }
    public String getName() {
        return name;
    }
    public String getPhone() {
        return phone;
    }
    //--------------------------------- 회원정보 Dto class 작성
    //--------------------------------- RequestBody를 전달받기 위한 Dto class 작성
    public void setEmail(String email) {
        this.email = email;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
    public long getMemberId(){
        return memberId;
    }
    public void setMemberId(long memberId) {
        this.memberId = memberId;
    }
}

 

 2-1) MemberPostDto의 멤버 변수에 적용된 유효성 검증 내용

  • email
    - @NotBlank
      : 이메일 정보가 비어있지 않은지를 검증한다
      : null 값이나 공백(””), 스페이스(” “) 같은 값들을 허용하지 않는다
      : 유효성 검증에 실패하면 에러 메시지를 콘솔에 출력한다

    - @Email
      : 유효한 이메일 주소인지 검증한다
      : 유효성 검증에 실패하면 내장된 디폴트 에러 메시지를 콘솔에 출력한다
  • name
    - @NotBlank
      : 이름 정보가 비어있지 않은지를 검증한다
      : null 값이나 공백(””), 스페이스(” “) 같은 값들을 모두 허용하지 않는다
      : 유효성 검증에 실패하면 message 애트리뷰트에 지정한 문자열 에러 메시지를 콘솔에 출력한다
  • phone
    - @Pattern
      : 휴대폰 정보가 정규표현식(Reqular Expression)에 매치되는 유효한 번호인지를 검증한다
      : 유효성 검증에 실패하면 내장된 디폴트 에러 메시지를 콘솔에 출력한다
  • 요청으로 전달 받는 MemberPostDto 클래스의 각 멤버 변수에 유효성 검증을 위한 애너테이션을 추가하여 MemberController의 핸들러 메서드에 별도의 유효성 검증을 추가하지 않고 유효성 검증 로직을 분리하였다

 

 2-2) Controller에 유효성 검증 적용

  • 유효성 검증 애너테이션이 추가된 DTO 클래스에서 유효성 검증 로직이 실행되게 하기 위해서 postMember()와 같이 DTO 클래스에 @Valid 애너테이션을 추가한다
@PostMapping
//Dto에 유효성 검증 조건을 적용하였으므로 @Valid 를 적용하여 준다
public ResponseEntity postMember (@Valid @RequestBody MemberPostDto memberPostDto) {
    return new ResponseEntity<>(memberPostDto, HttpStatus.CREATED);
}

postman에서 유효하지 않은 정보를 전송하면 Error이 발생한다

  • Spring MVC에서 출력한 로그를 IntelliJ IDE에서 확인하면 아래와 같이 출력된다
    - email의 오류를 출력한다
    - name는 공백이 없으므로 정상적으로 전송된다
    - phone은 '010'이 아니므로 오류 메시지를 출력한다

 

3) MemberPatchDto 유효성 검증 제약 사항

  • name (이름)
    - 값이 비어있지 않거나 공백이 아니어야 한다
  • phone (휴대폰 번호)
    - 값이 비어있지 않거나 공백이 아니어야 한다
    - 010으로 시작하는 11자리 숫자와 ‘-’로 구성된 문자열이어야 한다
      : 010-1234-5678
  • 아래는 유효성 검증을 위해 제약사항을 적용한 코드이다
    - 회원 정보 수정을 위해 클라이언트에서 전달 받는 Request Body의 데이터인 name, phone 정보에 유효성 검증을 위한 애너테이션을 추가하였다
import javax.validation.constraints.Email;
import javax.validation.constraints.Pattern;

public class MemberPatchDto {
    @Email
    private String email;

    @Pattern(regexp = "^(?=\\s*\\S).*$", message = "공백이 아니어야 합니다")
    private String name;

    @Pattern(regexp = "^(?=\\s*\\S).*$", message = "공백이 아니어야 합니다")
    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$", message = "010으로 시작하는 11자리 숫자를 '-'을 넣어서 작성해 주시기 바랍니다")
    private String phone;
    private long memberId;

    public String getEmail() {
        return email;
    }
    public String getName() {
        return name;
    }
    public String getPhone() {
        return phone;
    }
    //--------------------------------- 회원정보 Dto class 작성
    //--------------------------------- RequestBody를 전달받기 위한 Dto class 작성
    public void setEmail(String email) {
        this.email = email;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
    public long getMemberId(){
        return memberId;
    }
    public void setMemberId(long memberId) {
        this.memberId = memberId;
    }
}

 3-1) MemberPatchDto 클래스의 멤버 변수에 적용된 유효성 검증 내용

  • memberId
    - Request Body에 포함되는 데이터가 아니므로 유효성 검증이 필요하지 않다
  • name
    - @Pattern
      : 정규 표현식으로 다음 내용을 체크한다
        -> 이름 정보가 비어있으면 검증에 성공
        -> 이름 정보가 비어 있지 않고, 공백 문자열이라면 검증에 실패
  • phone
    - @Pattern
      : 정규 표현식으로 다음 내용을 체크한다
        -> 휴대폰 정보가 비어있으면 검증에 성공
        -> 휴대폰 정보가 비어 있지 않고, 010으로 시작하는 11자리 숫자와 ‘-’로 구성된 문자열이 아니라면 검증에 실패
  • MemberPostDto 클래스와 달리 MemberPatchDto 에서는 모두 정규 표현식을 사용했다
  • 웹 브라우저에서 회원 가입 후에 이름(또는 닉네임)만 수정하거나 휴대폰 번호만 수정할 수도 있고 모두 수정할 수도 있다
  • 이름이나 휴대폰 번호의 값으로 공백 문자열(”” 또는 “ “)만 포함이 되어 있을 경우를 검증해야할 수 있다

 3-2) Controller 클래스에 patchMember() 핸들러 메서드의 코드 적용

    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember (@PathVariable("member-id")long memberId,
                                       @Valid @RequestBody MemberPatchDto memberPatchDto) {
        memberPatchDto.setMemberId(memberId);
//        memberPatchDto.setName("홍길동");
        // No need Business logic
        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }

 

3. 쿼리 파라미터(Query Parameter 또는 Query String) 및 @Pathvariable에 대한 유효성 검증

  • Request Body의 유효성 검사 검증 대상에서 빠진 항목이 있다
  • patchMember() 핸들러 메서드의 URI path에서 사용되는 @PathVariable("member-id") long memberId 변수이다
  • 일반적으로 수정이 필요한 데이터의 식별자는 0이상의 숫자로 표현을 한다
  • patchMember() 핸들러 메서드에서 사용되는 memberId에 "1 이상의 숫자여야 한다"라는 조건을 부여하도록 한다
    - 1 이상의 숫자일 경우에만 유효성 검증에 통과하도록 @Min(1) 이라는 검증 애너테이션을 추가한다
    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember (@PathVariable("member-id") @Min(1) long memberId,
                                       @Valid @RequestBody MemberPatchDto memberPatchDto) {
        memberPatchDto.setMemberId(memberId);
//        memberPatchDto.setName("홍길동");
        // No need Business logic
        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }
  • 위와 같이 수정하여도 Postman으로 URI path에 유효하지 않은 값을 전송하면 유효성 검증이 정상적으로 수행되지 않는다
  • 유효성 검증이 정상적으로 수행되려면 클래스 레벨에 @Validated 애너테이션을 붙여주어야 한다
@RestController
@RequestMapping("v1/members"/*,produce=MediaType.APPLICATION_JSON_VALUE*/)
@Validated
  • postman으로 실행해 보면 응답 결과는 Response Satus가 500인 ‘Internal Server Error’를 전달 받는다
  • @Request Body에 대한 유효성 검증 실패 메시지와 조금 다르지만 유효성 검증은 정상적으로 이루어진 것이다

 

4. Jakarta Bean Validation

  • DTO 클래스의 유효성 검증을 위해서 사용한 애너테이션은 Jakarta Bean Validation라는 유효성 검증을 위한 표준 스펙에서 지원하는 내장 애너테이션이다
  • Jakarta Bean Validation은 라이브러리처럼 사용할 수 있는 API가 아닌 스펙(사양, Specification) 자체이다
    - Jakarta Bean Validation은 일종의 기능 명세와 같다
    - Jakarta Bean Validation 스펙을 구현한 구현체가 Hibernate Validator이다
  • Jakarta Bean Validation의 애너테이션을 DTO 클래스에만 사용할 수 있는 것은 아니다
    - Java Bean 스펙을 준수하는 Java 클래스라면 Jakarta Bean Validation의 애너테이션으로 유효성 검증을 할 수 있다

 

5. Custom Validator를 사용한 유효성 검증

  • DTO 클래스에 유효성 검증 시 Jakarta Bean Validation에 내장된 애너테이션 중에 목적에 맞는 애너테이션이 없을 수 있다
    - 목적에 맞는 애너테이션을 직접 만들어서 유효성 검증에 적용할 수 있다
  • MemberPatchDto 클래스의 name과 phone 멤버 변수에서 공백 여부를 검증하는 @Pattern(regexp = "^(?=\\s*\\S).*$") 애너테이션을 Custom Validator를 사용하여 바꿔 본다
  • 정규 표현식(Regular Expression)은 성능적인 면에서 때로는 비싼 비용을 치뤄야 될 가능성이 있다
    - 모든 로직을 정규표현식 위주로 작성하는 것은 좋은 개발 방식이 아니다

 

 1) Custom Validator를 구현하기 위한 절차

  • Custom Validator를 사용하기 위한 Custom Annotation을 정의한다
    - 공백을 허용하지 않는 Custom Annotation 'Notspace'를 생성한다
    - 중요한 부분은 NotSpace 애너테이션을 멤버 변수에 추가할 경우 동작하는 Custom Validator를 추가하는 것이다
     : @Constraint(validatedBy = {NotSpaceValidator.class})
    - 유효성 검증 실패 시 표시되는 디폴트 메시지를 애너테이션에서 정의해 준다
     : String message() default "공백이 아니어야 합니다"
     : 애너테이션에서 정의한 디폴트 메시지는 적용 시 공통적으로 사용된다
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {NotSpaceValidator.class}) // (1)
public @interface Notspace {
    String message() default "공백이 아니어야 합니다"; // (2)
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  • 정의한 Custom Annotation에 바인딩 되는 Custom Validator를 구현한다
    - CustomValidator를 구현하기 위해서는 ConstraintValidator 인터페이스를 먼저 구현해야 한다
    - ConstraintValidator<NotSpace, String>에서
     : NotSpace는 CustomValidator와 매핑된 Custom Annotation을 의미한다
     : String은 Custom Annotation으로 검증할 대상 멤버 변수의 타입을 의미한다
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class NotSpaceValidator implements ConstraintValidator<Notspace, String> {

    @Override
    public void initialize(Notspace constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || StringUtils.hasText(value);
    }
}
  • 유효성 검증이 필요한 DTO 클래스의 멤버 변수에 Custom Annotation을 추가한다
    - name과 phone 등의 멤버 변수에서 적용된 @Pattern(regexp = "^(?=\\s*\\S).*$") 애너테이션 대신 Custom Annotation인 @NotSpace를 추가한다
    - 애플리케이션을 재시작하고 patchMember() 핸들러 메서드에 해당되는 URI로 요청을 전송하여 확인한다
public class MemberPostDto {
//    @NotBlank(message = "공백이 아니어야 합니다")
    @Notspace(message = "공백이 아니어야 합니다")
    @Email
    private String email;
//    @NotBlank(message = "공백이 아니어야 합니다")
    @Notspace(message = "공백이 아니어야 합니다")
    private String name;

//    @NotBlank(message = "공백이 아니어야 합니다")
    @Notspace(message = "공백이 아니어야 합니다")
    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$", message = "010으로 시작하는 11자리 숫자를 '-'을 넣어서 작성해 주시기 바랍니다")
    private String phone;
public class MemberPatchDto {
    @Email
    private String email;

//    @Pattern(regexp = "^(?=\\s*\\S).*$", message = "공백이 아니어야 합니다")
    @Notspace(message = "공백이 아니어야 합니다")
    private String name;

//    @Pattern(regexp = "^(?=\\s*\\S).*$", message = "공백이 아니어야 합니다")
    @Notspace(message = "공백이 아니어야 합니다")
    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$", message = "010으로 시작하는 11자리 숫자를 '-'을 넣어서 작성해 주시기 바랍니다")
    private String phone;

 

 

■ 참조 링크

정규 표현식 관련 자료

 : https://www.w3schools.com/java/java_regex.asp 

 

Java Regular Expressions

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_Expressions

 

정규 표현식 - JavaScript | MDN

정규 표현식, 또는 정규식은 문자열에서 특정 문자 조합을 찾기 위한 패턴입니다. JavaScript에서는 정규 표현식도 객체로서, RegExp의 exec()와 test() 메서드를 사용할 수 있습니다. String의 match(), matchA

developer.mozilla.org

정규 표현식 모범 사례 자료

 : https://learn.microsoft.com/ko-kr/dotnet/standard/base-types/best-practices 

 

.NET의 정규식에 대한 모범 사례

.NET에서 효율적이고 효과적인 정규식을 만드는 방법을 알아봅니다.

learn.microsoft.com

 Jakarta Bean Validation Specification

 : https://beanvalidation.org/2.0/spec/ 

 

Jakarta Bean Validation specification

BeanNode, PropertyNode and ContainerElementNode host getContainerClass() and getTypeArgumentIndex(). If the node represents an element that is contained in a container such as Optional, List or Map, the former returns the declared type of the container and

beanvalidation.org

Jakarta Bean Validation Built-in Constraint definitions

 : https://beanvalidation.org/2.0/spec/#builtinconstraints 

 

Jakarta Bean Validation specification

BeanNode, PropertyNode and ContainerElementNode host getContainerClass() and getTypeArgumentIndex(). If the node represents an element that is contained in a container such as Optional, List or Map, the former returns the declared type of the container and

beanvalidation.org

Hibernate Validator

 : https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#preface 

 

Hibernate Validator 6.2.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th

docs.jboss.org

Java Bean 관련 자료

 : https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94%EB%B9%88%EC%A6%88 

 

자바빈즈 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 자바빈즈(JavaBeans)는 자바로 작성된 소프트웨어 컴포넌트이다. 자바빈즈의 사양은 썬 마이크로시스템즈에서 다음과 같이 정의되었다. "빌더 형식의 개발도구에

ko.wikipedia.org

 

1. DTO(Data Transfer Object)

  • DTO는 Data Transfer Object의 약자로 마틴 파울러(Martin Fowler)가 ‘Patterns of Enterprise Application Architecture’ 라는 책에서 처음 소개한 엔터프라이즈 애플리케이션 아키텍처 패턴이다
  • Transfer라는 의미에서 알 수 있듯이 데이터를 전송하기 위한 용도의 객체이다
  • 데이터 전송은 클라이언트에서 서버 쪽으로 전송하는 요청 데이터, 서버에서 클라이언트 쪽으로 전송하는 응답 데이터의 형식으로 클라이언트와 서버 간에 데이터 전송이 이루어진다

 

2. DTO를 사용하는 이유

 1) 코드의 간결성

public class MemberController {
    @PostMapping

    public /*String*/ResponseEntity postMember(@RequestHeader Map<String, String> headers,
                                               @RequestParam("email")String email,
                                               @RequestParam("name")String name,
                                               @RequestParam("phone")String phone) {

//        코드(1)
//        System.out.println("# email: " + email);
//        System.out.println("# name: " + name);
//        System.out.println("# phone: " + phone);

//        JSON문자열 응답 타입을 수작업 코드에서 Map객체로 변경->produce애트리뷰트를 삭제할 수 있다
        //MAp 객체를 리턴하면 내부적으로 응답 데이터를 JSON데이터로 자동 변환해야 한다고 인식한다
//            Map<String, String> map = new HashMap<>();
//            map.put("email", email);
//            map.put("name", name);
//            map.put("phone", phone);

        //코드(2)
        //@RequestHeader Map을 사용하여 전체 헤더 정보를 받아 온다
        for (Map.Entry<String, String> entry : headers.entrySet()){
            System.out.println("key: " + entry.getKey() +
                    ", value: " + entry.getValue());
        }
        //리턴 값을 변경된 ResponseEntity로 대체
        //ResponseEntity 객체를 생성하고 생성자 파라미터로 map과 HttpStatus.CREATED를 반환한다
        //HttpStatus.CREATED 는 201, created를 의미한다

//        코드(1)
//            return new ResponseEntity<>(map, HttpStatus.CREATED);

        //코드(2)
        //@RequestHeader Map 을 사용함으로 리턴 값의 코드가 변경된다
        return new ResponseEntity<>(new Member(email, name, phone), HttpStatus.CREATED);
  • 코드(1)에서 작성한 MemberController의 postMember() 핸들러 메서드에서 개선해야 될 부분을 찾아본다
    - 코드(1)에서는 회원 정보를 저장하기 위해서 총 세 개의 @RequestParam 애너테이션을 사용하고 있다
    - 요청 데이터는 회원의 주소 정보, 로그인 패스워드, 패스워드 확인 정보 등 추가 내용이 회원 정보에 포함될 수 있다
    - postMember()에 파라미터로 추가되는 @RequestParam의 개수는 늘어날 수 밖에 없다
  • 클라이언트의 요청 데이터를 하나의 객체로 모두 전달 받을 수 있다면 코드 자체가 간결해질 수 있다
    - DTO 클래스는 요청 데이터를 하나의 객체로 전달 받는 역할을 한다
//    public /*String*/ResponseEntity postMember(@RequestHeader Map<String, String> headers,
//                                               @RequestParam("email")String email,
//                                               @RequestParam("name")String name,
//                                               @RequestParam("phone")String phone) {
    //코드(3)
    public class ResponseEntity postMember(MemberDto memberDto){
    
    로 변경하면
    
    //코드(2)
        //@RequestHeader Map 을 사용함으로 리턴 값의 코드가 변경된다
//        return new ResponseEntity<>(new Member(email, name, phone), HttpStatus.CREATED);

        //코드(3)
        return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
        
        으로 변경된다
  • postMember()에서 @RequestParam을 사용하는 부분이 사라지고 MemberDto memberDto 를 추가한다
  • 비즈니스 로직이 없는 상태이지만
    - @RequestParam을 통해 전달 받은 요청 데이터들을 Map에 추가하는 로직을 삭제하고
    - MemberDto 객체를 ResponseEntity 클래스의 생성자 파라미터로 전달하도록 변경하였다
  • 일단, 코드는 매우 간결해졌다

 2) 데이터 유효성(Validation) 검증의 단순화

  • 지금까지 작성한 Controller의 핸들러 메서드는 클라이언트의 요청 데이터에 대한 유효성 검증 작업을 거치지 않았다
  • 클라이언트 쪽에서 회원 정보의 email 주소를 ‘1111@gmail.com’ 같은 이메일 주소 형식이 아닌 ‘1111’ 같은 단순 문자열로 전송해도 정상적으로 핸들러 메서드 쪽에서 전달 받을 수 있다
  • 서버 쪽에서 유효한 데이터를 전달 받기 위해 데이터를 검증하는 것을 유효성(Validation)검증이라고 한다
@RestController
@RequestMapping("/no-dto-validation/v1/members")
public class MemberController {
    @PostMapping
    public ResponseEntity postMember(@RequestParam("email") String email,
                                     @RequestParam("name") String name,
                                     @RequestParam("phone") String phone) {
				// (1) email 유효성 검증
        if (!email.matches("^[a-zA-Z0-9_!#$%&'\\*+/=?{|}~^.-]+@[a-zA-Z0-9.-]+$")) {
            throw new InvalidParameterException();
        }
        Map<String, String> map = new HashMap<>();
        map.put("email", email);
        map.put("name", name);
        map.put("phone", phone);

        return new ResponseEntity<Map>(map, HttpStatus.CREATED);
    }
		...
		...
}
  • (1)은 정규 표현식을 사용해서 이메일 주소의 유효성을 검증하는 로직으로 핸들러 메서드 내에 직접적으로 포함되어 있다
  • name, phone에 대한 유효성 검증도 필요하면 핸들러 내의 코드는 유효성을 검증하는 로직들로 코드가 복잡해 진다
  • HTTP 요청을 전달 받는 핸들러 메서드는 요청을 전달 받는 것이 주 목적이기 때문에 최대한 간결하게 작성되는 것이 좋다
  • 핸들러 메서드 내부에 있는 유효성 검사 로직을 외부로 뺄 수 있다면 핸들러 메서드의 간결함을 유지할 수 있을 것이다
  • DTO 클래스를 사용하면 유효성 검증 로직을 DTO 클래스로 빼내어 핸들러 메서드의 간결함을 유지할 수 있다
import javax.validation.constraints.Email;
public class MemberDto {
    //MemberDto의 email 멤버 변수에 유효성 검증을 적용한다
    //email 멤버 변수에 @Email을 추가하여 클라이언트의 요청 데이터에 유효한 이메일 주소가 포함되어 있지 않을 경우
    //유효성 검증에 실패하므로 클라이언트의 요청은 거부(reject)된다
    //MemberDto 클래스에서 이메일에 대한 유효성 검증을 진행하므로
    //MemberController의 postMember() 핸들러 메서드는 코드(5)와 같이 간결해진다
    @Email  //'org.springframework.boot:spring-boot-starter-validation'을 추가해 주어야 한다
    private String email;
    private String name;
    private String phone;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}
  • 코드(5)가 적용된 MemberController class 코드이다
@RestController
@RequestMapping("v1/members"/*,produce=MediaType.APPLICATION_JSON_VALUE*/)

public class MemberController {
    @PostMapping
//  코드(1)
//    public /*String*/ResponseEntity postMember(@RequestHeader Map<String, String> headers,
//                                               @RequestParam("email")String email,
//                                               @RequestParam("name")String name,
//                                               @RequestParam("phone")String phone) {
    //코드(3)
//    public class ResponseEntity postMember(MemberDto memberDto){

    //코드(5)
    public ResponseEntity postMember (@Valid/*'org.springframework.boot:spring-boot-starter-validation'을 추가해 주어야 한다*/MemberDto memberDto) {

//        코드(1)
//        System.out.println("# email: " + email);
//        System.out.println("# name: " + name);
//        System.out.println("# phone: " + phone);

//        JSON문자열 응답 타입을 수작업 코드에서 Map객체로 변경->produce애트리뷰트를 삭제할 수 있다
        //MAp 객체를 리턴하면 내부적으로 응답 데이터를 JSON데이터로 자동 변환해야 한다고 인식한다
//            Map<String, String> map = new HashMap<>();
//            map.put("email", email);
//            map.put("name", name);
//            map.put("phone", phone);

        //코드(4)
        //email 유효성 검증 : 코드(1) @RequestParam 과 함게 사용 시 에러 없음
//        if (!email.matches("^[a-zA-Z0-9_!#$%&'\\*+/=?{|}~^.-]+@[a-zA-Z0-9.-]+$")) {
//            throw new InvalidParameterException();
//        }
//        Map<String, String> map = new HashMap<>();
//        map.put("email", email);
//        map.put("name", name);
//        map.put("phone", phone);

        //코드(2)
        //@RequestHeader Map을 사용하여 전체 헤더 정보를 받아 온다
//        for (Map.Entry<String, String> entry : headers.entrySet()){
//            System.out.println("key: " + entry.getKey() +
//                    ", value: " + entry.getValue());
//        }
        //리턴 값을 변경된 ResponseEntity로 대체
        //ResponseEntity 객체를 생성하고 생성자 파라미터로 map과 HttpStatus.CREATED를 반환한다
        //HttpStatus.CREATED 는 201, created를 의미한다

//        코드(1)
//            return new ResponseEntity<>(map, HttpStatus.CREATED);

        //코드(2)
        //@RequestHeader Map 을 사용함으로 리턴 값의 코드가 변경된다
//        return new ResponseEntity<>(new Member(email, name, phone), HttpStatus.CREATED);

        //코드(3)
        return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);

//        코드(1)
//        String reponse =
//                "{\"" +
//                    "email\":\""+email+"\"," +
//                    "\"name\":\""+name+"\",\"" +
//                    "phone\":\"" + phone+
//                "\"}";
//        return reponse;
    }
  • @Valid 애너테이션은 MemberDto 객체에 유효성 검증을 적용하게 해주는 애너테이션이다
    - @Valid를 사용하기 위해서는 build.gradle에 아래 코드를 적용해야 한다
implementation 'org.springframework.boot:spring-boot-starter-validation'
Spring boot 2.3 version 이상부터는 spring-boot-starter-web 의존성이 분리되서
@validation 사용을 하기 위해 추가 해줘야 한다.
  • DTO 클래스를 사용하는 가장 중요한 목적은
    - 비용이 많이 드는 작업인 HTTP 요청의 수를 줄이고
    - 도메인 객체와 분리하는 것이다

 

3. HTTP 요청/응답 데이터에 DTO 적용하기

  • MemberController의 핸들러 메서드에 DTO 클래스를 적용해 본다
  • 지금까지는 HTTP Request Body가 JSON 형식이 아닌 ‘x-www-form-urlencoded’ 형식의 데이터였다
  • 프런트엔드와 함께 프로젝트를 진행 또는 하나의 제품이나 서비스를 제작할 경우
    - 프런트엔드의 웹앱과 백엔드의 애플리케이션 간 기본 API 통신 프로토콜은 대부분 JSON 형식으로 한다
  • 클라이언트에서 전달되는 요청 데이터 중에서 바디에 해당되는 데이터를 Request Body라고 한다
  • @RequestBody 애너테이션의 추가에 따라 Request Body를 전달 받는 방식이 JSON 형식으로 변경된다

 1) DTO 클래스 적용을 위한 코드 리팩토링 절차

  • 회원 정보를 전달 받을 DTO 클래스를 생성한다
    - MemberCotroller에서 현재 회원 정보로 전달 받는 각 데이터 항목(email, name, phone)들을 DTO 클래스의 멤버 변수로 추가한다
  • 클라이언트 쪽에서 전달하는 요청 데이터를 @RequestParam 애너테이션으로 전달 받는 핸들러 메서드를 찾는다
    - Request Body가 필요한 핸들러는 HTTP POST, PATCH, PUT 같이 리소스의 추가나 변경이 발생하는 경우이다
    - HTTP GET은 리소스를 조회하는 용도이기 때문에 Request Body는 필요가 없다
    - @PostMapping, @PatchMapping 애너테이션이 붙은 핸들러 메서드를 찾는것과 동일하다
  • @RequestParam의 코드를 DTO 클래스의 객체로 수정한다
  • Map 객체로 작성되어 있는 Response Body를 DTO 클래스의 객체로 변경한다
import javax.validation.constraints.Email;
public class MemberDto {
    @Email
    private String email;
    private String name;
    private String phone;
    private long memberId;

    public String getEmail() {
        return email;
    }
    public String getName() {
        return name;
    }
    public String getPhone() {
        return phone;
    }
    //--------------------------------- 회원정보 Dto class 작성
    //--------------------------------- RequestBody를 전달받기 위한 Dto class 작성
    public void setEmail(String email) {
        this.email = email;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
    public long getMemberId(){
        return memberId;
    }
    public void getMemberId(long memberId) {
        this.memberId = memberId;
    }
}
  • 위의 코드는 회원 정보 등록과 수정 시 Request Body를 전달 받기 위한 Dto 클래스이다
  • DTO 클래스를 만들 때에는 멤버 변수 이외에 각 멤버 변수에 해당하는 getter 메서드가 있어야 한다
    - getter 메서드가 없으면 Response Body에 해당 멤버 변수의 값이 포함되지 않는 문제가 발생한다
    - setter 메서드는 개발자의 필요에 의해서 생성할 수 있다

 

 

Generate constructors and accessor methods - IntelliJ IDEA Guide

You can use ⌘N (macOS), or Alt+Insert (Windows/Linux) for the Generate menu and then select Constructor, Getter, Setter or Getter and Setter.

www.jetbrains.com

lombok 라이브러리를 등록하면 getter/setter 메서드가 내부에서 자동으로 만들어진다

 

  • @RequestParam을 사용했던 코드를 DTO 클래스를 사용하여 수정하였다
    - postMember()에서는 MemberPostDto 클래스의 객체를
    - patchMember()에서는 MemberPatchDto 클래스의 객체를 통해 Request Body를 한번에 전달 받을 수 있도록 하였다
  • @RequestParam을 사용했던 코드가 간결해진 것을 확인할 수 있다
@RestController
@RequestMapping("v1/members"/*,produce=MediaType.APPLICATION_JSON_VALUE*/)

@RestController
@RequestMapping("v1/members"/*,produce=MediaType.APPLICATION_JSON_VALUE*/)

public class MemberController {
    @PostMapping
    public ResponseEntity postMember (@RequestBody MemberPostDto memberPostDto) {
        return new ResponseEntity<>(memberPostDto, HttpStatus.CREATED);
    }

    //추가 코드 : patchMember
    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember (@PathVariable("member-id")long memberId, @RequestBody MemberPatchDto memberPatchDto) {
        memberPatchDto.setMemberId(memberId);
        memberPatchDto.setName("홍길동");
        // No need Business logic
        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }

    @GetMapping("/{member-id}")
    public ResponseEntity getMember(@PathVariable("member-id")long memberId, MemberGetDto memberGetDto) {
        System.out.println("# memberId: " + memberId);  //터미널에 출력되는 코드이다
        // not implementation
        return new ResponseEntity<>(memberGetDto, HttpStatus.OK);
    }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/members”)에 매핑된다
    public ResponseEntity getMembers(MemberGetDto memberGetDto) {
        System.out.println("# get Members");  //터미널에 출력되는 코드이다
        // not implementation
        return new ResponseEntity<>(memberGetDto, HttpStatus.OK);
    }

    //추가 코드 : getDelete
    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(@PathVariable("member-id")long memberId) {
        // No need business logic
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}
  • postman을 통해 post를 보내보면 아래와 같이 데이터가 입력되는 것을 확인할 수 있다

 

4. Annotation

 1) @RequestBody

  • JSON 형식의 Request Body를 Dto 클래스의 객체로 변환을 시켜주는 역할을 한다
    - 클라이언트에서 전송하는 Request Body가 JSON 형식이어야 하기 때문이다
  • JSON 형식이 아닌 다른 형식의 데이터를 전송하면 Spring 내부에서 ‘Unsupported Media Type’ 과 같은 에러 메시지를 포함한 응답을 전달한다

 

 2) @ResponseBody

  • JSON 형식의 Response Body를 클라이언트에게 전달하기 위해 DTO 클래스의 객체를 Response Body로 변환한다
  • postMember(), patchMember()에는 @ResponseBody 를 사용하는 곳이 없다
    - postMember(), patchMember() 핸들러 메서드의 리턴 값이 ResponseEntity 클래스의 객체이기 때문이다
  • Spring MVC에서는 핸들러 메서드에 @ResponseBody 애너테이션이 붙거나 핸들러 메서드의 리턴 값이 ResponseEntity일 경우, 내부적으로 HttpMessageConverter가 동작하게 되어 응답 객체(여기서는 DTO 클래스의 객체)를 JSON 형식으로 바꿔준다

 

 3) JSON 직렬화(Serialization)와 역직렬화(Deserialization)

  • 클라이언트에서 JSON 형식의 데이터를 서버 쪽으로 전송하면 서버의 웹 애플리케이션은 전달 받은 JSON 형식의 데이터를 DTO 같은 Java의 객체로 변환하는 기능을 역직렬화(Deserialization)이라고 한다
  • 서버에서 클라이언트에게 응답 데이터를 전송하기 위해 DTO 같은 Java의 객체를 JSON 형식으로 변환하는 것을 직렬화(Serialization)라고 한다
  • JSON 직렬화(Serialization) : Java 객체 → JSON
  • JSON 역직렬화(Deserialization) : JSON → Java 객체

 

 

1. 클라이언트(Client)와 서버(Server)의 관계

클라이언트(Client)와 서버(Server)의 관계

  • WebBrower는 Frontend 서버에 content를 요청하면 Forntend 서버는 content를 찾아서 제공한다
  • Frontend 서버는 WenBrower에서 요청한 content를 필요에 따라 Backend 서버에 요청하여 제공받는다
  • 최종 Client는 WebBrower이지만 Frontend 서버는 필요에 따라 Client가 되기도 하고 Server가 되기도 한다
  • Backend도 다중의 Server를 사용하게 되면 Request Server가 Client가 되기도 한다

2. Rest API

  • Rest API 서버에 HTTP 요청을 보낼 수 있는 클라이언트 툴 또는 라이브러리를 의미한다
  • postman과 같은 프로그램은 UI가 있는 Rest API이다
  • UI가 없는 Rest API일 경우에는 Rest Client Library를 사용하면 된다
    - Server와 Server 간의 요청이 대표적이다

▶ IBM REST API : https://www.ibm.com/docs/en/integration-bus/10.0?topic=apis-rest

 

3. RestTemplate

  • Java에서 사용할 수 있는 HTTP Client 라이브러리
    - java.net.HttpURLConnection
    - Apache HttpComponents
    - OkHttp 3
    - Netty
  • Spring에서는 HTTP Client 라이브러리 중 하나를 이용해서 원격지에 있는 다른 Backend 서버에 HTTP 요청을 보낼 수 있는 RestTemplate이라는 Rest Client API를 제공한다
  • RestTemplate이라는 템플릿 클래스를 이용하여 HTTP Client 라이브러리 중 하나를 유연하게 사용할 수 있다

 

4. RestTemplate 객체 생성

  • Spring Initializr를 사용하여 프로젝트를 생성한다
package main;

public class RestClientExample01{
    public static void main(String[] args) {

        /*기본적으로 RestTemplate의 객체를 생성하기 위해서는
        RestTemplate의 생성자 파라미터로 HTTP Client 라이브러리의 구현 객체를 전달해야 한다*/

        RestTemplate restTemplate = new RestTemplate(

                /*HttpComponentsClientHttpRequestFactory 클래스를 통해 Apache HttpComponents를 전달한다*/

                new HttpComponentsClientHttpRequestFactory());
    }
}
  • 위의 코드는 객체 에러로 인하여 실행할 수 없다
  • Apache HttpComponents를 사용하기 위해서는 builde.gradle의 dependencies 항목에 아래와 같이 의존 라이브러리를 추가해야 한다
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	//추가한 코드
	implementation 'org.apache.httpcomponents:httpclient'
}
  • dependencies를 추가하면  객체를 import 할 수 있다
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class RestClientExample01{
    public static void main(String[] args) {

        /*기본적으로 RestTemplate의 객체를 생성하기 위해서는
        RestTemplate의 생성자 파라미터로 HTTP Client 라이브러리의 구현 객체를 전달해야 한다*/

        RestTemplate restTemplate = new RestTemplate(

                /*HttpComponentsClientHttpRequestFactory 클래스를 통해 Apache HttpComponents를 전달한다*/

                new HttpComponentsClientHttpRequestFactory());
    }
}

 

5. URI 생성

  • RestTemplate 객체를 생성한 후 HTTP Request를 전송할 Rest 엔드포인트 URI를 지정한다
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

public class RestClientExample01{
    public static void main(String[] args) {

        /*기본적으로 RestTemplate의 객체를 생성하기 위해서는
        RestTemplate의 생성자 파라미터로 HTTP Client 라이브러리의 구현 객체를 전달해야 한다*/

        RestTemplate restTemplate = new RestTemplate(

                /*HttpComponentsClientHttpRequestFactory 클래스를 통해 Apache HttpComponents를 전달한다*/

                new HttpComponentsClientHttpRequestFactory());

        //URI 생성
        /*
        UriComponentsBuilder 클래스를 이용해서 UriComponents 객체를 생성하고
        UriComponents 객체를 이용해서 HTTP Request를 요청할 엔드포인트의 URI를 생성한다
        */
        UriComponents uriComponents = UriComponentsBuilder

                .newInstance() //UriComponentsBuilder 객체를 생성한다
                .scheme("http") //URI의 scheme을 설정한다
                .host("worldtimeapi.org") //호스트 정보를 입력한다
                .port(80)  //디폴트 값은 80이므로 80 포트를 사용하는 호스트라면 생략 가능하다
                .path("api/timezone/{continents}/{city}")
                /*
                URI의 경로(path)를 입력한다
                URI의 path에서 {continents}, {city} 두 개의 템플릿 변수를 사용하고 있다
                두 개의 템플릿 변수는 uriComponents.expand("Asia", "Seoul").toUri(); 에서
                expand() 메서드 파라미터의 문자열로 채워진다
                빌드 타임에 {continents}는 ‘Asia’, {city}는 ‘Seoul’로 변환됩니다.
                 */
                .encode()
                /*URI에 사용된 템플릿 변수들을 인코딩 한다
                 non-ASCII 문자와 URI에 적절하지 않은 문자를 Percent Encoding 한다는 의미이다
                 */
                .build();
                //UriComponents 객체를 생성한다

        URI uri = uriComponents.expand("Asia","Seoul").toUri();
        /* expand 메서드는 파라미터로 입력한 값을 URI 템플릿 변수의 값으로 대체한다
           toUri 메서드는 객체를 생성한다
         */
    }

 

6. Request 전송

  • 엔드포인트로 Request를 전송한다
  • getForObject(URI uri, Class<T> responseType)
    - getForObject() 메서드는 HTTP Get 요청을 통해 서버의 리소스를 조회한다
    - URI uri : Request를 전송할 엔드포인트의 URI 객체를 지정한다
    - Class responseType : 응답으로 전달 받을 클래스의 타입을 지정한다
package com.dreamfactory.restAPI;

import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

public class RestClientExample01{
    public static void main(String[] args) {

        /*기본적으로 RestTemplate의 객체를 생성하기 위해서는
        RestTemplate의 생성자 파라미터로 HTTP Client 라이브러리의 구현 객체를 전달해야 한다*/

        RestTemplate restTemplate = new RestTemplate(

                /*HttpComponentsClientHttpRequestFactory 클래스를 통해 Apache HttpComponents를 전달한다*/

                new HttpComponentsClientHttpRequestFactory());

        //URI 생성
        /*
        UriComponentsBuilder 클래스를 이용해서 UriComponents 객체를 생성하고
        UriComponents 객체를 이용해서 HTTP Request를 요청할 엔드포인트의 URI를 생성한다
        */
        UriComponents uriComponents = UriComponentsBuilder

                .newInstance() //UriComponentsBuilder 객체를 생성한다
                .scheme("http") //URI의 scheme을 설정한다
                .host("worldtimeapi.org") //호스트 정보를 입력한다
                .port(80)  //디폴트 값은 80이므로 80 포트를 사용하는 호스트라면 생략 가능하다
                .path("api/timezone/{continents}/{city}")
                /*
                URI의 경로(path)를 입력한다
                URI의 path에서 {continents}, {city} 두 개의 템플릿 변수를 사용하고 있다
                두 개의 템플릿 변수는 uriComponents.expand("Asia", "Seoul").toUri(); 에서
                expand() 메서드 파라미터의 문자열로 채워진다
                빌드 타임에 {continents}는 ‘Asia’, {city}는 ‘Seoul’로 변환됩니다.
                 */
                .encode()
                /*URI에 사용된 템플릿 변수들을 인코딩 한다
                 non-ASCII 문자와 URI에 적절하지 않은 문자를 Percent Encoding 한다는 의미이다
                 */
                .build();
                //UriComponents 객체를 생성한다

        URI uri = uriComponents.expand("Asia","Seoul").toUri();
        /* expand 메서드는 파라미터로 입력한 값을 URI 템플릿 변수의 값으로 대체한다
           toUri 메서드는 객체를 생성한다
         */

        //Request 전송

        //getForObject() 메서드로 HTTP Get 요청을 통해 서버의 리소스를 조회한다
        //응답 데이터를 문자열로 받을 수 있도록 String.class로 지정한다
       String result = restTemplate.getForObject(uri, String.class);
        System.out.println(result);

    }
}
  • 코드를 최종적으로 실행하면 아래와 같이 출력된다

 

 1) getForObject()를 이용하여 커스텀 클래스 타입으로 원하는 정보만 응답으로 전달 받기

  • 전달 받고자하는 응답 데이터의 JSON 프로퍼티 이름과 클래스의 멤버변수 이름이 동일해야 한다
  • 해당 멤버 변수에 접근하기 위한 getter 메서드 역시 동일한 이름이어야 한다
package com.dreamfactory.restAPI;

import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

public class RestClientExample02 {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate(
                new HttpComponentsClientHttpRequestFactory()
        );
        UriComponents uriComponents = UriComponentsBuilder
                .newInstance()
                .scheme("http")
                .host("worldtimeapi.org")
                        .port(80)
                        .path("/api/timezone/{continents}/{city}")
                        .encode()
                        .build();
        URI uri = uriComponents.expand("Asia", "Seoul").toUri();
        //WorldTime 클래스를 사용해서 전체 응답 데이터가 아니라 datetime과 timezone 정보만 전달 받는다
        WorldTime worldTime = restTemplate.getForObject(uri, WorldTime.class);

        System.out.println("# datatime: " + worldTime.getDatetime());
        System.out.println("# timezone: " + worldTime.getTimezone());
        System.out.println("# day_of_week: " + worldTime.getDay_of_week());
    }
}
  • WorldTime에 대한 클래스를 새로 생성해야 한다
package com.dreamfactory.restAPI;

public class WorldTime {
    private String datetime;
    private String timezone;
    private String day_of_week;

    public String getDatetime() {
        return datetime;
    }
    public String getTimezone() {
        return timezone;
    }
    public String getDay_of_week() {
        return day_of_week;
    }
}
  • 실행하면 지정된 데이터만 응답 전송되는 것을 확인할 수 있다

 

 

 2) getForEntity()를 사용한 Response Body(바디, 컨텐츠) + Header(헤더) 정보 전달 받기

  • 응답 데이터는 ResponseEntity 클래스로 래핑되어서 전달 된다
  • getBody(), getHeaders() 메서드 등을 이용해서 바디와 헤더 정보를 얻을 수 있다
  • 응답으로 전달 되는 모든 헤더 정보를 보고 싶다면 getHeaders().entrySet() 메서드를 이용해서 확인할 수 있다
package com.dreamfactory.restAPI;

import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

public class RestClientExample02 {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate(
                new HttpComponentsClientHttpRequestFactory()
        );
        UriComponents uriComponents = UriComponentsBuilder
                .newInstance()
                .scheme("http")
                .host("worldtimeapi.org")
                        .port(80)
                        .path("/api/timezone/{continents}/{city}")
                        .encode()
                        .build();
        URI uri = uriComponents.expand("Asia", "Seoul").toUri();
        //WorldTime 클래스를 사용해서 전체 응답 데이터가 아니라 datetime과 timezone 정보만 전달 받는다
//        WorldTime worldTime = restTemplate.getForObject(uri, WorldTime.class);

//        System.out.println("# datatime: " + worldTime.getDatetime());
//        System.out.println("# timezone: " + worldTime.getTimezone());
//        System.out.println("# day_of_week: " + worldTime.getDay_of_week());
        
        //getForEntity()를 사용한 Response Body(바디, 컨텐츠) + Header(헤더) 정보 전달 받기
        ResponseEntity<WorldTime> response = restTemplate.getForEntity(uri, WorldTime.class);
        System.out.println("# datatime: " + response.getBody().getDatetime());
        System.out.println("# timezone: " + response.getBody().getTimezone());
        System.out.println("# day_of_week: " + response.getBody().getDay_of_week());
        System.out.println("# HTTP Status Code: " + response.getStatusCode());
        System.out.println("# HTTP Status Value: " + response.getStatusCodeValue());
        System.out.println("# Content Type: " + response.getHeaders().getContentType());
        System.out.println(response.getHeaders().entrySet());

    }
}
  • 실행하면 아래와 같은 결과 값이 출력된다

 

 3) exchange() 를 사용한 응답 데이터 받기

  • exchange() 메서드를 사용한 방식은 일반적인 HTTP Request 방식입니다.
  • HTTP Method나 HTTP Request, HTTP Response 방식을 개발자가 직접 지정해서 유연하게 사용할 수 있다
  • exchange(URI uri, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType)
    - HTTP Method, RequestEntity, ResponseEntity를 직접 지정해서 HTTP Request를 전송할 수 있는 가장 일반적인 방식이다
    URI uri : Request를 전송할 엔드포인트의 URI 객체를 지정한다
    - HttpMethod method : HTTP Method 타입을 지정한다
    - HttpEntity<?> requestEntity :
    HttpEntity 객체를 지정하며, HttpEntity 객체를 통해 헤더 및 바디, 파라미터 등을 설정해줄 수 있다
    -  Class<T> responseType : 응답으로 전달 받을 클래스의 타입을 지정한다
package com.dreamfactory.restAPI;

import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

public class RestClientExample02 {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate(
                new HttpComponentsClientHttpRequestFactory()
        );
        UriComponents uriComponents = UriComponentsBuilder
                .newInstance()
                .scheme("http")
                .host("worldtimeapi.org")
                        .port(80)
                        .path("/api/timezone/{continents}/{city}")
                        .encode()
                        .build();
        URI uri = uriComponents.expand("Asia", "Seoul").toUri();
        //WorldTime 클래스를 사용해서 전체 응답 데이터가 아니라 datetime과 timezone 정보만 전달 받는다
//        WorldTime worldTime = restTemplate.getForObject(uri, WorldTime.class);

//        System.out.println("# datatime: " + worldTime.getDatetime());
//        System.out.println("# timezone: " + worldTime.getTimezone());
//        System.out.println("# day_of_week: " + worldTime.getDay_of_week());

        //getForEntity()를 사용한 Response Body(바디, 컨텐츠) + Header(헤더) 정보 전달 받기
//        ResponseEntity<WorldTime> response = restTemplate.getForEntity(uri, WorldTime.class);
//        System.out.println("# datatime: " + response.getBody().getDatetime());
//        System.out.println("# timezone: " + response.getBody().getTimezone());
//        System.out.println("# day_of_week: " + response.getBody().getDay_of_week());
//        System.out.println("# HTTP Status Code: " + response.getStatusCode());
//        System.out.println("# HTTP Status Value: " + response.getStatusCodeValue());
//        System.out.println("# Content Type: " + response.getHeaders().getContentType());
//        System.out.println(response.getHeaders().entrySet());

        //exchange() 를 사용한 응답 데이터 받기
        ResponseEntity<WorldTime> response = restTemplate.exchange(
                uri, HttpMethod.GET, null, WorldTime.class);
        System.out.println("# datatime: " + response.getBody().getDatetime());
        System.out.println("# timezone: " + response.getBody().getTimezone());
        System.out.println("# day_of_week: " + response.getBody().getDay_of_week());
        System.out.println("# HTTP Status Code: " + response.getStatusCode());
        System.out.println("# HTTP Status Value: " + response.getStatusCodeValue());
    }
}
  • 코드를 실행하면 아래와 같이 출력된다

 

 

※ 참조 링크

URI scheme : https://en.wikipedia.org/wiki/List_of_URI_schemes

 

List of URI schemes - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Namespace identifier assigned by IANA This article lists common URI schemes. A Uniform Resource Identifier helps identify a source without ambiguity. Many URI schemes are registered wi

en.wikipedia.org

Percent Encoding : https://ko.wikipedia.org/wiki/%ED%8D%BC%EC%84%BC%ED%8A%B8_%EC%9D%B8%EC%BD%94%EB%94%A9

 

퍼센트 인코딩 - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

RestTemplate API : https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#rest-client-access

 

Integration

As a lightweight container, Spring is often considered an EJB replacement. We do believe that for many, if not most, applications and use cases, Spring, as a container, combined with its rich supporting functionality in the area of transactions, ORM and JD

docs.spring.io

RestTEmplate API Docs : https://docs.spring.io/spring-framework/docs/current/javadoc-api/

 

Spring Framework 5.3.22 API

 

docs.spring.io

Open API 서비스 제공 사이트

https://www.data.go.kr/dataset/3043385/openapi.do

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

https://developers.naver.com/products/intro/plan/plan.md

 

네이버 오픈 API 목록 - INTRO

네이버 오픈 API 목록 NAVER Developers - API 소개 네이버 오픈API 목록 및 안내입니다. 네이버 오픈 API 목록 API명 설명 호출제한 검색 네이버 블로그, 이미지, 웹, 뉴스, 백과사전, 책, 카페, 지식iN 등 검

developers.naver.com

https://console.cloud.google.com/getting-started?pli=1 

 

Google Cloud console

계정이 일시적으로 차단되었습니다. Google이 네트워크에서 악성 소프트웨어, 브라우저 플러그인 또는 자동화된 요청을 전송하는 스크립트에 의해 전송되었을 수 있는 요청을 감지한 경우 계정

console.cloud.google.com

https://aiopen.etri.re.kr/

 

공공 인공지능 오픈 API·DATA 서비스 포털

과학기술정보통신부의 R&D 과제를 통해 개발한 다양한 인공지능 기술 및 데이터를 누구나 사용할 수 있도록 제공

aiopen.etri.re.kr

 

1. HTTP헤더(Header)

  • General headers
      - 메시지 전체에 적용되는 헤더이며, body를 통해 전송되는 데이터와는 관련이 없는 헤더이다
  • Response headers
      - 위치 또는 서버 자체에 대한 정보(이름, 버전 등)와 같이 응답에 대한 부가적인 정보를 갖는 헤더이다
      - Vary, Accept-Ranges와 같이 상태 줄에 넣기에는 공간이 부족했던 추가 정보를 제공한다
  • Representation headers
      - Entity headers라고 부르기도 한다
      - body에 담긴 리소스의 정보(콘텐츠 길이, MIME 타입 등)를 포함하는 헤더이다

 

2. Spring MVC에서 HTTP Header의 사용

 1) 클라이언트와 서버 관점에서의 대표적인 HTTP 헤더

  • 클라이언트와 서버의 관점에서 내부적으로 가장 많이 사용되는 헤더 정보로 ‘Content-Type’이 있다
    - 클라이언트와 서버가 주고 받는 HTTP 메시지 body의 데이터 형식을 알려준다
    - 클라이언트와 서버는 Content-Type의 데이터 형식에 맞는 데이터들을 주고 받는다
  • 샘플로 작성하고 있는 커피주문 애플리케이션의 Content-Type은 ‘application/json’이다
    - Spring MVC - API 계층 - Controller (MVC 개요/핸들러 메서드) 에서는 prosuce 값으로 사용했지만, Spring MVC - API 계층 -Controller (ResponseEntity 적용) 에서 Map 객체를 사용하면서 Content-Type을 삭제했다

 

 2) 대표적인 HTTP 헤더 예시

  • 개발자가 직접 코드 레벨에서 HTTP 헤더를 컨트롤 해야될 경우 사용하는 대표적인 HTTP 헤더이다
  • Authorization 헤더
    - 클라이언트가 적절한 자격 증명을 가지고 있는지를 확인하기 위한 정보이다
    - REST API 기반 애플리케이션의 경우 클라이언트와 서버 간의 로그인(사용자 ID/비밀번호) 인증(Authenticatioin)에 통과한 클라이언트들은 ‘Authorization’ 헤더 정보를 기준으로 인증에 통과한 클라이언트가 맞는지 확인하는 인가 절차를 거친다
  • User-Agent 헤더
    - 애플리케이션을 구현 과정에서 여러가지 유형의 클라이언트가 하나의 서버 애플리케이션에 요청을 전송하는 경우가 많다
    - 사용자에 따라 데스크탑이나 노트북의 웹 브라우저를 사용하거나 스마트폰, 태블릿 등 모바일에서 서버에 요청을 보낸다
    - 보내오는 요청의 클라이언트를 구분해서 응답 데이터를 다르게 보내줘야 되는 경우가 있다
    - 모바일, 데스크탑, 노트북의 화면 크기에 따라 더 많은 정보를 보여주기 위해 각각 데이터의 종류와 크기가 다를 수 있다
    - User-Agent 헤더 정보를 이용해서 클라이언트의 요청 브라우져를 구분해서 처리할 수 있다

3. 샘플 어플리케이션에 HTTP Header 적용

 1) @RequestHeader 로 개별 헤더 정보 받기

  • CoffeeController에 적용하여 실행한다
package com.dreamfactory.exam_controller.coffee;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Member;
import java.sql.SQLOutput;
import java.util.HashMap;
import java.util.Map;


@RestController
@RequestMapping(value="v1/coffees"/*, produces = MediaType.APPLICATION_JSON_VALUE*/)
//v1은 버젼을 의미한다. coffees는 데이터 post, get 의 조회 위치를 의미한다

public class CoffeeController {
    @PostMapping

    //postCoffee() 메서드는 커피 정보를 등록해 준다
    public /*String*/ResponseEntity postCoffee(//@RequestParam("coffee")String coffee,
                                               @RequestHeader("user-agent")String userAgent,
                                                 @RequestParam("coffeeId")String coffeeId,
                                                 @RequestParam("korName")String korName,
                                                 @RequestParam("engName")String engName,
                                                 @RequestParam("price")int price) {
//        System.out.println("# coffee:" + coffee);
//        System.out.println("# coffeeId:" + coffeeId);
//        System.out.println("# korName:" + korName);
//        System.out.println("# engName:" + engName);
//        System.out.println("# price:" + price);

        //@RequestHeader 로 개별 정보 받아오기
        System.out.println("user-agent: " + userAgent);
        return new ResponseEntity<>(new Coffee(korName, engName, price),
        HttpStatus.CREATED);

        //Map객체로 변경
//            Map<String, String> map = new HashMap<>();
//            map.put("coffee", coffee);
//            map.put("coffeeId", coffeeId);
//            map.put("korName", korName);
//            map.put("engName", engName);
//            map.put("price", String.valueOf(price));
//
//            //return 값을 ResponseEntity로 변경
//        return new ResponseEntity<>(map, HttpStatus.CREATED);

//        String reponse =
//                "{\"" +
//                    "coffee\":\""+coffee+"\"," +
//                    "\"coffeeId\":\""+coffeeId+"\"," +
//                    "\"korName\":\""+korName+"\"," +
//                    "\"engName\":\""+engName+"\"," +
//                    "\"price\":\""+price+
//                "\"}";
//        return reponse;
    }

    @GetMapping("/{coffee-id}")

    //getCoffee() 메서드는 커피 정보을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getCoffee(@PathVariable("coffee-id")long coffeeId) {
        System.out.println("# coffeeId: " + coffeeId);
        return new ResponseEntity<>(HttpStatus.OK);
        }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/coffees”)에 매핑된다

    //getCoffees() 메서드는 커피 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getCoffees() {
        System.out.println("# get Coffees");
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
  • 실행하면 error가 발생한다
  • new Coffee에 대한 신규 class를 생성하여 문제를 해결한다
package com.dreamfactory.exam_controller.coffee;

public class Coffee {
    public Coffee(String korName, String engName, int price) {
    }
}

 

 2) @RequestHeader 로 전체 헤더 정보 받기

  • MemberController에 적용하여 실행한다
package com.dreamfactory.exam_controller.member;

import ch.qos.logback.classic.util.LogbackMDCAdapter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.sql.SQLOutput;

import java.util.HashMap;
import java.util.Map;

@RestController

@RequestMapping("v1/members"/*,produce=MediaType.APPLICATION_JSON_VALUE*/)

public class MemberController {
    @PostMapping

    public /*String*/ResponseEntity postMember(@RequestHeader Map<String, String> headers,
                                               @RequestParam("email")String email,
                                               @RequestParam("name")String name,
                                               @RequestParam("phone")String phone) {
       
//        System.out.println("# email: " + email);
//        System.out.println("# name: " + name);
//        System.out.println("# phone: " + phone);

//        JSON문자열 응답 타입을 수작업 코드에서 Map객체로 변경->produce애트리뷰트를 삭제할 수 있다
            //MAp 객체를 리턴하면 내부적으로 응답 데이터를 JSON데이터로 자동 변환해야 한다고 인식한다
//            Map<String, String> map = new HashMap<>();
//            map.put("email", email);
//            map.put("name", name);
//            map.put("phone", phone);

        //@RequestHeader Map을 사용하여 전체 헤더 정보를 받아 온다
        for (Map.Entry<String, String> entry : headers.entrySet()){
            System.out.println("key: " + entry.getKey() +
                    ", value: " + entry.getValue());
        }
            //리턴 값을 변경된 ResponseEntity로 대체
            //ResponseEntity 객체를 생성하고 생성자 파라미터로 map과 HttpStatus.CREATED를 반환한다
            //HttpStatus.CREATED 는 201, created를 의미한다
//            return new ResponseEntity<>(map, HttpStatus.CREATED);

        //@RequestHeader Map 을 사용함으로 리턴 값의 코드가 변경된다
        return new ResponseEntity<>(new Member(email, name, phone), HttpStatus.CREATED);

//        String reponse =
//                "{\"" +
//                    "email\":\""+email+"\"," +
//                    "\"name\":\""+name+"\",\"" +
//                    "phone\":\"" + phone+
//                "\"}";
//        return reponse;
    }

    @GetMapping("/{member-id}")

    public /*String*/ResponseEntity getMember(@PathVariable("member-id")long memberId) {
       
        System.out.println("# memberId: " + memberId);
        //리턴 값을 변경된 ResponseEntity로 대체
        //HttpStatus.OK 는 200, OK를 의미한다
        return new ResponseEntity<>(HttpStatus.OK);
        //not implementation
//        return null;
    }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/members”)에 매핑된다

    //getMembers() 메서드는 회원 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getMembers() {
        System.out.println("# get Members");
        //리턴 값을 변경된 ResponseEntity로 대체
        //HttpStatus.OK 는 200, OK를 의미한다
        return new ResponseEntity<>(HttpStatus.OK);
        //not implementation
//        return null;
    }
}
  • 위의 코드를 실행하면 error가 발생한다
  • Member class를 생성하라는 메시지가 확인된다
  • new Member에 대한 신규 class를 생성해 주면 해결된다
package com.dreamfactory.exam_controller.member;

public class  Member {
    public Member(String email, String name, String phone) {
    }
}
  • postman으로 get/post를 실행하면 아래와 같은 CLI 메시지가 출력된다

 

 3) HttpServletRequest 객체로 헤더 정보 얻기

  • HttpServletRequest 객체를 이용하면 Request 헤더 정보에 다양한 방법으로 접근이 가능하다
  • HttpServletRequest는 다양한 API를 지원하지만 특정 헤더 정보에 접근하고자 한다면 @RequestHeader 가 더 용이하다
  • orderController에 HttpservletRequest 적용하기
package com.dreamfactory.exam_controller.order;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping(value="v1/orders"/*, produces = MediaType.APPLICATION_JSON_VALUE*/)

public class OrderController {
    @PostMapping

    //postOrder() 메서드는 커피 주문 정보를 등록한다
    public /*String*/ResponseEntity postOrder(HttpServletRequest httpServletRequest,
                                              @RequestParam("memberId") String memberId,
                                              @RequestParam("coffeeId") String coffeeId) {

////        Map<String, String> map = new HashMap<>();
////        map.put("memberId", memberId);
////        map.put("coffeeId", coffeeId);
//
//        return new ResponseEntity(map, HttpStatus.CREATED);

        System.out.println("user-agent: " + httpServletRequest.getHeader("user-agent"));
        return new ResponseEntity<>(new Order(memberId, coffeeId),
                HttpStatus.CREATED);
//        System.out.println("# memberId:" + memberId);
//        System.out.println("# coffeeId:" + coffeeId);
//
//        String reponse =
//                "{\"" +
//                        "memberId\":\"" + memberId + "\"," +
//                        "\"coffeeId\":\"" + coffeeId +
//                        "\"}";
//        return reponse;
    }

    @GetMapping("/{order-id}")
    public /*String*/ResponseEntity getOrder(@PathVariable("order-id") long orderId) {
        System.out.println("# orderId: " + orderId);
        return new ResponseEntity(HttpStatus.OK);
    }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/orders”)에 매핑된다

    //getOrders() 메서드는 주문 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getOrders() {
        System.out.println("# get Orders");
        return new ResponseEntity(HttpStatus.OK);
    }
}
  • 실행하면 error가 발생한다
  • new Order에 대한 신규 class를 생성하면 해결된다
package com.dreamfactory.exam_controller.order;

public class Order {
    public  Order (String memberId, String CoffeeId){

    };
}
  • postman으로 post/get를 실행하면 아래와 같이 CLI가 출력된다

 

 

 

※ 참조 링크

▶ HTTP Header : https://developer.mozilla.org/ko/docs/Web/HTTP/Headers

 

HTTP 헤더 - HTTP | MDN

HTTP 헤더는 클라이언트와 서버가 요청 또는 응답으로 부가적인 정보를 전송할 수 있도록 해줍니다. HTTP 헤더는 대소문자를 구분하지 않는 이름과 콜론 ':' 다음에 오는 값(줄 바꿈 없이)으로 이루

developer.mozilla.org

 HttpServletRequest : https://docs.oracle.com/javaee/7/api/index.html?javax/servlet/http/HttpServletRequest.html 

 

Java(TM) EE 7 Specification APIs

 

docs.oracle.com

https://docs.oracle.com/javaee/7/api/index.html?javax/servlet/http/HttpServletResponse.html 

 

Java(TM) EE 7 Specification APIs

 

docs.oracle.com

User Agent의 유형 : https://gist.github.com/pzb/b4b6f57144aea7827ae4

 

user-agents.txt

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent

 

User-Agent - HTTP | MDN

The User-Agent request header is a characteristic string that lets servers and network peers identify the application, operating system, vendor, and/or version of the requesting user agent.

developer.mozilla.org

▶ 백엔드 서비스에 커스텀 헤더 만들기 : https://cloud.google.com/load-balancing/docs/https/custom-headers?hl=ko 

 

백엔드 서비스에 커스텀 헤더 만들기  |  부하 분산  |  Google Cloud

전역 외부 HTTP(S) 부하 분산기(기본)에서 사용하는 백엔드 서비스의 커스텀 헤더를 구성합니다.

cloud.google.com

HTTP 헤더 및 Application Load Balancer : https://docs.aws.amazon.com/ko_kr/elasticloadbalancing/latest/application/x-forwarded-headers.html

 

HTTP 헤더 및 Application Load Balancer - Elastic Load Balancing

HTTP 요청 및 HTTP 응답은 헤더 필드를 사용하여 HTTP 메시지에 대한 정보를 전송합니다. HTTP 헤더가 자동으로 추가됩니다. 헤더 필드는 콜론으로 구분된 이름-값 페어이며 CR(캐리지 리턴) 및 LF(줄

docs.aws.amazon.com

 

■ 이번 페이지는 https://coding-mid-life.tistory.com/60?category=1287134  에서 작성된 Controller 예제의 개선을 진행한다

 

Spring MVC - Controller (MVC 개요/핸들러 메서드)

1. Controller 클래스 설계 및 구조 생성 API 계층을 Spring MVC 기반의 코드로 구현해 본다  1) 애플리케이션 경계 설정 커피 주문 애플리케이션으로 설정  2) 애플리케이션 기능 요구사항 수집 기능적

coding-mid-life.tistory.com

 

1. MemberComtroller 개선

  • ResponseEntity를 사용한다
    - ResponseEntity는 HttpEntity의 확장 클래스이다
    - HttpStatus 상태 코드를 추가한 전체 HTTP응답을 표현한다(상태코드, 헤더, 본문)
    - @Controller @RestController 애너테이션이 붙은 Controller 클래스의 핸들러 메서의 요청에 대한 응답을 표현한다
    - RestTemplate에서 외부 API통신에 대한 응답을 전달받아서 표현할 경우에 사용한다

  • Map 과 HashMap을 사용한다

 1) 기존 작성 코드

package com.dreamfactory.exam_controller.member;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.sql.SQLOutput;

@RestController

@RequestMapping(value ="v1/members", produces = MediaType.APPLICATION_JSON_VALUE)

public class MemberController {
    @PostMapping
    public String postMember(@RequestParam("email")String email,
                             @RequestParam("name")String name,
                             @RequestParam("phone")String phone) {        
        System.out.println("# email: " + email);
        System.out.println("# name: " + name);
        System.out.println("# phone: " + phone);
       
        //---> code change <---
        String reponse =
                "{\"" +
                    "email\":\""+email+"\"," +
                    "\"name\":\""+name+"\",\"" +
                    "phone\":\"" + phone+
                "\"}";
        return reponse;
    }

    @GetMapping("/{member-id}")    
    public String getMember(@PathVariable("member-id")long memberId) {
        
        System.out.println("# memberId: " + memberId);

        //not implementation
        return null;
    }

    @GetMapping 
    public String getMembers() {
        System.out.println("# get Members");

        //not implementation
        return null;
    }
}
  • 위의 코드에서 다음 부분이 개선이 우선된다
    - JSON 형식으로 응답을 받기 위해 작성된 코드이다
String reponse =
                "{\"" +
                    "email\":\""+email+"\"," +
                    "\"name\":\""+name+"\",\"" +
                    "phone\":\"" + phone+
                "\"}";
        return reponse;
  • 위의 수동 코드를 Map 메서드를 사용하여 개선하였다
//JSON문자열 응답 타입을 수작업 코드에서 Map객체로 변경->produce애트리뷰트를 삭제할 수 있다
//MAp 객체를 리턴하면 내부적으로 응답 데이터를 JSON데이터로 자동 변환해야 한다고 인식한다
Map<String, String> map = new HashMap<>();
map.put("email", email);
map.put("name", name);
map.put("phone", phone);
//리턴 값을 변경된 ResponseEntity로 대체
//ResponseEntity 객체를 생성하고 생성자 파라미터로 map과 HttpStatus.CREATED를 반환한다
//HttpStatus.CREATED 는 201, created를 의미한다
return new ResponseEntity<>(map, HttpStatus.CREATED)
  • 전체적으로 개선된 코드는 아래와 같다
  • getMember()와 getMembers() 핸들러 메서드도 ResponseEntity 객체를 리턴하고 HttpStatus.OK의 응답을 전송하는 것으로 수정되었다
package com.dreamfactory.exam_controller.member;

import ch.qos.logback.classic.util.LogbackMDCAdapter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.sql.SQLOutput;
//@애터테이션에 의해 자동으로 import 된다

import java.util.HashMap;
import java.util.Map;


@RestController
//Spring MVC에서는 특정 클래스에 @RestController 를 추가하면 해당 클래스가 REST API의 리소스(자원, Resource)를
  처리하기 위한 API 엔드포인트로 동작함을 정의한다
//@RestController가 추가된 클래스는 애플리케이션 로딩 시, Spring Bean 으로 등록 해 준다
//REST API란 REST 방식을 통해서 리소스에 접근하기 위한 서비스 API를 지칭한다
//REST(Representational State Transfer)는 HTTP 네트워크 상의 리소스(Resource, 자원)를 정의하고 
  해당 리소스를 URI라는 고유의 주소로 접근하는 접근 방식을 의미한다

@RequestMapping("v1/members"/*,produce=MediaType.APPLICATION_JSON_VALUE*/)
//produces애트리뷰트(Attribute)는 응답 데이터를 어떤 미디어 타입으로 클라이언트에게 전송할 지를 설정한다


public class MemberController {
    @PostMapping

    //postMember() 메서드는 회원 정보를 등록해주는 핸들러 메서드이다
    //클라이언트의 요청 데이터(request body)를 서버에 생성할 때 사용하는 애너테이션이다
    //클라이언트에서 요청 전송 시 HTTP Method 타입을 동일하게 맞춰 주어야 한다(POST)
    public /*String*/ResponseEntity postMember(@RequestParam("email")String email,
                                                  @RequestParam("name")String name,
                                                  @RequestParam("phone")String phone) {
        //@RequestParam() 핸들러 메서드의 파라미터 종류 중 하나이다
        /*클라이언트에서 전송하는 요청 데이터를 쿼리 파라미터(Query Parmeter 또는 Query String),
        폼 데이터(form-data), x-www-form-urlencoded 형식으로 전송하면 서버 쪽에서 전달 받을 때
        사용하는 애너테이션이다*/

//        System.out.println("# email: " + email);
//        System.out.println("# name: " + name);
//        System.out.println("# phone: " + phone);

//        JSON문자열 응답 타입을 수작업 코드에서 Map객체로 변경->produce애트리뷰트를 삭제할 수 있다
            //MAp 객체를 리턴하면 내부적으로 응답 데이터를 JSON데이터로 자동 변환해야 한다고 인식한다
            Map<String, String> map = new HashMap<>();
            map.put("email", email);
            map.put("name", name);
            map.put("phone", phone);
            //리턴 값을 변경된 ResponseEntity로 대체
            //ResponseEntity 객체를 생성하고 생성자 파라미터로 map과 HttpStatus.CREATED를 반환한다
            //HttpStatus.CREATED 는 201, created를 의미한다
            return new ResponseEntity<>(map, HttpStatus.CREATED);


        //클라이언트 쪽에서 JSON 형식의 데이터를 전송 받아야 하기 때문에
        //응답 문자열을 JSON 형식에 맞게 작성한다
//        String reponse =
//                "{\"" +
//                    "email\":\""+email+"\"," +
//                    "\"name\":\""+name+"\",\"" +
//                    "phone\":\"" + phone+
//                "\"}";
//        return reponse;
    }

    @GetMapping("/{member-id}")
    //@GetMapping은 클라이언트가 서버에 리소스를 조회할 때 사용하는 애너테이션이다
    //@GetMapping 애너테이션의 괄호 안에는 몇 가지 애트리뷰트(Attribute)를 사용할 수 있다
    //여기서는 전체 HTTP URI의 일부를 지정했다
    //클라이언트에서 getMember() 핸들러 메서드에 요청을 보낼 경우, 최종 URI는 형태는 아래와 같다
    // /v1/members/{member-id}
    //{member-id}는 회원 식별자를 의미한다
    //클라이언트가 요청을 보낼 때 URI로 어떤 값을 지정하느냐에 따라서 동적으로 바뀌는 값이다

    public /*String*/ResponseEntity getMember(@PathVariable("member-id")long memberId) {
        //getMember() 메서드는 특정 회원의 정보를 클라이언트 쪽에 제공하는 핸들러 메서드이다

        //@PathVariable역시 핸들러 메서드의 파라미터 종류 중 하나이다
        //@PathVariable의 괄호 안에 입력한 문자열 값은 @GetMapping("/{member-id}") 처럼 중괄호({ })
          안의 문자열과 동일해야 한다
        //여기서는 두 문자열 모두 “member-id” 로 동일하게 지정했다
        //두 문자열이 다르면 MissingPathVariableException이 발생한다

        System.out.println("# memberId: " + memberId);
        //리턴 값을 변경된 ResponseEntity로 대체
        //HttpStatus.OK 는 200, OK를 의미한다
        return new ResponseEntity<>(HttpStatus.OK);
        //not implementation
//        return null;
    }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/members”)에 매핑된다

    //getMembers() 메서드는 회원 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getMembers() {
        System.out.println("# get Members");
        //리턴 값을 변경된 ResponseEntity로 대체
        //HttpStatus.OK 는 200, OK를 의미한다
        return new ResponseEntity<>(HttpStatus.OK);
        //not implementation
//        return null;
    }
}
  • 수정된 코드를 실행한 후 postman에서 post로 request한 결과는 아래와 같다
  • map에 의한 파라미터 값과 HttpStatus.CREATED의 결과값 201 Created가 반환된다
  • ResponseEntity를 사용하지 않았던 코드의 결과값 200 OK 가 변경된 결과이다

  • CoffeeController 와 OrderController 도 개선된 코드로 수정한다
  • CoffeeController 
package com.dreamfactory.exam_controller.coffee;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping(value="v1/coffees", produces = MediaType.APPLICATION_JSON_VALUE)
//v1은 버젼을 의미한다. coffees는 데이터 post, get 의 조회 위치를 의미한다

public class CoffeeController {
    @PostMapping

    //postCoffee() 메서드는 커피 정보를 등록해 준다
    public /*String*/ResponseEntity postCoffee(@RequestParam("coffee")String coffee,
                                                 @RequestParam("coffeeId")String coffeeId,
                                                 @RequestParam("korName")String korName,
                                                 @RequestParam("engName")String engName,
                                                 @RequestParam("price")int price) {
//        System.out.println("# coffee:" + coffee);
//        System.out.println("# coffeeId:" + coffeeId);
//        System.out.println("# korName:" + korName);
//        System.out.println("# engName:" + engName);
//        System.out.println("# price:" + price);

        //Map객체로 변경
            Map<String, String> map = new HashMap<>();
            map.put("coffee", coffee);
            map.put("coffeeId", coffeeId);
            map.put("korName", korName);
            map.put("engName", engName);
            map.put("price", String.valueOf(price));

            //return 값을 ResponseEntity로 변경
        return new ResponseEntity<>(map, HttpStatus.CREATED);

//        String reponse =
//                "{\"" +
//                    "coffee\":\""+coffee+"\"," +
//                    "\"coffeeId\":\""+coffeeId+"\"," +
//                    "\"korName\":\""+korName+"\"," +
//                    "\"engName\":\""+engName+"\"," +
//                    "\"price\":\""+price+
//                "\"}";
//        return reponse;
    }

    @GetMapping("/{coffee-id}")

    //getCoffee() 메서드는 커피 정보을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getCoffee(@PathVariable("coffee-id")long coffeeId) {
        System.out.println("# coffeeId: " + coffeeId);
        return new ResponseEntity<>(HttpStatus.OK);
        }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/coffees”)에 매핑된다

    //getCoffees() 메서드는 커피 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getCoffees() {
        System.out.println("# get Coffees");
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

 

  • OrderController
package com.dreamfactory.exam_controller.order;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping(value="v1/orders"/*, produces = MediaType.APPLICATION_JSON_VALUE*/)

public class OrderController {
    @PostMapping

    //postOrder() 메서드는 커피 주문 정보를 등록한다
    public /*String*/ResponseEntity postOrder(@RequestParam("memberId") String memberId,
                                              @RequestParam("coffeeId") String coffeeId) {

        Map<String, String> map = new HashMap<>();
        map.put("memberId", memberId);
        map.put("coffeeId", coffeeId);

        return new ResponseEntity(map, HttpStatus.CREATED);
//        System.out.println("# memberId:" + memberId);
//        System.out.println("# coffeeId:" + coffeeId);
//
//        String reponse =
//                "{\"" +
//                        "memberId\":\"" + memberId + "\"," +
//                        "\"coffeeId\":\"" + coffeeId +
//                        "\"}";
//        return reponse;
    }

    @GetMapping("/{order-id}")
    public /*String*/ResponseEntity getOrder(@PathVariable("order-id") long orderId) {
        System.out.println("# orderId: " + orderId);
        return new ResponseEntity(HttpStatus.OK);
    }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/orders”)에 매핑된다

    //getOrders() 메서드는 주문 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public /*String*/ResponseEntity getOrders() {
        System.out.println("# get Orders");
        return new ResponseEntity(HttpStatus.OK);
    }
}

 

 

※ 참조 링크

▶ ResponseEntity : https://itvillage.tistory.com/44

 

ResponseEntity 알아보기

ResponseEntity란? ResponseEntity는 HttpEntity의 확장 클래스로써 HttpStatus 상태 코드를 추가한 전체 HTTP 응답(상태 코드, 헤더 및 본문)을 표현하는 클래스입니다. ResponseEntity를 어디에 사용할 수 있나..

itvillage.tistory.com

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html

 

ResponseEntity (Spring Framework 5.3.21 API)

Extension of HttpEntity that adds an HttpStatus status code. Used in RestTemplate as well as in @Controller methods. In RestTemplate, this class is returned by getForEntity() and exchange(): ResponseEntity entity = template.getForEntity("https://example.co

docs.spring.io

 

1. Controller 클래스 설계 및 구조 생성

  • API 계층을 Spring MVC 기반의 코드로 구현해 본다

 1) 애플리케이션 경계 설정

  • 커피 주문 애플리케이션으로 설정

 2) 애플리케이션 기능 요구사항 수집

  • 기능적으로 요구사항을 정리해 본다

 3) 패키지 구조 생성

  • 기능 기반 패키지 구조(package-by-feature)
    - 애플리케이션에서 구현해야 하는 기능을 기준으로 패키지를 구성하는 것을 말한다
    - 패키지를 커피 / 회원 / 주문 등 기능으로 구분할 수 있다
  • 계층 기반 패키지 구조(package-by-layer)
    - 패키지를 하나의 계층(Layer)으로 보고 클래스들을 계층별로 묶어서 관리하는 구조를 말한다
    - controller / model / repository / service 등 계층으로 구분할 수 있다
  • 테스트와 리팩토링이 용이하고, 향후에 마이크로 서비스 시스템으로의 분리가 상대적으로 용이기능 기반 패키지 구조 사용을 권장한다

 4) 애플리케이션의 기능 요구사항 및 클래스 정리

  • REST API 기반의 애플리케이션에서는 일반적으로 애플리케이션이 제공해야 될 기능을 리소스(Resource, 자원)로 분류한다
  • 리소스에 해당하는 Controller 클래스를 작성한다

 5) 엔트리포인트(Entrypoint) 클래스 작성

  • Spring Boot 기반의 애플리케이션이 정상적으로 실행되기 위해서 가장 먼저 해야될 일은 main() 메서드가 포함된 애플리케이션의 엔트리포인트(Entrypoint, 애플리케이션 시작점)를 작성하는 것이다
  • 부트스트랩(Bootstrap)은 애플리케이션이 실행되기 전에 여러가지 설정 작업을 수행하여 실행 가능한 애플리케이션으로 만드는 단계를 의미한다
  • 엔트리포인트는 spring initializr를 사용하여 프로젝트를 생성하면 자동으로 만들어 진다

  • exam_controller > src > main > java > com.dreamfactory.exam_controller > ExamControllerApplication 으로 자동 생성된다
//엔트리포인트 클래스

package com.dreamfactory.exam_controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//자동구성을 활성화 해 준다
//애플리케이션 패키지 내에서 @Component가 붙은 클래스를 검색한 후(scan), Spring Bean으로 등록하는 기능을 
  활성화 해 준다
//@Configuration 이 붙은 클래스를 자동으로 찾아주고, 추가적으로 Spring Bean을 등록하는 기능을 활성화 해 준다

public class ExamControllerApplication {

	public static void main(String[] args) {
		SpringApplication.run(ExamControllerApplication.class, args);
	}
	//SpringApplication.run : Spring 애플리케이션을 부트스트랩하고, 실행하는 역할을 한다
}

 

 6) 애플리케이션의 Controller 구조 작성

  • 회원관리를 위한 MemberController 구조 작성
package com.dreamfactory.exam_controller.member;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//@애터테이션에 의해 자동으로 import 된다

@RestController
//Spring MVC에서는 특정 클래스에 @RestController 를 추가하면 해당 클래스가 REST API의 리소스(자원, Resource)를
  처리하기 위한 API 엔드포인트로 동작함을 정의한다
//@RestController가 추가된 클래스는 애플리케이션 로딩 시, Spring Bean 으로 등록 해 준다

@RequestMapping
//@RequestMapping 은 클라이언트의 요청과 클라이언트 요청을 처리하는 핸들러 메서드(Handler Method)를 매핑해주는
  역할을 한다
//Controller 클래스 레벨에 추가하여 클래스 전체에 사용되는 공통 URL(Base URL)을 설정할 수 있다

public class MemberController {

}
  • 커피정보를 위한 CoffeeController 구조 작성
package com.dreamfactory.exam_controller.coffee;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1/coffees")
//v1은 버젼을 의미한다. coffees는 데이터 post, get 의 조회 위치 URI를 지정한다
//Controller 클래스 레벨에 추가하여 클래스 전체에 사용되는 공통 URL(Base URL)을 설정할 수 있다.

public class CoffeeController {

}
  • 주문관리를 위한 OrderController 구조 작성
package com.dreamfactory.exam_controller.order;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping

public class OrderController {

}

 

[참조 자료]

※ REST API란?

  • REST(Representational State Transfer)는 HTTP 네트워크 상의 리소스(Resource, 자원)를 정의하고 해당 리소스를 URI라는 고유의 주소로 접근하는 접근 방식을 의미한다
  • REST API란 REST 방식을 통해서 리소스에 접근하기 위한 서비스 API를 지칭한다

 REST에서 의미하는 리소스

  • REST에서 의미하는 자원은 데이터베이스에 저장된 데이터, 문서, 이미지, 동영상 등 HTTP 통신을 통해 주고 받을 수 있는 모든 것을 의미한다

 URI(Uniform Resource Identifier)와 URL(Uniform Resource Locator)

  • URI는 네트워크 상에 있는 특정 리소스를 식별하는 통합 자원 식별자(Uniform Resource Identifier)를 의미한다
  • URL은 인터넷에 있는 리소스를 나타내는 통합 리소스 식별자를 의미한다
  • 일상적인 웹 상의 주소는 URL을 의미한다
  • URI는 URL의 상위 개념으로 볼 수 있다
  • URI는 리소스를 식별하는 식별자 역할을 하고, URL은 리소스의 위치를 가리킨다
    - http://www.itivllage.tistory.com/manage? id = 1 에서
    - http://www.itivllage.tistory.com/manage'까지는 리소스의 위치를 가리키는 URL이고
    - http://www.itivllage.tistory.com/manage? id = 1는 리소스를 식별하기 위한 'id = 1'이라는 고유 식별자가 붙었으므로 URI이다

※ HTTP 에서 REST API 서비스를 만드는 경우의 REST API URI 작성 규칙

  • URI의 마지막이 '/' 로 끝나지 않게 한다
    - http://www.dreamfactory.com/coffees   (good)
    - http://www.dreamfactory.com/coffees/ (bad)
  • 동사 보다는 명사를 사용한다
    - http://www.dreamfactory.com/coffees             (good)
    - http://www.dreamfactory.com/coffees/update (bad) 
  • 단수형 보다는 복수형 명사를 사용한다
    - http://www.dreamfactory.com/coffees   (good)
    - http://www.dreamfactory.com/coffee     (bad)
  • URI는 기본 소문자를 사용한다
  • 언더스코어( _ ) 대신에 하이픈(-)을 사용한다
  • 파일 확장자는 URI에 포함하지 않는다

 

※ 참조 링크

 

RESTful API의 URL 작성 규칙

REST API란? REST(Representational State Transfer)는 HTTP 네트워크 상의 리소스(Resource, 자원)를 정의하고 해당 리소스를 URI라는 고유의 주소로 접근하는 접근 방식을 의미하며, REST API란 REST 방식을 통..

itvillage.tistory.com

 

@SpringBootApplication 의 역할

[코드 1-1] @SpringBootApplication public class CoffeeApplication { public static void main(String[] args) { SpringApplication.run(CoffeeApplication.class, args); } } [코드 1-1]과 같이 Spring Boot Ap..

itvillage.tistory.com

Spring Boot 애플리케이션의 부트스트랩(Bootstrap) 과정: https://itvillage.tistory.com/37

 

Spring Boot 애플리케이션의 부트스트랩(Bootstrap) 과정 알아보기

Spring Boot에서의 부트스트랩이란? 일반적으로 부트스트랩(Bootstrap)이란 어떠한 과정이 시작되어 알아서 진행되는 일련의 과정을 의미합니다. 컴퓨터의 부팅 과정을 생각해보면 이해가 쉬울것입

itvillage.tistory.com

 

2. 핸들러 메서드(Handler Method)

postman 에서 request 결과

  • MemberController에 클라이언트의 요청을 처리할 핸들러 메서드(Handler Method)가 아직 없기 때문에 localhost를 요청해도 에러가 발생한다
  • 작업 중인 애플리케이션은 REST API 기반 애플리케이션이기 때문에 응답 메시지는 JSON 형식으로 클라이언트에게 전달된다

 1) MemberController의 핸들러 메서드(Handler Method) 작성

  • 회원 이메일 주소: email
  • 회원 이름: name
  • 회원 전화번호: phoneNumber
  • 위의 정보를 기준으로 작성한다
package com.dreamfactory.exam_controller.member;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.sql.SQLOutput;
//@애터테이션에 의해 자동으로 import 된다

@RestController

@RequestMapping(value ="v1/members", produces = MediaType.APPLICATION_JSON_VALUE)
//produces애트리뷰트(Attribute)는 응답 데이터를 어떤 미디어 타입으로 클라이언트에게 전송할 지를 설정한다
//JSON 형식의 데이터를 응답 데이터로 전송하기 위해 MediaType.APPLICATION_JSON_VALUE 값을 설정했다
//이 값을 설정하지 않으면 현재 코드에서는 JSON 형식의 데이터를 응답으로 전송하지 않고, 문자열 자체를 전송한다


public class MemberController {
    @PostMapping

    //postMember() 메서드는 회원 정보를 등록해주는 핸들러 메서드이다
    //클라이언트의 요청 데이터(request body)를 서버에 생성할 때 사용하는 애너테이션이다
    //클라이언트에서 요청 전송 시 HTTP Method 타입을 동일하게 맞춰 주어야 한다(POST)
    public String postMember(@RequestParam("email")String email,
                             @RequestParam("name")String name,
                             @RequestParam("phone")String phone) {
        //@RequestParam() 핸들러 메서드의 파라미터 종류 중 하나이다
        /*클라이언트에서 전송하는 요청 데이터를 쿼리 파라미터(Query Parmeter 또는 Query String),
        폼 데이터(form-data), x-www-form-urlencoded 형식으로 전송하면 서버 쪽에서 전달 받을 때
        사용하는 애너테이션이다*/

        System.out.println("# email: " + email);
        System.out.println("# name: " + name);
        System.out.println("# phone: " + phone);

        //클라이언트 쪽에서 JSON 형식의 데이터를 전송 받아야 하기 때문에
        //응답 문자열을 JSON 형식에 맞게 작성한다
        String reponse =
                "{\"" +
                    "email\":\""+email+"\"," +
                    "\"name\":\""+name+"\",\"" +
                    "phone\":\"" + phone+
                "\"}";
        return reponse;
    }

    @GetMapping("/{member-id}")
    //@GetMapping은 클라이언트가 서버에 리소스를 조회할 때 사용하는 애너테이션이다
    //@GetMapping 애너테이션의 괄호 안에는 몇 가지 애트리뷰트(Attribute)를 사용할 수 있다
    //여기서는 전체 HTTP URI의 일부를 지정했다
    //클라이언트에서 getMember() 핸들러 메서드에 요청을 보낼 경우, 최종 URI는 형태는 아래와 같다
    // /v1/members/{member-id}
    //{member-id}는 회원 식별자를 의미한다
    //클라이언트가 요청을 보낼 때 URI로 어떤 값을 지정하느냐에 따라서 동적으로 바뀌는 값이다

    public String getMember(@PathVariable("member-id")long memberId) {
        //getMember() 메서드는 특정 회원의 정보를 클라이언트 쪽에 제공하는 핸들러 메서드이다

        //@PathVariable역시 핸들러 메서드의 파라미터 종류 중 하나이다
        //@PathVariable의 괄호 안에 입력한 문자열 값은 @GetMapping("/{member-id}") 처럼 중괄호({ }) 안의 문자열과 동일해야 한다
        //여기서는 두 문자열 모두 “member-id” 로 동일하게 지정했다
        //두 문자열이 다르면 MissingPathVariableException이 발생한다

        System.out.println("# memberId: " + memberId);

        //not implementation
        return null;
    }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/members”)에 매핑된다

    //getMembers() 메서드는 회원 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public String getMembers() {
        System.out.println("# get Members");

        //not implementation
        return null;
    }
}
  • 쿼리 파라미터(Query Parameter 또는 QueryString)
    - 요청 URL에서 ‘?’를 기준으로 붙는 key/value 쌍의 데이터를 말한다
    - ex) http://localhost:8080/coffees/1?page=1&size=10
  • RestController
    - Spring MVC에서 웹 요청을 하기 위한 준비가 완료되었음을 의미한다
  • RequestMapping
    - RequestMapping(value="파일경로", method={HTTP 요청값}) 의 형식으로 작성한다
    - HTTP 값을 지정하지 않으면 모든 HTTP요청에 매핑된다
    - Request에 대한 헤더 값을 지정하면 매핑 범위를 좁힐 수 있다
헤더 값을 지정하여 범위를 좁힌 예)

@RequestMapping(value = "/ex/dream", headers = "key=val", method = GET)
@ResponseBody
public String getDreamWithHeader() {
    return "Get some Dream with Header";
}
HTTP POST에 요청된 예)

@RequestMapping(value = "/ex/dream", method = POST)
@ResponseBody
public String postDream() {
    return "Post some Dream";
}
headers속성을 사용하여 헤더를 여러개 지정할 수 있다 

@RequestMapping(
  value = "/ex/dream", 
  headers = { "key1=val1", "key2=val2" }, method = GET)
@ResponseBody
public String getDreamWithHeaders() {
    return "Get some Dream with Header";
}

 

※ MemberController : postMember() 요청 및 응답

  • HTTP POST Method와 요청 URI를 입력한다
  • [Body] 탭에서 ‘x-www-form-urlencoded’ 형식의 요청 데이터를 KEY/VALUE 형태로 입력한다
  • JSON 형식의 응답 데이터를 전달 받을 수 있다
  • 우측 중간을 보면 ‘200’, ‘OK’ 라는 값을 통해서 클라이언트가 정상적으로 응답을 전달 받았음을 알 수 있다

 

 2) CoffeeController의 핸들러 메서드(Handler Method) 작성

  • 커피 식별자: coffee
  • 식별자: coffeeId
  • 커피명(영문): engName
  • 커피명(한글): korName
  • 가격: price
package com.dreamfactory.exam_controller.coffee;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value="v1/coffees", produces = MediaType.APPLICATION_JSON_VALUE)
//v1은 버젼을 의미한다. coffees는 데이터 post, get 의 조회 위치를 의미한다

public class CoffeeController {
    @PostMapping

    //postCoffee() 메서드는 커피 정보를 등록해 준다
    public String postCoffee(@RequestParam("coffee")String coffee,
                             @RequestParam("coffeeId")String coffeeId,
                             @RequestParam("korName")String korName,
                             @RequestParam("engName")String engName,
                             @RequestParam("price")int price) {
        System.out.println("# coffee:" + coffee);
        System.out.println("# coffeeId:" + coffeeId);
        System.out.println("# korName:" + korName);
        System.out.println("# engName:" + engName);
        System.out.println("# price:" + price);

        String reponse =
                "{\"" +
                    "coffee\":\""+coffee+"\"," +
                    "\"coffeeId\":\""+coffeeId+"\"," +
                    "\"korName\":\""+korName+"\"," +
                    "\"engName\":\""+engName+"\"," +
                    "\"price\":\""+price+
                "\"}";
        return reponse;
    }

    @GetMapping("/{coffee-id}")

    //getCoffee() 메서드는 커피 정보을 클라이언트에게 제공하는 핸들러 메서드이다
    public String getCoffee(@PathVariable("coffee-id")long coffeeId) {
        System.out.println("# coffeeId: " + coffeeId);
        return null;
        }

    @GetMapping //별도의 URI를 지정해주지 않았기 때문에 클래스 레벨의 URI(“/v1/coffees”)에 매핑된다

    //getCoffees() 메서드는 커피 목록을 클라이언트에게 제공하는 핸들러 메서드이다
    public String getCoffees() {
        System.out.println("# get Coffees");
        return null;
    }
}

 

postman - v1/coffees post

 3) OrderController의 핸들러 메서드(Handler Method) 작성

  • 회원 식별자: memberId
  • 커피 식별자: coffeeId
package com.dreamfactory.exam_controller.order;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value="v1/orders", produces = MediaType.APPLICATION_JSON_VALUE)

public class OrderController {
    @PostMapping

    //postOrder() 메서드는 주문 정보를 등록한다
    public String postOrder(@RequestParam("memberId")String memberId,
                             @RequestParam("coffeeId")String coffeeId) {
        System.out.println("# memberId:" + memberId);
        System.out.println("# coffeeId:" + coffeeId);

        String reponse =
                "{\"" +
                    "memberId\":\""+memberId+"\"," +
                    "\"coffeeId\":\""+coffeeId+
                "\"}";
        return reponse;
    }

    @GetMapping("/{order-id}")
    public String getOrder(@PathVariable("order-id")long orderId) {
        System.out.println("# orderId: " + orderId);
        return null;
    }

    @GetMapping
    public String getOrders() {
        System.out.println("# get Orders");
        return null;
    }
}

※ 식별자(Identifier)

  • 어떤 데이터를 식별할 수 있는 고유값을 의미한다
  • 기본키(Primary key)는 대표적인 식별자 중 하나이다
  •  API 계층에서 사용하는 memberId, coffeeId 등은 데이터베이스의 테이블에 저장되는 로우(row)의 식별자인 기본키(Primary key)가 된다

 

참조 링크

▶ 핸들러메서드 파라미터 : https://itvillage.tistory.com/41

 

Controller 핸들러 메서드의 Argument 알아보기

Controller의 핸들러 메서드는 다양한 유형의 Argument(인수)를 지원합니다. 그 중에서 REST API 애플리케이션에서 자주 사용되는 유형의 Argument를 간단히 살펴보도록 하겠습니다. Method Argument 설명 @Reque

itvillage.tistory.com

 미디어 타입(Media Type) : https://ko.wikipedia.org/wiki/%EB%AF%B8%EB%94%94%EC%96%B4_%ED%83%80%EC%9E%85

 

미디어 타입 - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

▶ MIME 타입 : https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_Types

 

MIME 타입 - HTTP | MDN

MIME 타입이란 클라이언트에게 전송된 문서의 다양성을 알려주기 위한 메커니즘입니다: 웹에서 파일의 확장자는 별  의미가 없습니다. 그러므로, 각 문서와 함께 올바른 MIME 타입을 전송하도

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types

 

MIME 타입의 전체 목록 - HTTP | MDN

다음은 일반적인 확장자로 정렬된, 문서 타입과 관련된 MIME 타입의 포괄적인 목록입니다.

developer.mozilla.org

 HTTP POST Method의 요청 데이터 형식 : https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/POST

 

POST - HTTP | MDN

HTTP POST 메서드는 서버로 데이터를 전송합니다. 요청 본문의 유형은 Content-Type 헤더로 나타냅니다.

developer.mozilla.org

HTTP 응답 상태(Response Status) : https://developer.mozilla.org/ko/docs/Web/HTTP/Status

 

HTTP 상태 코드 - HTTP | MDN

HTTP 응답 상태 코드는 특정 HTTP 요청이 성공적으로 완료되었는지 알려줍니다. 응답은 5개의 그룹으로 나누어집니다: 정보를 제공하는 응답, 성공적인 응답, 리다이렉트, 클라이언트 에러, 그리고

developer.mozilla.org

 

1. Spring MVC

 1) 개요

  • 소프트웨어 설계에서 세 가지 구성 요소인 모델(Model), 뷰(View), 컨트롤러(Controller)를 이용한 설계 방식을 말한다
  • 웹 계층을 담당하는 몇가지 모듈 중에서 서블릿(Servlet) API를 기반으로 클라이언트의 요청을 처리하는 모듈을 spring-webmvc라고 한다
  • Spring Web MVC를 줄여서 'Spring MVC' 라고 한다
  • Spring MVC가 웹 프레임워크의 한 종류이기 때문에 'Spring MVC 프레임워크'라고도 한다
  • 클라이언트의 요청을 편리하게 처리해주는 프레임워크이다.

※ 서블릿(Servlet)

  • 클라이언트의 요청을 처리하도록 특정 규약에 맞추어서 Java 코드로 작성하는 클래스 파일이다
  • 아파치 톰캣(Apache Tomcat)은 서블릿들이 웹 애플리케이션으로 실행이 되도록 해주는 서블릿 컨테이너(Servlet Container) 중 하나이다
  • Spring MVC 내부에서는 서블릿을 기반으로 웹 애플리케이션이 동작한다

 2) 모델(MODEL)

  • Spring MVC 기반의 웹 애플리케이션은 클라이언트의 요청을 전달 받으면 요청 사항을 처리하기 위한 작업을 한다
  • 작업 완료 후 클라이언트에게 응답으로 돌려주는 처리한 작업의 결과 데이터를 Model이라고 한다
  • 클라이언트의 요청 사항을 구체적으로 처리하는 영역을 서비스 계층(Service Layer)이라고 한다
  • 요청 사항을 처리하기 위해 Java 코드로 구현한 것을 비즈니스 로직(Business Logic)이라고 한다

 3) 뷰(View)

  • Model 데이터를 이용해서 웹브라우저 같은 클라이언트 애플리케이션의 화면에 보여지는 리소스(Resource)를 제공하는 역할을 한다
  • View의 형태별 종류
    ▶ HTML 페이지의 출력
     - 클라이언트 애플리케이션에 보여지는 HTML 페이지를 직접 렌더링해서 클라이언트 측에 전송하는 방식이다
     - 기본적인 HTML 태그로 구성된 페이지에 Model 데이터를 채워서 최종 HTML 페이지를 만든 후 클라이언트 측에 전송한다
     - Spring MVC에서 지원하는 HTML 페이지 출력 기술에는 Thymeleaf, FreeMarker, JSP + JSTL, Tiles 등이 있다

     PDF, Excel 등의 문서 형태로 출력
     -  Model 데이터를 가공해서 PDF 문서나 Excel 문서를 만들어서 클라이언트 측에 전송하는 방식이다
     -  문서 내에서 데이터가 동적으로 변경되어야 하는 경우 사용할 수 있다

    ▶ XML, JSON 등 특정 형식의 포맷으로의 변환
     - Model 데이터를 특정 프로토콜 형태로 변환해서 클라이언트 측에 전송하는 방식이다
     - 특정 형식의 데이터만 전송하고, 프런트엔드 측에서 이 데이터를 기반으로 HTML 페이지를 만드는 방식이다
     - 프런트엔드 영역과 백엔드 영역이 명확하게 구분되므로 개발 및 유지보수가 상대적으로 용이하다
     - 프런트엔드 측에서 비동기 클라이언트 애플리케이션을 만드는 것이 가능해진다

※ JSON(JavaScript Object Notation)

  • JSON은 Spring MVC에서 클라이언트 애플리케이션과 서버 애플리케이션이 주고 받는 데이터 형식이다
  • 과거에는 XML 형식의 데이터가 많이 사용되었다
  • 현재는 XML보다 상대적으로 가볍고, 간결한 JSON 형식을 사용하고 있는 추세이다
  • JSON은 {"속성", "값"} 의 형태로 작성된다

 

 4) 컨트롤러(Controller)

  • 클라이언트 측의 요청을 직접적으로 전달 받는 엔드포인트(Endpoint)이다
  • Model 과 View 중간에서 상호 작용을 해주는 역할을 한다
  • 클라이언트 측의 요청을 전달 받아서 비즈니스 로직을 거친 후에 Model 데이터가 만들어지면 View로 전달하는 역할을 한다

 

2. Spring MVC의 동작 방식

Client가 요청 데이터 전송 → Controller가 요청 데이터 수신 → 비즈니스 로직 처리 → Model 데이터 생성

→ Controller에게 Model 데이터 전달 → Controller가 View에게 Model 데이터 전달 → View가 응답 데이터 생성

 

  • DispatcherServlet이 역활이 많아 보이지만 요청에 대한 처리는 다른 구성 요소들에게 위임(Delegate)하고 있다
  • DispatcherServlet이 애플리케이션의 가장 앞단에 배치되어 다른 구성요소들과 상호작용하면서 클라이언트의 요청을 처리하는 패턴을 Front Controller Pattern이라고 한다

 

 

 

 

※ 참조 링크

▶ JSON : https://ko.wikipedia.org/wiki/JSON

 

JSON - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

JOSN을 JAVA로 변환 : https://json2csharp.com/code-converters/json-to-pojo

 

JSON to POJO Object Online Converter - Json2CSharp Toolkit

 

json2csharp.com

동작방식 추가 : https://itvillage.tistory.com/entry/Spring-MVC%EC%9D%98-%EB%8F%99%EC%9E%91-%EB%B0%A9%EC%8B%9D-%EC%B6%94%EA%B0%80-%EC%84%A4%EB%AA%85

 

Spring MVC의 동작 방식 추가 설명

Handler 용어의 의미 핸들(Handle) 이라고 하면 일반적으로 자동차의 핸들을 제일 먼저 떠올릴 수 있는데, 자동차의 핸들은 운전자가 직접 핸들을 움직이면서 직접적으로 자동차의 주행을 처리하는

itvillage.tistory.com

 

  • Question
문자열을 입력받아 문자열을 구성하는 각 단어의 첫 글자가 대문자인 문자열을 리턴
  • Code
public class Question6_letterCapitalize {
    public static void main(String[] args) throws Exception{
        //문자열을 입력받을 객체를 생성
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        //문자열을 출력할 객체를 생성
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        //문자열을 입력받을 변수를 생성
        String words = br.readLine();
        String arr[] = words.split(" ");

        //조건 생성
        if(arr.length == 0 || words.isBlank()) {  //빈 문자열이거나 공백이면

            bw.write(words);  //그대로 출력한다

        } else {
            Pattern pattern = Pattern.compile("([a-z])([a-z]*)");  //모든 문자열에 대한 정규식

            Matcher matcher = pattern.matcher(words);  //pattern과 일치하는 문자열을 찾아서 객체에 전달

            //위의 두 코드를 하나로 합칠 수 있다
//            Matcher match = Pattern.compile("([a-z])([a-z]*)", Pattern.CASE_INSENSITIVE).matcher(words);

            StringBuffer str = new StringBuffer();  //변경한 문자열을 저장할 공간을 생성한다

            while (matcher.find()) {  //matcher.find()) 메서드로 매칭되는 위치로 이동한다

                //appendReplacement 메서드로 (str, 바꿀 문자열)을 통해 바뀐 문자열을 str에 저장한다
                matcher.appendReplacement(str, matcher.group(1).toUpperCase() + matcher.group(2).toLowerCase());
            }
            matcher.appendTail(str);  //더이상 찾을 문자열이 없으므로 꼬리를 붙이기 위한 메서드를 사용한다
            bw.write(str.toString());  //str 값을 String으로 변환하여 출룍한다
        }
//        bw.flush();//남아있는 데이터를 모두 출력
        bw.close();//스트림 닫음
    }
}
  • Result
> Task :classes
java  is good 

> Task :Question6_letterCapitalize.main()
Java  Is Good
  • Question
문자열을 입력받아 순서가 뒤집힌 문자열을 리턴
  • Code
public class Question4_firstReverse {
    public static void main(String[] args) {

        //문자열을 입력받는다
        Scanner sc = new Scanner(System.in);
        String words = sc.nextLine();

        //입력받은 문자열을 뒤집는다
        StringBuffer sb = new StringBuffer(words);
        //StringBuffer : 생성자의 파라미터로 문자열을 입력받는다
        
        String wordsReverse = sb.reverse().toString();
        //reverse() 메서드로 객체의 reference를 반환한다
//        String wordsReverse = String.valueOf(sb.reverse()); 
              //sb.reverse().toString();와 같은 결과 값 반환

        System.out.println(wordsReverse);
    }
}
  • Result
Java 명령문
문령명 avaJ
  • Question
문자열을 입력받아 문자열을 구성하는 각 단어의 첫 글자로 이루어진 문자열을 리턴
  • Code
public class Question4_firstCharacter {
    public static void main(String[] args) {
        //문자열을 입력받는다
        Scanner sc = new Scanner(System.in);
        String word = sc.nextLine();

        //문자열을 ""를 기준으로 배열로 변환한다
        String[] wordArr = word.split(" ");


        //배열 길이만큼 반복하며 단어의 첫번째 문자를 구한다
        for (int i=0; i< wordArr.length; i++) {

            char firstArr = wordArr[i].charAt(0);

            System.out.print(firstArr);

        }
    }
  • Result
> Task :classes
Hello welcome to develop world!!

> Task :Question4_firstCharacter.main()
Hwtdw

1. charAt( )

  • 반환하는 조건에 정수 인덱스 값을 매개 변수로 사용한다
  • 해당 인덱스에 있는 문자를 반환한다
  • String 클래스 메서드와 해당 반환 유형은 char 값이다
public class String_getTextOfLocation {

    //charAt() 사용하여 문자 출력
    public static Character getFirstCharacter(String str){

        if(str == null || str.length() == 0) {  //문자열이 null이거나 문자열 길이가 0이면
            return null;  //null을 반환
        } else {
            return (Character) str.charAt(0);  //그렇지 않으면 문자열의 0번째 문자를 반환
        }
    }
    public static void main(String[] args)
    {
        String str = "Hello welcome to develop world!!";
        System.out.println("문자열 : " + str);
        System.out.print("문자열에 첫번째 문자는 : " + getFirstCharacter(str));
    }
}


문자열 : Hello welcome to develop world!!
문자열에 첫번째 문자는 : H

 

2. substring( )

  • startIdx에서 시작하는 하위 문자열부터 나머지 모든 문자를 반환한다
  • startIdx, endIdx를 전달하면 startIdx에서 시작하여 endIdx-1번째 문자까지 반환한다
public String substring(int startIdx)
public String substring(int startIdx, int endIdx)
public class String_getTextOfLocation_substring {
    public static void main(String[] args){

        String str = "Hello welcome to develop world!!";
                    //01234567890123456789012345678901
        System.out.println("문자열 : " + str);

        System.out.println("시작점 index(3): " + str.substring(3));  //4번째 문자부터 전체 반환

        System.out.println("index(2)부터 index(6)까지의 문자 : " + str.substring(2, 7));  //3번째 문자부터 6번째 앞 문자까지
        System.out.println("index(3)부터 index(7)까지의 문자 : " + str.substring(3, 8));  //3번째 문자부터 8번째 앞 문자까지


        System.out.print("문자열에 첫번째 문자는 : " + getFirstCharacter(str));  //첫번째 문자열 가져오기

    }

    public static String getFirstCharacter(String str)
    {
        if(str == null || str.length() == 0)  //빈 문자열일 경우의 조건
            return null;
        else
            return str.substring(0, 1);
    }
}



문자열 : Hello welcome to develop world!!
시작점 index(3): lo welcome to develop world!!
index(2)부터 index(6)까지의 문자 : llo w
index(3)부터 index(7)까지의 문자 : lo we
문자열에 첫번째 문자는 : H

 

3. toCharArray( )

  • index 값을 사용하여 배열의 요소에 액세스할 수 있다
  • 문자열을 char 데이터 유형의 배열로 변환할 수 있다면 index를 사용하여 모든 요소를 ​​가져올 수 있다
  • 인덱스 0을 통해 첫 번째 문자를 가져올 수 있다
  • toCharArray() 메서드를 사용하여 문자열을 문자 배열로 변환한다
public class String_getTextOfLocation_toCharArray {
    public static Character getFirstCharacter(String str)
    {
        if(str == null || str.length() == 0)  //빈 문자열일 경우 null 반환
            return null;
        else
        {
            char[] charArr = str.toCharArray();  //문자열을 배열로 변환
            return charArr[0];
        }
    }
    public static void main(String[] args)
    {
        String str = "Hello welcome to develop world!!";
        System.out.println("문자열 : " + str);
        System.out.print("문자열에 첫번째 문자는 : " + getFirstCharacter(str));
    }
}


문자열 : Hello welcome to develop world!!
문자열에 첫번째 문자는 : H

1. 기본적인 정규식

정규식 내용 예시
. 임의의 문자 1개를 의미  
^ 시작을 의미, [ ] 괄호 안에 있다면 일치하지 않는 부정의 의미로 사용 ^a : a로 시작하는 단어
[^a] : a가 아닌 철자인 문자 1개
$ $앞의 문자열로 문자가 끝나는지를 의미 a$ : a로 끝나는 단어
[ ] [ ] 안의 문자가 있는지를 확인 [ab][cd] : a,b중 한 문자와 c,d중 한 문자
-> ac ad bc bd 
- 시작부터 마지막까지 문자나 숫자를 의미 [a-z] : 알파벳 소문자 a부터 z까지
[a-z0-9] : 알파벳 소문자 전체,0~9 중 한 문자
| 또는, OR [a|b] : a 혹은 b
( ) 그룹 01 (0|1) : 01뒤에 0 또는 1이 들어간다
-> 010(o), 011(o), 012(x)
{ } 반복하는 개수를 의미 a{3}b : a가 3번 온 후 b가 온다
-> aab(x), aaab(o), aaaab(o)
\b 공백, 탭, ",", "/" 등을 의미한다 apple\b : apple뒤에 공백 탭등이 있다
-> apple juice (o), apple.com (x)
\B \b의 부정, 공백, 탭 등이 아닌 문자인 경우 매치 apple\b
-> apple.com (o)
\d 0~9 사이의 숫자 [0-9]와 동일  
\D \d의 부정 의미, 숫자가 아닌 어떤 문자, [^0-9]와 동일  
\s 공백, 탭  
\S 공백, 탭이 아닌 문자  
\w 알파벳 대소문자+숫자+"_",  [a-zA-Z_0-9]와 동일  
\W \w의 부정,  [^a-zA-Z_0-9]와 동일  

 

2. 자주 사용하는 정규식

정규식 내용 예시
? 앞의 표현식이 0개 이상 a1? : 1이 있을수도 없을수도 있다
-> a (o), a1(o), a2(o)
* 앞의 표현식이 0개 이상 a1* : 1이 있을수도 없을수도 있다
-> a (o), a1(o), a2(o)
+ 1개 이상 a1* : 1이 1개 이상있다
-> a (x), a1(o), a11(o)
{n} n개 있다 a{3} : a가 3개 있다
-> aa(x), aaa(o), aaaa(o)
{n, m} n개 이상 m개 이하 a{3,5} : a가 3개 or 4개 or 5개 있다
-> aa(x), aaa(o), aaaa(o), aaaaaaa(o)
{n,} n개 이상 (m제거) a{3,} : a가 3개 이상 있다
-> aa(x), aaa(o)

1. indexOf( )

  • 대상 문자열에서 찾을 문자의 index 값을 반환한다
indexOf(String str) 대상 문자열에 인자값으로 주어지는 String값이 있는지 검색
indexOf( char ch) 대상 문자열에 인자값으로 주어지는 char값이 있는지 검색
indexOf(String str, int fromIndex) 대상 문자열에 첫번째 인자값으로 주어지는 String값이 있는지 두번째 인자값의 index부터 검색
indexOf(char ch, int fromIndex) 대상 문자열에 첫번째 인자값으로 주어지는 char값이 있는지 두번째 인자값의 index부터 검색
public class String_indexOf {
    public static void main(String[] args) {

    String str = "Hello welcome to develop world!!";
                //01234567890123456789012345678901

        System.out.println(str.indexOf("welcome")); //문자열 검색 -> 6
        System.out.println(str.indexOf("to")); //문자열 검색 -> 14

        System.out.println(str.indexOf("t")); //문자 검색 -> 14
        System.out.println(str.indexOf("w")); //문자 검색 -> 6

        System.out.println(str.indexOf("welcome",10)); //문자열을 10번째 index부터 검색 -> -1
        System.out.println(str.indexOf("to",10)); //문자열을 10번째 index부터 검색 -> 14
        System.out.println(str.indexOf("world",10)); //문자열을 10번째 index부터 검색 -> 25

        System.out.println(str.indexOf("t",10)); //문자를 10번째 index부터 검색 -> 14
        System.out.println(str.indexOf("w",10)); //문자를 10번째 index부터 검색 -> 25
        System.out.println(str.indexOf("p",10)); //문자를 10번째 index부터 검색 -> 23

        if(str.indexOf("welcome")!=-1) {
            System.out.println("문자가 포함되어 있습니다");
        } else {
            System.out.println("문자가 포함되어 있지 않습니다");
        }
    }
}

 

2. contains( )

  • 문자열에 검색하려는 문자가 있는지 확인한다
  • 문자가 있으면 true, 없으면 false 값을 반환한다
public class String_contains {
    public static void main(String[] args)  {
        String str = "Hello welcome to develop world!!";

        if(str.contains("welcome")) {
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
}

 

3. matches( )

matches는 정규식을 이용하여 문자열을 검색한다

영문자나 숫자등의 정규표현식이 대상 문자열에 있는지 확인할 경우에 사용하면 편리하다

boolean 형식으로 결과 값을 리턴한다

public class String_matches {
    public static void main(String[] args)  {
        String s = "Hello welcome to develop world!!";

        //특정 문자열 검색
        if(s.matches(".*welcome.*")) {
            System.out.println(true);
        }else {
            System.out.println(false);
        }

        //영문자 검색
        if(s.matches(".*[a-zA-Z].*")) {
            System.out.println(true);
        }else {
            System.out.println(false);
        }

        //숫자 검색
        if(s.matches(".*[0-9].*")) {
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
}

'JAVA > 명령어' 카테고리의 다른 글

Java - 명령어 - 문자열 특정값 가져오기  (0) 2022.09.16
Java - 명령어 - 정규표현식  (0) 2022.09.16
  • Question
수를 입력받아 2의 거듭제곱인지 여부를 리턴한다

 

  • Code
public class Question3_powerOfTwo {
    public static void main(String[] args) {
        //수를 입력받는다
        Scanner sc = new Scanner(System.in);
        System.out.println("수를 입력하세요 : ");
        long num = sc.nextInt();

        //2의 거듭제곱인지 확인 후 boolean 값을 출력한다
        while (num >= 1) {
            if (num % 2 == 0 || num == 1) {
                System.out.println(true);                
            } else {
                System.out.println(false);                
            }
            break;
        }
    }
}
  • Result
수를 입력하세요 : 
80
true
  • Question
연이율을 입력받아 원금이 2배 이상이 될 때까지 걸리는 시간(년)을 구한다
  • Code
public class Question2_computeWhenDouble {
    public static void main(String[] args) {

        //연이율을 입력받는다
        Scanner sc = new Scanner(System.in);

        System.out.println("연이율을 입력해 주세요 : ");
        Double interestRate = sc.nextDouble();
        Double interestRate1 = interestRate/100;

        //원금을 입력받는다
        System.out.println("원금을 입력해 주세요 : ");
        Double amountMoney = sc.nextDouble();

        //원금이 2배 이상이 되는데 걸리는 시간을 구한다
        int year = 0;
        Double doubleAmountMoney = amountMoney;
        while (doubleAmountMoney < amountMoney * 2) {
            doubleAmountMoney = doubleAmountMoney + (doubleAmountMoney * interestRate1);
            year++;
        }    

        //원금의 2배가 넘는 시간(년)을 출력한다
        System.out.println("원금의 2배 이상이 되는 기간은 " + year + " 년 입니다");
    }
}
  • Result
연이율을 입력해 주세요 : 
20
원금을 입력해 주세요 : 
100
원금의 2배 이상이 되는 기간은 4 년 입니다
  • Question
배열을 입력받아 차례대로 배열의 첫 요소와 마지막 요소를 키와 값으로 하는 HashMap을 리턴
  • Code
public class Question1_transformFirstAndLast {
    public static void main(String[] args) {

        //입력받을 데이터의 개수를 입력받는다
        Scanner sc = new Scanner(System.in);

        System.out.println("입력받을 문자의 개수는?");
        Integer count = sc.nextInt();
        sc.nextLine();

        //배열에 들어갈 문자열을 입력한다
        System.out.println(count + "개의 문자를 입력하세요");
        String[] arr = new String[count];
        for (int i = 0; i < count; ++i) {//
            arr[i] = sc.nextLine();

            //배열에 빈문자열이 입력되면 null을 반환한다
            if(arr[i].length()==0){
                arr[i]=null;
            }
        }

        //저장된 배열의 데이터 중에서 첫번째와 마지막 데이터를 key와 value 로 저장한다
        HashMap result = new HashMap();

        //배열의 첫번째와 마지막 값을 key와 value로 저장한다
        result.put(arr[0], arr[arr.length - 1]);

        //저장된 값을 출력한다
        System.out.println("{" + result.keySet() + " : " + result.values() + "}");
    }
}
  • Result
입력받을 문자의 개수는?
5
5개의 문자를 입력하세요
a
s
d
f
g
{[a] : [g]}

'예제 > Java 예제' 카테고리의 다른 글

Java 예제 - 2의 거듭제곱  (0) 2022.09.16
Java 예제 - 원금의 두배 구하기  (0) 2022.09.15
Java - HashMap 활용 - Id,Pw 확인  (0) 2022.09.14
배열의 합 구하기  (0) 2022.08.14
Java 예제 - 백준 문제 - 10039  (0) 2022.08.02

■ HashMap

1. 정의

  • HashMap은 key와 value 값으로 구성된다
    - HashMap(key, value)
    - key 값은 중복되면 않된다
    - value 값은 중복될 수 있다 
  • HashMap = Hash Table 에 저장 = 배열(Array) + 링크드리스트(LinkedList) 로 구성된다
  • HashMap은 순서를 유지하지 않아도 된다
     - 순서를 유지하고 싶다면 LikedHashMap을 사용하면 된다
  • HashMap은 Hashing 기법으로 만들어진다
HashMap map = new HasgMap();
map.put("key", "value");  //저장하기 위한 명령어로 put를 사용한다
  • key와 value 값 한쌍을 Entry라고 한다

2. Hashing

  • hash function은 key값을 받아서 해당되는 index 값(저장위치)을 반환하는 함수이다
  • 반환되는 index 값을 hash code라고 한다
  • hash 함수를 이용하여 hash 테이블에 데이터를 저장하고 반환하는 작업을 hashing이라고 한다
    - key값이 같으면 항상 같은 값을 반환한다
    - key값이 달라도 같은 값을 반환할 수 있다
  • hash table은 배열과 링크드 리스트의 조합이다
    - 배열의 접근성과 링크드리스트의 변경 용이성을 모두 가지고 있다

3. HashMap 메서드

 1) HashMap

  • HashMap()
  • HashMap(int initialCapacity)
  • HashMap(int initialCapacity, float loadFactor)
  • HashMap(Map m)

 

 2) Object Method

  • Object put(Object key, Object value)
    - 데이터를 저장할 때 사용한다
    - key와 value를 묶어서 저장한다(key=value)
  • void putAll(Map m)
    - 지정한 Map m의 값을 모두 저장할 때 사용한다
  • Object remove(Object key)
    - key 데이터를 삭제할 때 사용한다
  • Object replace(Object key, object value)
    - 지정한 key의 value를 변경할 때 사용한다
  • boolean replace(Object key, Object oldValue, Object newValue)
    - 지정한 key에 해당되는 value를 변경할 때 사용한다

 3) HashMap에 저장된 데이터를 읽어오는 Method

  • Set entrySet()
    - key와 value 쌍으로 구성된 데이터를 호출한다
  • Set keySet()
    - key 데이터만 호출한다
  • Collection value()
    - value 데이터만 호출한다

 4) HashMap에 저장된 데이터를 조회하는 Method

  • Object get(Object key)
    - 지정한 key에 해당하는 value를 반환한다
  • Object getOrDefault(Object key, Object defaultValue)
    - 주어진 key가 데이터에 없을 경우, 지정한 defaultValue 데이터를 반환한다
  • boolean containsKey(object key)
    - 주어진 key가 key 데이터에 있는지 여부를 확인한다
    - true / false 로 반환한다
  • boolean containsValue(Object vaalue)
    - 주어진 value가 value 데이터에 있는지 여부를 확인한다
    - true / false 로 반환한다

 5) 정량적 Method

  • int size()
    - 데이터의 크기를 확인한다
    boolean isEmpty()
    - 데이터가 비었는지 확인한다
  • void clear()
    - 데이터를 삭제한다
  • Object clone()
    - 데이터를 복제한다

 

4. 예제

import java.util.HashMap;
import java.util.Scanner;

public class HashMap_IdPw {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("Id1", "1111");
        map.put("Id2", "2222");
        map.put("Id3", "3333");
//        System.out.println(map);

        Scanner scanner = new Scanner(System.in);

        while (true) {  //반복문을 실행
            System.out.println("Id와 Pw를 입력하세요");
            System.out.println("Id : ");
            String id = scanner.nextLine().trim();  //trim 은 앞 뒤 공백을 없애준다
            System.out.println("Pw : ");
            String pw = scanner.nextLine().trim();

            System.out.println();  //줄바꿈 역활

            if (!map.containsKey(id)) {  //containsKey는 key 값 전체를 가리킨다
                System.out.println("일치하는 id가 없습니다" + "\n" + "다시 입력해 주시기 바랍니다");
                continue;  //다음 반복 while 문으로 이동
            }

            if (!map.get(id).equals(pw)) {  //get(id)는 key 값 중에 id와 일치하는 값이 있으면  value 값을 반환한다
                System.out.println("비밀번호가 일치하지 않습니다" + "\n" + "다시 입력해 주시기 바랍니다");
            } else {  //id와 pw가 일치하면 문장 출력 후 반복문 털출
                System.out.println("id와 비밀번호가 일치합니다");
                break;
            }
        }
    }
}
import java.util.*;

public class HashMap_SumAverage {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("kim", 90);
        map.put("lee", 50);
        map.put("cha", 80);
        map.put("park", 100);
        map.put("choi", 70);
//        System.out.println(map);

        //전체 map에 저장된 데이터에서 값을 호출,조회하는 코드

        Set set = map.entrySet();  //map에 저장된 데이터를 key,value 쌍으로 호출한다
        Iterator iter = set.iterator();  //set에 데이터가 남아 있는지 확인한다

        while(iter.hasNext()) {
            Map.Entry me = (Map.Entry)iter.next();  //Map 인터페이스 안의 Entry 인터페이스
            System.out.println("이름 : " + me.getKey() + ", 점수 : " + me.getValue());
        }

        //전체 map에서 key 데이터만을 호출하는 코드
        set = map.keySet();  //key에 저장된 값만 가져온다
        System.out.println("참가자 명단 : " + set);

        //전체 map에서 value 데이터만을 호출하는 코드
        Collection values = map.values();  //value 데이터를 가져온다
        iter = values.iterator();

        int total = 0;

        while(iter.hasNext()) {  //hasNext()는 읽어 올 요소가 남았는지 확인한다
            int i = (int)iter.next();  //next() 메소드는 읽어 올 요소가 남았는지 확인한다
            total = total +i;
        }

        System.out.println("총점 : " + total);
        System.out.println("평균 : " + (float)total/set.size());  //정수로 결과값이 나오므로 float로 형변환 해 준다
        System.out.println("최고점수 : "+ Collections.max(values));
        System.out.println("최저점수 : "+ Collections.min(values));
   }
}

 

 

 

'JAVA' 카테고리의 다른 글

Java - HashMap 활용 - 합계, 평균  (0) 2022.09.14
Java - 문자열 바꾸기 - replace  (0) 2022.09.13
Java - Static  (0) 2022.08.14
Java - 문자열 입력(Scanner, BufferReader)  (0) 2022.08.08
Java - 날짜(Date), 시간(Time)  (0) 2022.08.08

HashMap에 저장된 데이터를 사용하여 합계, 평균, 최고점, 최저점을 구한다

import java.util.*;

public class HashMap_SumAverage {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("kim", 90);
        map.put("lee", 50);
        map.put("cha", 80);
        map.put("park", 100);
        map.put("choi", 70);
//        System.out.println(map);

        //전체 map에 저장된 데이터에서 값을 호출,조회하는 코드

        Set set = map.entrySet();  //map에 저장된 데이터를 key,value 쌍으로 호출한다
        Iterator iter = set.iterator();  //set에 데이터가 남아 있는지 확인한다

        while(iter.hasNext()) {
            Map.Entry me = (Map.Entry)iter.next();  //Map 인터페이스 안의 Entry 인터페이스
            System.out.println("이름 : " + me.getKey() + ", 점수 : " + me.getValue());
        }

        //전체 map에서 key 데이터만을 호출하는 코드
        set = map.keySet();  //key에 저장된 값만 가져온다
        System.out.println("참가자 명단 : " + set);

        //전체 map에서 value 데이터만을 호출하는 코드
        Collection values = map.values();  //value 데이터를 가져온다
        iter = values.iterator();

        int total = 0;

        while(iter.hasNext()) {  //hasNext()는 읽어 올 요소가 남았는지 확인한다
            int i = (int)iter.next();  //next() 메소드는 읽어 올 요소가 남았는지 확인한다
            total = total +i;
        }

        System.out.println("총점 : " + total);
        System.out.println("평균 : " + (float)total/set.size());  //float로 형변환 해 준다
        System.out.println("최고점수 : "+ Collections.max(values));
        System.out.println("최저점수 : "+ Collections.min(values));
   }
}

'JAVA' 카테고리의 다른 글

Java - HashMap / Hashing  (0) 2022.09.14
Java - 문자열 바꾸기 - replace  (0) 2022.09.13
Java - Static  (0) 2022.08.14
Java - 문자열 입력(Scanner, BufferReader)  (0) 2022.08.08
Java - 날짜(Date), 시간(Time)  (0) 2022.08.08

■ HashMap에 저장된 데이터를 활용하여 ID와 PW를 확인한다

import java.util.HashMap;
import java.util.Scanner;

public class HashMap_IdPw {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("Id1", "1111");
        map.put("Id2", "2222");
        map.put("Id3", "3333");
//        System.out.println(map);

        Scanner scanner = new Scanner(System.in);

        while (true) {  //반복문을 실행
            System.out.println("Id와 Pw를 입력하세요");
            System.out.println("Id : ");
            String id = scanner.nextLine().trim();  //trim 은 앞 뒤 공백을 없애준다
            System.out.println("Pw : ");
            String pw = scanner.nextLine().trim();

            System.out.println();  //줄바꿈 역활

            if (!map.containsKey(id)) {  //containsKey는 key 값 전체를 가리킨다
                System.out.println("일치하는 id가 없습니다" + "\n" + "다시 입력해 주시기 바랍니다");
                continue;  //다음 반복 while 문으로 이동
            }

            if (!map.get(id).equals(pw)) {  //get(id)는 key 값 중에 id와 일치하는 값이 있으면  value 값을 반환한다
                System.out.println("비밀번호가 일치하지 않습니다" + "\n" + "다시 입력해 주시기 바랍니다");
            } else {  //id와 pw가 일치하면 문장 출력 후 반복문 털출
                System.out.println("id와 비밀번호가 일치합니다");
                break;  
            }
        }
    }
}

'예제 > Java 예제' 카테고리의 다른 글

Java 예제 - 원금의 두배 구하기  (0) 2022.09.15
Java 예제 - 배열과 HashMap  (0) 2022.09.15
배열의 합 구하기  (0) 2022.08.14
Java 예제 - 백준 문제 - 10039  (0) 2022.08.02
coding test - 성적처리  (0) 2022.05.22

■ replace

  • String 문자열은 연산에 의해 원본 문자열을 바꿀 수 없다
    - 바꾸고져 하는 문자열이 새로 생성되어 반환된다
  • replace 함수는 원본 문자열을 조건에 맞춰 변경한다

 

1. String replace(char oldChar, char newChar)

  • 문자열에 있는 모든 oldChar 문자를 newChar로 변경한 문자열을 반환한다
  • 하나의 문자를 변경한다
String str = "Hello World";
String text = str.replace(oldChar: 'l', newChar: 'r');
System.out.println(text);


Herro Worrd
  • 문자열에 있는 모든 target 문자 객체를 replacement로 변경한 문자 객체로 반환한다
  • 한 개의 단어나 문장을 변경한다
String str = "Hello World";
String text = str.replace(target: 'Hello', replacement: 'Hi');
System.out.println(text);


Hi World

 

2. String replaceAll(String regex, String replacement)

  • 문자열에 있는 regex의 문자열을 replacement 문자열로 변경한 문자열을 반환한다
  • replace와 기능은 같지만 줄바꿈이 있는 경우에 사용한다

 

3. String replaceFirst(String regex, String replacement)

  • 문자열에 있는 regex와 같은 문자 중에 첫번째 문자만을 replacement 문자열로 변경하여 문자열을 반환한다
  • 동일한 문자열이 있는 경우 첫번째만 변경할 때 사용한다
String change3 = str.replaceFirst("l", "Hi");
        System.out.println(change3);
        

HeHilo World

 

'JAVA' 카테고리의 다른 글

Java - HashMap / Hashing  (0) 2022.09.14
Java - HashMap 활용 - 합계, 평균  (0) 2022.09.14
Java - Static  (0) 2022.08.14
Java - 문자열 입력(Scanner, BufferReader)  (0) 2022.08.08
Java - 날짜(Date), 시간(Time)  (0) 2022.08.08

1. 통합개발환경(Integrated Development Environment, IDE) 선택

  • 통합개발환경의 결정은 매우 중요하다
  • 개발자는 어떤 통합개발환경을 선택하는가에 따른 영향을 받을 수 있으므로 팀 단위로 개발 업무를 진행하기 위하여 통합개발환경을 맞추고 개발을 진행하는 것이 좋다
  • 통합개발환경의 선택 요인은 개발자의 툴 학습 정도에 따른 생산성이나 기타 여러 요인들을 종합적으로 검토하여 결정한다
  • Jetbrains사의 IntelliJ는 상용 프로그램으로 최근 전세계적으로 많은 개발자가 사용하고 있다
  • 이전까지는 무료로 제공되는 Eclipse를 많이 사용했다
    - 프로그램이 무겁고 개발에 따른 구현 항목이 많아지면 속도가 느려지는 등의 문제가 많다
  • IntelliJ는 Eclipse의 여러 문제를 해결하고 더 강력한 사용 경험을 제공한다
  • IntelliJ는 무료 버젼도 제공한다
    - 무료인 커뮤니티 버젼은 학습 수준의 개발을 경험하는데 크게 무리가 없다
    - 상용 버전의 전체 기능을 제공하지는 않으며 일부 제한된 기능만을 사용할 수 있다

 

2. JDK(Java Development Kit) 선택

  • JDK는 자바 언어로 자바 프로그래밍을 할 수 있게하는 자바 개발 도구이다
  • JDK를 설치하지 않으면 자바 언어로 개발 할 수 없다
  • 예전에는 JDK가 무료로 제공되었지만 Oracle 기업에서 JDK를 관리하면서 유료 도구로 전환되었다
  • OpenJDK는 무료 버전의 자바 개발 도구이며 여러 벤더에서 제공하고 있다

<벤더별 OpenJDK 목록 - 출처 : Stackoverflow>

  • 일반적으로 AdoptOpenJDK를 많이 사용한다

 

3. 프레임워크(Framework) 선택

  • 프레임워크는 Spring-boot를 선택한다

 

4. 빌드 방식 선택

  • 빌드 방식은 크게 두가지로 Maven 과 Gradle 이 있다
  • 프로젝트를 진행하는 과정에서 구현하는 코드 외에 많은 외부 라이브러리들을 참조해서 개발을 하게 되는데 빌드 툴을 사용하지 않으면 외부 라이브러리들을 모두 직접 관리해야 하고 프로젝트 빌드 시에도 빌드에 대한 표준을 설정하여 빌드 해야한다
  • 빌드 툴을 사용하면 개발 속도를 높이고, 생산성을 증대시킬 수 있다
  • 이전에는 Ant라는 것을 많이 사용했지만 Maven이 Ant를 넘어서 더 많은 개발자들이 사용하고 있다
    - Maven은 빌드의 요소를 XML로 정의하고 있는데 XML은 사람의 시각에서 보기 편한지 않다
    - 여러 라이브러리들이 의존 관계를 형성하는 경우에도 어려움을 겪을 수 있다
  • Gradle은 최근에 선호도가 매우 높아졌다
    - Gradle은 Groovy라는 것을 사용하기 때문에 동적인 빌드 시에 여러 문제들을 Groovy 스크립트 등을 통해 해결한다
    - 처리 속도가 Maven에 비해 월등히 빠르다

5. 형상관리(Software Configuration Management) 방법 결정

  • Git과 같은 형상관리 방법을 사용하지 않으면 개발자는 개발을 진행함에 있어 여러가지 어려움을 경험한다
    - 첫번째는 개발중인 코드가 예상치 못한 사고로 인해 통째로 날아갈 수 있다
    - 두번째는 버전 관리가 어렵다
     : 작성한 코드를 수정해야 할 경우에 한번에 완벽하게 수정이 완료된다면 문제 없지만, 잘못 수정될 수 있다
     : 형상관리 툴은 버져닝(버전관리)을 한다
     : 소스 코드를 원격지에 백업하는 용도 외에도 코드의 히스토리를 기록하여 원하는 버전으로 대체할 수 있다
    - 세번째는 협업을 하기에 용이하다
     : 규모가 큰 프로젝트를 진행할 경우 개발자 혼자서 모든 것을 구현하기는 어렵다
     : 여러 사람이 협업하여 진행할 경우에는 소스코드가 중복되거나 같은 부분을 다르게 수정하여 소스 코드간에 ‘충돌' 현상이 발생할 수 있다
     : 동일한 소스 코드의 업무별 분리와 취합이 힘들고 장소의 제약을 받을 확률도 매우 높다
     : 형상관리를 활용하면 여러 사람이 함께 공통의 코드를 관리하고 취합할 수 있으며, 용도별로 저장소를 구분하여 ‘충돌(Conflic)'과 ‘병합(Merge)'의 문제가 해결된다
  • 형상관리 서비스는 많은 제품이 있다
    -  Git 외에도 이전에는 SVN이라는 형상관리 방식을 많이 사용했다
    - SVN은 아직도 많은 개발자와 기업에서 사용하고 있다
    - Git으로의 전환을 늦추는 이유는 비용과 시간을 고려한 업무 효율성 때문이다
    - Git은 Github 라는 강력한 서비스를 통해 전 세계에서 가장 많이 사용하는 형상관리 서비스이다

 

6. IntelliJ 설정

  • 인텔리제이의 Setting > Version Control > Git 에서 Git을 설치한다
  • 인텔리제이의 Setting > Version Control > GitHub 에서 +를 눌러 자신의 Git을 등록한다

  • 신규 프로젝트에서 Alt + ` 을 누른다
    - 화면 창에서 commit를 선택하여 저장소를 연결한다
  • Commit 을 선택하면 IntelliJ의 화면 구성이 변하고 Commit to main이라는 탭이 나타난다

  • Github Repository에 최초의 소스코드를 등록할 예정이므로 프로젝트 생성시 만들어진 모든 파일들이 업로드 되어야 한다
    - Unversioned Files를 체크하여 Commit 대상을 모두 선택한다
  • 아래 ‘Commit Message’ 항목에 커밋 내용을 initial 이라고 적고 ‘Commit and Push’ 버튼을 눌러 Github Repository에 등록한다

  • Github 계정에는 연결되어 있지만 어떤 Repository에 연결할지는 설정하지 않았다
  • Commit and push 전 해당 프로젝트가 연결될 Repository를 지정해야 한다
    - Define remote를 누르고 Github에서 생성한 Repository의 주소를 적습니다.

  • Repository가 연결되고 난 뒤 push 버튼을 눌러 원격지에 내보낸다

  • 프로젝트명에서 우클릭하여 나타나는 메뉴 중에서 ignoir를 선택한다
    - 아래 화면처럼 설정한 후 generate을 클릭하면 프로젝트 파일에 ingnoir 파일이 생성된다

 

1. 브랜칭(branching)

  • 메인 개발 코드를 그대로 복사하여 기존의 메인 개발 코드를 건드리지 않고 새로운 기능을 개발할 수 있는 버전 관리 기법이다
  • 처음에 Git 리포지토리를 생성하면 나오는 main 브랜치에서만 작업을 하다가 새로운 기능 개발을 위해 feature 브랜치를 새로 생성하는 경우, 기존 main 브랜치에서의 작업은 유지하고 새로운 feature 브랜치에서 자유롭게 코드를 추가 및 삭제할 수 있다

 

2. Git branch

 1) main 브랜치

  • main 브랜치는 사용자에게 언제든 배포할 수 있으며, 사용자에게 언제든 제품으로 출시할 수 있는 브랜치이다
  • 회사에 따라서 master, prod, production 등으로 사용하기도 한다
  • 일정 기준을 충족했고, 핵심 기능이 완성되었으면 main 브랜치로 배포할 수 있다
    - 대표적인 기능이 완성되어 있다
    - 기존 기획했던 레이아웃이나 전체적인 디자인이 대부분 완성되어 있다
    - 클라이언트, 서버, 데이터베이스가 공개된 웹에서 정상적으로 통신할 수 있다
    - 최소한의 보안이 마련되어 있다
     : 브라우저에서 개발 버전에서 사용하던 secret이나 유저의 비밀번호가 노출되지 않도록 한다
     : 유저의 기밀 정보 조회를 위해 인증 토큰, 세션이 필요하도록 한다

 

 2) dev 브랜치

  • 다음 버전 배포를 위한 개발 중인 브랜치이다
  • main 브랜치에서부터 브랜치를 생성한다
  • main 브랜치와 dev 브랜치는 Github 리포지토리에 항상 업데이트 되어있어서 팀원의 코드 리뷰를 받고 진행한다
  • 가능하면 모든 팀원이 확인 가능하도록 코멘트를 Github Pull Request에 남기는 것을 권장한다

 

 3) 보조 브랜치

  • feature 브랜치라고 한다
  • feature 브랜치는 기능 개발, 리펙토링, 문서 작업, 단순 오류 수정 등 다양한 작업을 기록하기 위한 브랜치이다
  • 분류를 세세하게 나누는 경우에는 refactor, fix, docs, chore와 같이 세세하게 커밋 메시지나 브랜치명에 prefix를 달기도 한다
  • 아래는 feature 브랜치 이름과 커밋 메시지의 예시이다
hash (브랜치 명) 커밋 메시지
2f85eea (feat/create-todo) feat: Todo 추가 기능
2ad0805 (fix/var-name) fix: 변수 네이밍 컨벤션에 맞게 변수명 변경 (ismale => isMale)
e7ce3ad (refactor) refactor: 불필요한 for 루프 삭제
  • feature 브랜치는 보통 각 개인의 로컬 리포지토리에서 만들고 작업한다
  • feature 브랜치는 기능 개발을 위한 브랜치이기 때문에 2명 이상 같이 작업하는 경우가 없다
  • 커밋 기록을 남기는 일반적인 rebase-and-merge 와 커밋 기록을 정리하는 squash-and-merge 등의 merge가 있다
  • 일반적으로 feature 브랜치는 머지하고 나서 삭제하지만, 복원해야 할 필요성이 있는 경우는 남겨두기도 한다

▶ 커밋 메시지의 예시 : https://www.conventionalcommits.org/ko/v1.0.0/ 

 

Conventional Commits

커밋 메세지에 사용자와 기계 모두가 이해할 수 있는 의미를 부여하기 위한 스펙

www.conventionalcommits.org

 

 

3. 브랜치 생성 및 변경(git switch)

 1) 브랜치 생성

브랜치를 새로 생성하는 경우, -c를 붙인다
git switch -c feature

checkout 이라는 명령어도 사용할 수 있다
git checkout -b feature

feature 브랜치로 이동한다
git switch feature

기존에 있던 main 브랜치로 HEAD를 변경 시에는 -c를 붙이지 않는다
git switch main
git checkout main

 

 

4. 브랜치 합치기(git merge)

  • 기능 개발이 끝나면 브랜치를 main 브랜치와 합칠 수 있다
feature 브랜치에서 기능 개발
git commit -m "기능1의 세부 기능1"
git commit -m "기능1의 세부 기능2"
git commit -m "기능1 개발 완료"

머지를 위해 main 브랜치로 전환
git switch main

main 브랜치로 feat/todo 브랜치를 병함
git merge feat/todo

  • 프로젝트 개발 시에는 브랜치를 Github의 pull request 기능을 이용하여 변경 내역을 충분히 확인한다
    - 로컬에서 머지하지 않고 feature 브랜치를 push하여 pull request를 요청하는 것이 문제 발생을 감소시킨다
기능 개발이 진행
git commit -m "기능1의 세부 기능1"
git commit -m "기능1의 세부 기능2"
git commit -m "기능1 개발 완료"

Github 리포지토리로 푸시
git push origin feat/todo

Github에서 Pull Request 실행

 

5. 브랜치 삭제(git branch -d)

  • 머지된 feature 브랜치는 이미 dev 브랜치에 기록이 완벽하게 남아있기 때문에 남겨둘 이유가 없으므로 삭제한다
  • 원격 레포지토리에서 pull request가 성공적으로 마무리되면, 아래 스크린샷처럼 브랜치를 삭제하는 버튼을 눌러 쉽게 삭제할 수 있다

  • 로컬 리포지토리에서 브랜치 삭제는 git branch -d <브랜치명> 으로 할 수 있다
git branch -d feat/todo
  • Git은 버전 관리를 위해서 브랜치가 합쳐지지 않으면 삭제하지 못 하도록 설정되어 있다
    - 미완성 기록을 삭제하고 싶을 경우에 -D 옵션을 쓰면 삭제할 수 있다
    - 머지되지 않은 브랜치 삭제는 버전 기록 시스템의 사용 목적과는 맞지 않는다
    - 미완성 기록이라도 해당 기능으로 돌아가야 할 수도 있으므로 남겨두는 것을 권장한다
git branch -D feat/todo

 

6. Git 문제 발생 대처안

 

 

7. Git Error  대처방법

 1) reject error

  •  ! [rejected]        master -> master (non-fast-forward))
  • git push 또는 git push origin main 등의 명령을 실행해도 reject를 동반한 에러가 발생할 경우 대처 방법이다
 ! [rejected]        master -> master (non-fast-forward)

push 하고져 하는 디렉토리에서

git init 
로 초기화 한 후
git remote add origin {git push 주소}
로 다시 연결을 진행한다
다시
git push 또는 git push origin main
을 하여도 에러가 발생한다면...

git push origin +main
처럼 main 앞에 '+'를 붙여서 진행한다

'Git' 카테고리의 다른 글

Git - 명령어 / HELP  (0) 2022.08.01
GitHub  (0) 2022.07.10
Git(global information tracker)  (0) 2022.07.10

1. REST API

 1) 개요

  • 웹 애플리케이션에서는 HTTP 메서드를 이용해 서버와 통신한다
    - GET을 통해 웹 페이지나 데이터를 요청한다
    - POST로 새로운 글이나 데이터를 전송한다
    - DELETE로 저장된 글이나 데이터를 삭제한다
  • 클라이언트와 서버가 HTTP 통신을 할 때는 어떤 요청을 보내고 받느냐에 따라 메서드의 사용이 달라진다
  • REST API에서 REST는 “Representational State Transfer”의 약자이다
    - 로이 필딩의 박사학위 논문에서 웹(http)의 장점을 최대한 활용할 수 있는 아키텍처로써 처음 소개되었다
  • REST API는 웹에서 사용되는 데이터나 자원(Resource)을 HTTP URI로 표현하고, HTTP 프로토콜을 통해 요청과 응답을 정의하는 방식이다

 2) 역활

  • 클라이언트와 서버 사이에는 데이터와 리소스를 요청하고 요청에 따른 응답을 전달하기 위한 도구 필요하다
  • 도구를 통해 클라이언트는 서버에 필요한 것을 요청하고, 이에 대한 응답을 다시 서버에서 클라이언트로 전송할 수 있다
  • HTTP 프로토콜 기반으로 요청과 응답에 따라 리소스를 주고받는 작업을 API가 수행해야 한다
    - 클라이언트와 서버 간에 서로 잘 알아볼 수 있도록 작성하는 것이 중요하다

 

2. REST API를 디자인하는 방법

  • REST API를 작성에는 몇 가지 지켜야 할 규칙들이 있다
  • 로이 필딩이 논문에서 제시한 REST 방법론을 보다 더 실용적으로 적용하기 위해 레오나르드 리차드슨은 REST API를 잘 적용하기 위한 4단계 모델을 만들었다
    - 로이 필딩은 이 모델의 모든 단계를 충족해야 REST API라고 부를 수 있다고 주장했다
    -  2단계까지만 적용해도 좋은 API 디자인이라고 볼 수 있고, 이런 경우 HTTP API 라고 한다
    - 리차드슨의 REST 성숙도 모델은 총 4단계(0~3단계)로 나누어진다

 1) REST 성숙도 모델 - 0단계

  • 0단계에서는 HTTP 프로토콜을 사용하기만 해도 된다
    - 0단계의 경우에는 해당 API를 REST API라고 할 수는 없다
    - 0단계는 좋은 REST API를 작성하기 위한 기본 단계이다
  • 허준이라는 의사의 예약 가능한 시간을 확인하고, 어떤 특정 시간에 예약하는 상황의 예제
    - HTTP 프로토콜을 사용하고 있다
    - 단순히 HTTP 프로토콜을 사용하는 것이 REST API의 출발점이다
요청 내용 요청 응답
예약 가능 시간 확인 POST/appointment HTTP/1.1
[헤더 생략]

{
   "date" : "2022-08-10" ,
   "doctor" : "허준"



HTTP/1.1 200 OK
[헤더 생략]

{
   " slots" : [
      { "doctor" : "허준", "start" : "09:00", "end" : "12:00"} ,
      { "doctor" : "허준", "start" : "14:00", "end" : "16:00"}
   ]
}
특정 시간 예약 POST/appointment HTTP/1.1
[헤더 생략]

{
   "doctor" : "허준" ,
   "start" : "14:00" ,
   "end" : "15:00" ,
   "patient" : "김코딩"
}
HTTP/1.1 200 OK
[헤더 생략]







 

 2) REST 성숙도 모델 - 1단계

  • 1단계에서는 개별 리소스와의 통신을 준수해야 한다
  • REST API는 웹에서 사용되는 모든 데이터나 자원(Resource)을 HTTP URI로 표현한다
    - 모든 자원은 개별 리소스에 맞는 엔드포인트(Endpoint)를 사용해야 한다
    - 모든 자원은 요청하고 받은 자원에 대한 정보를 응답으로 전달해야 한다
  • 0단계에서는 모든 요청에서 엔드포인트로 /appointment 를 사용하였다
  • 1단계에서는 요청하는 리소스가 무엇인지에 따라 각기 다른 엔드포인트로 구분하여 사용해야 한다
  • 1단계 예제
    - 예약 가능 시간 확인이라는 요청의 응답으로 받게 되는 자원(리소스)은 허준이라는 의사의 예약 가능 시간대이다
     : 예약 가능 시간 확인 요청 시 /doctors/허준이라는 엔드포인트를 사용하였다
    - 특정 시간에 예약하게 되면, 실제 slot이라는 리소스의 123이라는 id를 가진 리소스가 변경된다
     : 특정 시간 예약이라는 요청에서는 /slots/123으로 실제 변경되는 리소스를 엔드포인트로 사용하였다
요청 내용 요청 응답
예약 가능 시간 확인 POST/doctors/허준 HTTP/1.1
[헤더 생략]

{
   "date" : "2022-08-10" ,




HTTP/1.1 200 OK
[헤더 생략]

{
   " slots" : [
      { "id" : 123, "doctor" : "허준", "start" : "09:00", "end" : "12:00"} ,
      { "id" : 124, "doctor" : "허준", "start" : "14:00", "end" : "16:00"}
   ]
}
특정 시간 예약 POST/slots/123 HTTP/1.1
[헤더 생략]

{
   "patient" : "김코딩"
}



HTTP/1.1 200 OK
[헤더 생략]

{
   "appointment" : {
      "slots" : { "id" : 123, "doctor" : "허준", ... } ,
      "patient" : "김코딩"
   }
}
  • 어떤 리소스를 변화시키는지 혹은 어떤 응답이 제공되는지에 따라 각기 다른 엔드포인트를 사용해야 한다
    - 적절한 엔드포인트를 작성하는 것이 중요하다
    - 엔드포인트 작성 시에는 동사, HTTP 메서드, 어떤 행위에 대한 단어 사용은 지양한다
    - 리소스에 집중된 명사 형태의 단어로 작성하는 것이 적당하다
  • 요청에 따른 응답으로 리소스를 전달할 때에도 사용한 리소스에 대한 정보와 리소스 사용에 대한 성공/실패 여부를 반환해야 한다
    - 김코딩 환자가 허준 의사에게 9시에 예약을 진행하였으나, 해당 시간에 예약이 불가능할 경우에는 리소스 사용에 대한 실패 여부를 포함한 응답을 받아야 한다
요청 내용 요청 응답
예약 가능 시간 확인 POST/doctors/허준 HTTP/1.1
[헤더 생략]

{
   "date" : "2022-08-10" ,




HTTP/1.1 200 OK
[헤더 생략]

{
   " slots" : [
      { "id" : 123, "doctor" : "허준", "start" : "09:00", "end" : "12:00"} ,
      { "id" : 124, "doctor" : "허준", "start" : "14:00", "end" : "16:00"}
   ]
}
특정 시간 예약 POST/slots/123 HTTP/1.1
[헤더 생략]

{
   "patient" : "김코딩"
}



HTTP/1.1 200 OK
[헤더 생략]

{
   "appointmentFailure" : {
      "slots" : { "id" : 123, "doctor" : "허준", ... } ,
      "patient" : "김코딩" ,
      "reason" : "해당 시간은 이미 다른 예약이 있습니다"
   }
}

 

 3) REST 성숙도 모델 - 2단계

  • 2단계에서는 CRUD에 맞게 적절한 HTTP 메서드를 사용하는 것에 중점을 둔다
  • 0단계와 1단계 예시에서는 모든 요청을 CRUD에 상관없이 POST로 하고 있다
    - REST 성숙도 모델 2단계에 따르면 CRUD에 따른 적합한 메서드를 사용한 것이 아니다
  • 예약 가능 시간 확인은 예약 가능한 시간을 조회(READ)하는 행위를 의미한다
    - 조회(READ)하기 위해서는 GET 메서드를 사용하여 요청을 보낸다
    - GET 메서드는 body를 가지지 않기 때문에 query parameter를 사용하여 필요한 리소스를 전달한다
  • 특정 시간 예약은 해당 특정 시간에 예약을 생성(CREATE)한다는 것과 같다
    - 예약을 생성(CREATE)하기 위해서는 POST 메서드를 사용하여 요청을 보내는 것이 적당하다
  • 2단계에서는 POST 요청에 대한 응답이 어떻게 반환되는지도 중요하다
    - 응답은 새롭게 생성된 리소스를 보내주기 때문에, 응답 코드도 201 Created 로 명확하게 작성해야 한다
    - 관련 리소스를 클라이언트가 Location 헤더에 작성된 URI를 통해 확인할 수 있어야 완벽하게 REST 성숙도 모델의 2단계를 충족한 것이다
요청 내용 요청 응답
예약 가능 시간 확인 POST/doctors/허준/slots?data=2022-08-10 HTTP/1.1
[헤더 생략]






HTTP/1.1 200 OK
[헤더 생략]

{
   " slots" : [
      { "id" : 123, "doctor" : "허준", ... } ,
      { "id" : 124, "doctor" : "허준", ... }
   ]
}
특정 시간 예약 POST/slots/123 HTTP/1.1
[헤더 생략]

{
   "patient" : "김코딩"
}




HTTP/1.1 201 Created
Location : slots/123/appointment

[헤더 생략]

{
   "appointment" : {
      "slots" : { "id" : 123, "doctor" : "허준", ... } ,
      "patient" : "김코딩"
   }
}

※ 메서드 사용 규칙

  • GET 메서드는 서버의 데이터를 변화시키지 않는 요청에 사용해야 한다
  • POST 메서드는 요청마다 새로운 리소스를 생성한다
  • PUT 메서드는 요청마다 같은 리소스를 반환한다
    - 요청마다 같은 리소스를 반환하는 특징을 멱등(idempotent)하다고 한다
    - 멱등성을 가지는 메서드 PUT 과 그렇지 않은 POST는 구분하여 사용해야 한다
  • PUT 과 PATCH 도 구분하여 사용해야한다
    - PUT은 교체의 용도로 사용한다
    - PATCH는 수정의 용도로 사용한다
  • HTTP request methods : https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods 
 

HTTP request methods - HTTP | MDN

HTTP defines a set of request methods to indicate the desired action to be performed for a given resource. Although they can also be nouns, these request methods are sometimes referred to as HTTP verbs. Each of them implements a different semantic, but som

developer.mozilla.org

 

 4) REST 성숙도 모델 - 3단계

  • 3단계는 HATEOAS(Hypertext As The Engine Of Application State)라고 표현되는 하이퍼미디어 컨트롤을 적용한다
  • 3단계의 요청은 2단계와 동일하다
  • 3단계의 응답은 리소스의 URI를 포함한 링크 요소를 삽입하여 작성하는 것이 다르다
  • 응답에 들어가게 되는 링크 요소는 응답을 받은 다음에 할 수 있는 다양한 액션들을 위해 많은 하이퍼미디어 컨트롤을 포함하고 있다
요청 내용 요청 응답
예약
가능
시간
확인
POST/doctors/허준/slots?data=2022-08-10 HTTP/1.1
[헤더 생략]












HTTP/1.1 200 OK
[헤더 생략]

{
   " slots" : [
      { "id" : 123, "doctor" : "허준", ... } , ...
   ] ,
   "links" : {
      "appointment" : {
         "href" : "http://llocalhost:8080/slots/123",
         "method" : "POST"
      }
   }
}
특정
시간
예약
POST/slots/123 HTTP/1.1
[헤더 생략]

{
   "patient" : "김코딩"
}




HTTP/1.1 201 Created
Location : slots/123/appointment

[헤더 생략]

{
   "appointment" : {
      "slots" : { "id" : 123, "doctor" : "허준", ... } ,
      "patient" : "김코딩"
   } ,
   "links" : {
      "self" : {
         "href" : "http://llocalhost:8080/slots/123",
         "method" : "GET"
      }

   "cancel" : {
         "href" : { "http://llocalhost:8080/slots/123/cancel",
         "method" : "DELETE"
      }

   }
}
  • 응답 내에 새로운 링크를 넣어 새로운 기능에 접근할 수 있도록 하는 것이 3단계의 중요 포인트이다
    - 예약 가능 시간을 확인한 후에는 그 시간대에 예약을 할 수 있는 링크를 삽입할 수 있다
    - 특정 시간에 예약을 완료하고 나서 예약을 다시 확인할 수 있도록 링크를 작성할 수 있다

 

5가지의 기본적인 REST API 디자인 가이드 : https://blog.restcase.com/5-basic-rest-api-design-guidelines/

 

5 Basic REST API Design Guidelines

As soon as we start working on an API, design issues arise. Robust and strong design is a key factor for API success. A poorly designed API will indeed lead to misuse or – even worse – no use at all by its intended clients: application developers. Crea

blog.restcase.com

 호주 정부 - API 설계 표준 : https://api.gov.au/sections/definitions.html#api 

 

api.gov.au

Definitions API In the context of this API Design Standard, an API (Application Programming Interface) is defined as a RESTful API. A RESTful API is a style of communication between systems where resources are defined by URI and its operations are defined

api.gov.au

API 디자인 가이드 : https://cloud.google.com/apis/design?hl=ko 

 

API 디자인 가이드  |  Google Cloud

의견 보내기 API 디자인 가이드 2017년 2월 21일에 게시됨. 변경 로그 소개 본 문서는 네트워크 API를 위한 종합 디자인 가이드입니다. 2014년부터 Google 내부에서 사용되었으며, Cloud APIs 및 기타 Google

cloud.google.com

 Microsoft REST API Guidelines : https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md 

 

GitHub - microsoft/api-guidelines: Microsoft REST API Guidelines

Microsoft REST API Guidelines. Contribute to microsoft/api-guidelines development by creating an account on GitHub.

github.com

 

'Network' 카테고리의 다른 글

네트워크 - HTTP  (0) 2022.06.08
네트워크 - 브라우져(Browser)  (0) 2022.06.08
네트워크 - 클라이언트(Client)  (0) 2022.06.07

1. 상속(inheritance)

  • 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 자바의 문법 요소이다
  • 두 클래스를 상위 클래스와 하위 클래스로 나누어 상위 클래스의 멤버(필드, 메서드, 이너 클래스)를 하위 클래스와 공유하는 것을 의미한다
  • 여기서 우리는 이 두 클래스를 서로 상속 관계 있다고 하며, 하위 클래스는 상위 클래스가 가진 모든 멤버를 상속받게 됩니다.
  • 하위 클래스의 멤버 개수는 상위 클래스의 멤버 개수보다 같거나 많아야 한다
  • 상위 클래스-하위 클래스의 관계를 조상-자손 관계로 표현하기도 한다
  • 두 클래스 간 상속 관계를 설정할 때 사용하는 extends 키워드는 "확장하다"라는 의미를 가지고 있다

  • 상위 클래스와 하위 클래스의 공통점과 다른점이 있다
    - 상위 클래스의 변수와 메서드를 하위 클래스에 그대로 받아와서 사용한다
    - 하위 클래스는 필요한 변수와 메서드를 추가하여 코드를 작성할 수 있다 
  • 상속을 통해 클래스를 작성하면 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있어 코드의 중복을 제거할 수 있다
  • 상속은 다형적 표현이 가능하다는 장점이 있다
  • 하위 객체의 클래스에 상위 객체에서 확장된 클래스의 성격도 가지고 있는 것을 다형성이라 한다
  • 위의 그림을 코드로 작성하면 아래와 같다
    - extends 를 확장 메서드로 사용한다
class Person{
    String name;
    int age;
    void learn(){
        System.out.println("study");
    }
    void walk(){
        System.out.println("walking");
    }
    void eat(){
        System.out.println("eating");
    }
}
class Teacher extends Person{
    String schoolName;
    void teach(){
        System.out.println("teaching");
    }
}
class Ceo extends Person{
    String companyName;
    void approval(){
        System.out.println("review");
    }
}
class Developer extends Person{
    String groupName;
    void coding(){
        System.out.println("coding");
    }
}
public class Extends {
    public static void main(String[] args) {
        Person p = new Person();
        p.name = "kms";
        p.age = 25;
        p.learn();
        p.walk();
        p.eat();
        System.out.println(p.name);

        Teacher t = new Teacher();
        t.name = "kim";
        t.age = 30;
        t.learn();
        t.walk();
        t.eat();
        t.teach();
        System.out.println(t.name);
    }
}
  • 코드의 결과는 다음과 같이 출력된다
    - Teach 클래스가 person 클래스의 변수와 메서드를 상속받아서 확장된 것을 확인할 수 있다
    - extends 메서드로 상위 클래스를 확장하지 않았다면 중복된 변수와 메서드를 반복해서 작성해야 했을 것이다

 

 

2. 포함(composit)

  • 포함(composite)은 상속처럼 클래스를 재사용할 수 있는 방법이다
  • 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언한다
  • 클래스명(멤버1, 멤버2, 멤버3,...) 의 형태로 사용한다
    - 외부 클래스 내에 내부 클래스를 작성한다
    - 내부 클래스에 main 클래스가 있다
    - Address 객체를 외부 클래스 밖에서 별도의 클래스로 선언 한다
public class Composit {
    int id;
    String name;
    Address address;

    public Composit(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    void showInfo() {
        System.out.println(id + " " + name);
        System.out.println(address.city + " " + address.country);
    }


    public static void main(String[] args) {
        Address address1 = new Address("Gangnamgu", "Seoul");
        Address address2 = new Address("Junggu", "Seoul");

        Composit e = new Composit(1, "kms", address1);
        Composit e2 = new Composit(2, "kim", address2);

        e.showInfo();
        e2.showInfo();
    }
}
class Address {
    String city, country;

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}
  • Address 클래스에 포함되어 있는 인스턴스 변수 city와 country를 각각 Composit 클래스의 변수로 정의해주어야 하지만, Address 클래스로 해당 변수들을 묶어준 후 Composit 클래스 안에 참조변수를 선언하였다
    - 코드의 중복을 없애고 포함관계로 재사용하는 방법이다

 

 

3. 상속과 포함의 선택

  • 클래스 간의 관계가 ‘~은 ~이다(IS-A)’ 의 관계이면 상속을 사용한다
  • 클래스 간의 관계가 '~은 ~을 가지고 있다(HAS-A)'의 관계이면 포함을 사용한다

 

4. 메서드 오버라이딩(Method Overriding)

  • 상위 클래스로부터 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것을 의미한다
  • Override는 사전적으로 "~위에 덮어쓰다"를 의미한다

 1) 일반적인 메서드오버라이딩

class Airplane{
    void fly(){
        System.out.println("Airplane is flying");
    }
}

public class MethodOverriding extends Airplane {
    void fly(){
        System.out.println("MethodOverriding is flying");
    }

    public static void main(String[] args) {
        MethodOverriding over = new MethodOverriding();
        over.fly();
    }
}

  • MethodOverriding 클래스가 Airplane 클래스의 fly 메서드를 오버라이딩 한 후 MethodOverriding 클래스에 적합하게 fly 메서드를 변경하고 있다
  • 메서드 오버라이딩은 세가지 조건을 반드시 만족해야 한다
    - 메서드의 선언부(메서드 이름, 매개변수, 반환타입)가 상위클래스와 완전히 일치해야 한다
    - 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다
    - 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다

 2) 상위 클래스의 타입을 적용한 메서드 오버라이딩

public class Overriding {
    public static void main(String[] args) {
        Bike bike = new Bike();
        Car car = new Car();
        Human human = new Human();

        bike.run();
        car.run();
        human.run();

//상위 클래스 Vehicle 타입을 사용하여 각각의 객체의 메서드 값을 다르게 출력한다
        Vehicle bike2 = new Bike();
        Vehicle car2 = new Car();
        Vehicle human2 = new Human();

        bike2.run();
        car2.run();
        human2.run();
    }
}
class Vehicle{
    void run(){
        System.out.println("Vehicle is running");
    }
}
class Bike extends Vehicle{
    void run(){
        System.out.println("Bike is running");
    }
}
class Car extends Vehicle{
    void run(){
        System.out.println("Car is running");
    }
}
class Human extends Vehicle{
    void run(){
        System.out.println("Human is running");
    }
}

 

 3) 배열을 적용한 메서드 오버라이딩

 Vehicle[] vehicles = new Vehicle[]{new Bike(), new Car(), new Human()};
        for (Vehicle vehicle : vehicles) {
            vehicle.run();
        }

??? 이해가 잘 안간다...추가로 찾아보자.

 

5. super

 1) super

  • 자신보다 상위의 클래스에 있는 객체를 의미한다
  • 반드시 함수의 첫 줄에 위치하여야 한다

ex1) Parents의 변수 count와 Kim의 변수 count가 동일한 상황에서의 Kim 과 Parents의 나이를 출력

class Kim Parents       
variable age count count    
constructor Kim()        

- #19 에서 굳이 this 키워드를 사용하지 않아도 출력은 동일하다. 설명을 위하여 구분하기 위해 사용하였다

- 여기서 super 과 this() 의 차이점은 각각 상위클래스의 객체와 생성자를 의미한다는 부분이다

- 상속관계를 전제로 하며, super.변수명 의 형식으로 사용한다

※ 상속관계가 아닐 경우 에러(count 변수를 찾을 수 없다)가 발생한다

 

 2) super()

  • super은 객체를 의미하지만, super()는 생성자를 의미한다
  • 반드시 함수의 첫 줄에 위치하여야 한다

ex1) Parentsname의 변수 count와 Kimname의 변수 count가 동일한 상황에서의 Kimname 과 Parentsname의 나이를 출력하고, Parentsname를 상속받아 모두 출력

class Kimname Parentsname       
variable name count      
constructor Kimname Parentsname      

 

6. Object 클래스

  • 자바 클래스 상속계층도 중 최상위에 위치한 상위클래스이다
  • 자바의 모든 클래스는 Object클래스를 기본적으로 상속되면, Object클래스로부터 확장된다
  • 상속클래스가 없는 경우 자동으로 extends object를 추가하여 Object클래스가 상속된다
class Parent //컴파일러가 "extends Object" 자동 추가 { 
}
class Child extends Parent {
}

※ 기본 Object 메서드 

메서드명  반환 타입 주요 내용
toString() String 객체 정보를 문자열로 출력
equals(Object obj) boolean 등가 비교 연산(==)과 동일하게 스택 메모리값을 비교
hashCode() int 객체의 위치정보 관련. Hashtable 또는 HashMap에서 동일 객체여부 판단
wait() void 현재 쓰레드 일시정지
notify() void 일시정지 중인 쓰레드 재동작
 

 

 

▶ 상속 (프로그래밍) : http://wiki.hash.kr/index.php/%EC%83%81%EC%86%8D_(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D) 

 

상속 (프로그래밍) - 해시넷

상속(相續, inheritance)은 객체 지향 프로그래밍(OOP)에서 자손 클래스가 조상 클래스의 기능을 그대로 이어받아서 재사용하는 것을 말한다. 자바에서는 계승, 확장이라는 단어로 사용된다.[1] 객체

wiki.hash.kr

  상속 (객체 지향 프로그래밍) : https://ko.wikipedia.org/wiki/%EC%83%81%EC%86%8D_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D)

 

상속 (객체 지향 프로그래밍) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 객체 지향 프로그래밍(OOP)에서, 상속(inheritance)은 객체들 간의 관계를 구축하는 방법이다. 클래스로 객체가 정의되는 고전 상속에서, 클래스는 기반 클래스, 수

ko.wikipedia.org

  상속의 개념 : http://www.tcpschool.com/java/java_inheritance_concept 

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

   Java inheritance : https://www.youtube.com/watch?v=Zs342ePFvRI 

 

1. 필드(Field)

  • ‘클래스에 포함된 변수'를 의미하는 것으로 객체의 속성을 정의한다
  • 변수는 클래스 변수(cv, class variable), 인스턴스 변수(iv, instance variable), 지역 변수(lv, local variable) 세 가지로 구분될 수 있다
  • 필드는 클래스 변수와 인스턴스 변수이며, static 키워드의 유무로 구분할 수 있다
    - static 키워드가 함께 선언된 것은 클래스 변수이다
    - static 키워드가 없는 것은 인스턴스 변수이다
  • 두 가지 변수 유형에 포함되지 않고 메서드 내에 포함된 모든 변수를 지역변수라고 한다
  • 세 가지 유형의 변수들은 주로 선언된 위치에 따라 그 종류가 결정되며 각각 다른 유효 범위(scope)를 가진다
class Area { //클래스 영역 시작

	int instanceVariable; // 인스턴스 변수
	static int classVariable; // 클래스 변수(static 변수, 공유변수)

	void method() { 
    // 메서드 영역 시작

		int localVariable = 0; // 지역 변수. 속해있는 {}블록 안에서만 유효
	
    } //메서드 영역 끝

}//클래스 영역 끝

 

 1) 인스턴스 변수(iv)

  • 인스턴스가 가지는 각각의 고유한 속성을 저장하기 위한 변수이다
  • new 생성자() 를 통해 인스턴스가 생성될 때 만들어진다
  • 클래스를 통해 만들어진 인스턴스는 힙 메모리의 독립적인 공간에 저장된다
  • 동일한 클래스로부터 생성되었지만 객체의 고유한 개별성을 가진다
  • 사람마다 성별, 이름, 나이, MBTI가 다 다르듯 인스턴스 변수는 그 고유한 특성을 정의하기 위한 용도로 사용한다

 

 2) 클래스 변수(cv)

  •  static 키워드를 통해 선언한다
  • 클래스 변수는 독립적인 저장 공간을 가지는 인스턴스 변수와 다르게 공통된 저장공간을 공유한다
  • 한 클래스로부터 생성되는 모든 인스턴스 들이 특정한 값을 공유해야하는 경우에 주로 static 키워드를 사용하여 클래스 변수를 선언한다
  • 사람을 예로 들면 손가락과 발가락 개수와 같이 모든 사람이 공유하는 특성을 저장하는 데에 사용된다
  • 클래스 변수는 인스턴스 변수와 달리 인스턴스를 따로 생성하지 않고 '클래스명.클래스변수명' 을 통해 사용이 가능하다
  • area.classVariable 처럼 클래스 변수를 사용할 수 있다

 3) 지역변수(lv)

  • 지역변수는 메서드 내에 선언된다
  • 메서드 내({} 블록)에서만 사용가능한 변수이다
  • 멤버 변수와는 다르게 지역변수는 스택 메모리에 저장되어 메서드가 종료되는 것과 동시에 함께 소멸되어 더이상 사용할 수 없게 된다
  • 힙 메모리에 저장되는 필드 변수는 객체가 없어지지 않는 한 절대로 삭제되지 않지만, 스택 메모리에 저장되는 지역변수는 한동안 사용되지 않는 경우 가상 머신에 의해 자동으로 삭제된다

 

 4) 필드변수와 지역변수의 차이

  • 필드 변수와 지역 변수의 주요한 한 가지 차이점은 초기값에 있다
  • 지역변수는 직접 초기화하지 않으면 값을 출력할 때에 오류가 발생한다
  • 필드 변수는 직접적으로 초기화를 실행하지 않더라도 강제로 초기화가 이뤄진다
  • 메모리의 저장 위치와 긴밀한 연관성을 가진다
  • 힙 메모리에는 빈 공간이 저장될 수 없기 때문에 저장되는 필드는 강제로 초기화된다
  • 스택 메모리는 강제로 초기화되지 않으므로 지역 변수는 선언시 반드시 초기화를 실행해야 한다

 

2. static & instance 변수

 1) static 변수

  • 공유의 개념을 가지고 있다
  • 클래스 변수를 생성하는데 사용한다
  • 예제1)
public class StaticTest0 {
    public static void main(String[] args) {

    }

    class Car {
        public String str1 = "Instance Variable";
        public static String str2 = "class Variable";

        public static void method() {
            System.out.println(str1);
            System.out.println(str2);
        }

        public void method() {
            System.out.println(str1);
            System.out.println(str2);

        }
    }
}

<결과>
java: non-static variable str1 cannot be referenced from a static context
java: 정적 컨텍스트에서 비정적 변수 str1을 참조할 수 없습니다.

      - 위의 코드에서 어느 부분에서 에러가 발생할까?

      - public static void method() {
                  System.out.println(str1);
       :  출력 명령에서 발생한다
       :  str1은 인스턴스 변수로써 클래스 메서드 내에서는 참조를 할 수 없다
       :  클래스 메서드 내에서는 클래스 변수만이 참조가 가능하다

 

  • 예제2)
public class StaticTest1 {
    public static void main(String[] args) {

        Car.method1();
        Car.method2();

        System.out.println(Car.str1);
        System.out.println(Car.str2);

    }

    class Car {
        public String str1 = "Instance variable";
        public static String str2 = "class variable";

        public static void method1() {
//            System.out.println(str1);
            System.out.println(str2);
        }

        public void method2() {
            System.out.println(str1);
            System.out.println(str2);
        }
    }
}


java: non-static method method2() cannot be referenced from a static context
java: 정적 메서드2는 정적 컨텍스트에서 참조할 수 없습니다.

      - Car.method2();
              System.out.println(Car.str1);
       : 참조와 출력 명령에서 에러가 발생한다
       : method2 메서드는 인스턴스 메서드이므로 공유가 되지 않는다
       : str1 역시 인스턴스 변수로써 공유가 되지 않는다
      - 인스턴스 메서드를 참조하고져 하면 인스턴스를 new 연산자로 생성하여야 한다

  • 예제 3) 클래스 변수의 내용을 변경할 경우 전체가 변경된다
public class StaticTest1 {
    public static void main(String[] args) {

        Car car1 = new Car();
        Car car2 = new Car();

        car1.cv = "car1.str1 = Instance variable";

        System.out.println(car1.cv);
        System.out.println(car2.cv);
        System.out.println(Car.cv);

    }

    static class Car {
        public String iv = "Instance variable";
        public static String cv = "class variable";

        public static void method1() {
            System.out.println(cv);
        }
    }
}

  • 예제 4) 인스턴스 변수의 내용을 변경할 경우 변경한 iv만 적용된
public class StaticTest1 {
    public static void main(String[] args) {

        Car car1 = new Car();
        Car car2 = new Car();

        car1.iv = "car1.str1 = Instance variable";

        System.out.println(car1.iv);
        System.out.println(car2.iv);
//        System.out.println(Car.iv);

    }

    static class Car {
        public String iv = "Instance variable";
        public static String cv = "class variable";

        public static void method1() {
//            System.out.println(iv);
        }
    }
}

 

1. 문제

 1) 5명의 기말고사 시험점수의 합을 구하시오

 2) a=100, b=90, c=85, d=95, e=100

 3) 배열을 사용하시오

 

2. 풀이

 1) array와 for문을 사용한 코드

  • .length 메서드는 index 값으로 변황하는 역활을 한다
    - index로 변환하여 해당 위치에 있는 배열의 값을 환원한다
package array;

public class Test1 {
    public static void main(String[] args) {
        //scores 변수에 5개의 int 값을 배정한다
        int[] scores = {100, 90, 85, 95, 100};
        int sum =0;  //합계를 구하기 위하여 sum 변수를 0으로 지정한다

        //scores.length로 배열의 index값인 0~4까지를 지정한다
        for (int i=0; i<scores.length; i++) {
            sum = sum + scores[i];
            /* 0 = 0 + 100
               190 = 100 + 90
               275 = 190 + 85
               370 = 275 + 95
               470 = 370 + 100
             */
        }
        System.out.println(sum); //최종 sum 값이 출력된다
    }
}

 

 2) array와 while를 사용한 코드

package array;

public class ArrayWhile {
    public static void main(String[] args) {
        int[] scores = {100, 90, 85, 95, 100};
        int sum=0;
        int i=0;
        
        while(i<scores.length){
            sum = sum + scores[i++];
        }
        System.out.println(sum);
    }
}

 

3. 결과

 

'예제 > Java 예제' 카테고리의 다른 글

Java 예제 - 원금의 두배 구하기  (0) 2022.09.15
Java 예제 - 배열과 HashMap  (0) 2022.09.15
Java - HashMap 활용 - Id,Pw 확인  (0) 2022.09.14
Java 예제 - 백준 문제 - 10039  (0) 2022.08.02
coding test - 성적처리  (0) 2022.05.22

+ Recent posts