지난 번에 개념을 공부했으니 오늘은 Github을 이용하여 CI를 구현하는 방법을 포스팅 : ) 미래의 나를 위한 포스팅이다.

 

 

1. Actions -> new workflow -> Java with Gradle [configure] 클릭

 

2. <>Edit new file 에 코드 추가 -> Start commit

 

Test용으로 생성한 파일 

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
    - name: Grant execute permission for gradlew       // 이부분은 추가로 작성해줘야 한다
      run: chmod +x ./gradlew            			   // Build전에  gradlew 권한을 부여하는 워크플로우
# Build     
    - name: Build with Gradle
      uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
      with:
        arguments: build

 

아래는 우리 프로젝트에 맞게 커스텀되어있는 코드

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
  pull_request:
    branches: [ "dev" ]

env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: my-cicd-s3-bucket
  CODE_DEPLOY_APPLICATION_NAME: my-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: my-codedeploy-deployment-group

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      # 기본 체크아웃
      - name: Checkout
        uses: actions/checkout@v3

      # JDK 11 세팅
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      # 키 정보 있는 properties 따로 관리
      - name: make application.properties
        if: true
        run: |
          cd ./src/main/resources
          touch ./application.properties
          echo "${{ secrets.PROPERTIES }}" > ./application.properties
        shell: bash

      # gradlew 권한 설정
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash

      # build
      - name: Build with Gradle
        run: ./gradlew clean build -x test
        shell: bash

      # aws 인증
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      # 압축파일 형태로 전달
      - name: Make zip file
        run: zip -qq -r ./$GITHUB_SHA.zip .
        shell: bash

      # S3 bucket으로 copy
      - name: Upload to AWS S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip

      # S3 bucket에 있는 파일을 대상으로 CodeDeploy 실행
      - name: Deploy to AWS EC2 from S3
        run: aws deploy create-deployment --application-name $CODE_DEPLOY_APPLICATION_NAME --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name $CODE_DEPLOY_DEPLOYMENT_GROUP_NAME --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip

 

3. 나는 이미 CI/CD 적용을 모두 마친상태이기 때문에 위의 코드를 저장하고 테스트용으로 브랜치를 만들어서 main 브랜치에 merge를 해주면 아래와 같이 "This branch is being deployed" 라는 문구를 볼 수 있다.

 

아직 CD 적용을 안 한 상태라면 아래와 같이 "Some checks haven't completed yet" 이라는 checking 문구를 볼 수 있다.

https://littlezero48.tistory.com/289

 

 

4. 다시 Actions 탭을 가보면 아래와 같이 진행 상황을 확인 할 수 있다. 다시 말하지만, 나는 이미 CD를 끝낸 상태로 포스트를 작성했기 때문에 상단에 "Deploy"라고 뜨지만, CI 까지만 진행한 경우 상단에 "build"라는 표시와 함께 아래와 비슷한 화면이 보인다 !

 

이로써 CI는 끝났다! 아주 간단함 ! 

 


[ 참고 자료 ]

 

https://littlezero48.tistory.com/289

 

CI/CD] Github Actions CI

📌 Workflow 선택 깃헙에서 Actions > gradle 검색 > java with Gradle 의 Cofigure 클릭 📌 Workflow 선택 📍 yml이 뭔 파일이지? Yet another Markup Language의 약자로 yml 또는 yaml 확장자로 사용된다. 여러 configuration을

littlezero48.tistory.com

 

소영님 감사합니다 !! ㅎㅎ 소영님 블로그 없으면 아무것도 못할 1인 = 나

1. 어려웠던 부분 

  • 오늘 백엔드팀은 프로젝트에 관하여 급하게 처리할 사항이 없어서 코드리뷰의 시간을 가졌다. S3는 이전 프로젝트에서 사용해 봤기 때문에 대략적으로 흐름을 알고 있었지만, 웹소켓과 웹알티씨 관련된 부분은 코드를 보아도 이해가 잘 되지 않았다. 
  • 그래도 담당하신 팀원분이 차근히 설명을 해주셔서 대략적으로 이게 어떻게 돌아가는지는 알 것 같았다. 

 

2. 느낀 점 : 

  • 뷰작업에 한창인 프론트엔드와 달리 백엔드는 비교적 일찍 작업이 마무리된다. 덕분에 내가 구현하지 않은 부분을 더 공부할 수 있다. 코드를 많이 봐야 한다고 들었는데, 코드를 보면 볼수록 이 컴퓨터의 세계는 정말 신선하다. 알파벳 몇자로 애플리케이션이 만들어지는 것, 그리고 애플리케이션이 우리의 생활을 더 편리하게 해줄 수 있는 것, 내가 지금까지 살면서 한번도 궁금해하지 않고 알지 못한 세계이다. 좋은 개발자가 되서 사회에 도움이 되는 프로그램을 만들고 싶다.

 

3. 새로 알게 된 내용 :

  • 아래 코드는 웹소켓 연결과 관련된 코드이다.
// 기능 : WebRTC를 위한 시그널링 서버 부분으로 요청타입에 따라 분기 처리
@Slf4j
@Component
public class SignalHandler extends TextWebSocketHandler {

    @Autowired
    private GameRoomService gameRoomService;
    @Autowired
    private RepositoryService repositoryService;
    private final SessionRepository sessionRepository = SessionRepository.getInstance();  // 세션 데이터 저장소
    private final ObjectMapper objectMapper = new ObjectMapper();
    private static final String MSG_TYPE_JOIN_ROOM = "join_room";
    private static final String MSG_TYPE_OFFER = "offer";
    private static final String MSG_TYPE_ANSWER = "answer";
    private static final String MSG_TYPE_CANDIDATE = "candidate";

    @Override
    public void afterConnectionEstablished(final WebSocketSession session) {
        // 웹소켓이 연결되면 실행되는 메소드
    }

    // 시그널링 처리 메소드
    @Override
    protected void handleTextMessage(final WebSocketSession session, final TextMessage textMessage) {

        try {
            WebSocketMessage message = objectMapper.readValue(textMessage.getPayload(), WebSocketMessage.class);
            String userName = message.getSender();
            Long roomId = message.getRoomId();

            switch (message.getType()) {
                // 처음 입장
                case MSG_TYPE_JOIN_ROOM:

                    if (sessionRepository.hasRoom(roomId)) {
                        // 해당 챗룸이 존재하면
                        // 세션 저장 1) : 게임방 안의 session List에 새로운 Client session정보를 저장
                        sessionRepository.addClient(roomId, session);
                    } else {
                        // 해당 챗룸이 존재하지 않으면
                        // 세션 저장 1) : 새로운 게임방 정보와 새로운 Client session정보를 저장
                        sessionRepository.addClientInNewRoom(roomId, session);
                    }

                    // 세션 저장 2) : 이 세션이 어느 방에 들어가 있는지 저장
                    sessionRepository.saveRoomIdToSession(session, roomId);

                    // 세션 저장 3) : 방 안에 닉네임들 저장
                    sessionRepository.addNicknameInRoom(session.getId(), message.getNickname());

                    Map<String, WebSocketSession> joinClientList = sessionRepository.getClientList(roomId);

                    // 방안 참가자 중 자신을 제외한 나머지 사람들의 Session ID를 List로 저장
                    List<String> exportSessionList = new ArrayList<>();
                    for (Map.Entry<String, WebSocketSession> entry : joinClientList.entrySet()) {
                        if (entry.getValue() != session) {
                            exportSessionList.add(entry.getKey());
                        }
                    }

                    Map<String, String> exportNicknameList = new HashMap<>();
                    for (Map.Entry<String, WebSocketSession> entry : joinClientList.entrySet()) {
                        if (entry.getValue() != session) {
                            exportNicknameList.put(entry.getKey(), sessionRepository.getNicknameInRoom(entry.getKey()));
                        }
                    }

                    // 접속한 본인에게 방안 참가자들 정보를 전송
                    sendMessage(session,
                            new WebSocketResponseMessage().builder()
                                    .type("all_users")
                                    .sender(userName)
                                    .data(message.getData())
                                    .allUsers(exportSessionList)
                                    .allUsersNickNames(exportNicknameList)
                                    .candidate(message.getCandidate())
                                    .sdp(message.getSdp())
                                    .build());

                    break;

                case MSG_TYPE_OFFER:
                case MSG_TYPE_ANSWER:
                case MSG_TYPE_CANDIDATE:

                    if (sessionRepository.hasRoom(roomId)) {
                        Map<String, WebSocketSession> oacClientList = sessionRepository.getClientList(roomId);

                        if (oacClientList.containsKey(message.getReceiver())) {
                            WebSocketSession ws = oacClientList.get(message.getReceiver());
                            if(!ws.isOpen()){
                            }
                            sendMessage(ws,
                                    new WebSocketResponseMessage().builder()
                                            .type(message.getType())
                                            .sender(session.getId())            // 보낸사람 session Id
                                            .senderNickName(message.getNickname())
                                            .receiver(message.getReceiver())    // 받을사람 session Id
                                            .data(message.getData())
                                            .offer(message.getOffer())
                                            .answer(message.getAnswer())
                                            .candidate(message.getCandidate())
                                            .sdp(message.getSdp())
                                            .build());
                        }
                    } else {
                        throw new CustomException(CHAT_ROOM_NOT_FOUND);
                    }
                    break;

                default:

            }
        } catch (JsonProcessingException e) {
        } catch (Exception e) {
        }
    }

    // 웹소켓 연결이 끊어지면 실행되는 메소드
    @Override
    public void afterConnectionClosed(final WebSocketSession session, final CloseStatus status) {
        String nickname = sessionRepository.getNicknameInRoom(session.getId());
        // 끊어진 세션이 어느방에 있었는지 조회
        Long roomId = sessionRepository.getRoomId(session);

        // 1) 방 참가자들 세션 정보들 사이에서 삭제
        sessionRepository.deleteClient(roomId, session);

        // 2) 별도 해당 참가자 세션 정보도 삭제
        sessionRepository.deleteRoomIdToSession(session);

        // 3) 별도 해당 닉네임 리스트에서도 삭제
        sessionRepository.deleteNicknameInRoom(session.getId());

        // 본인 제외 모두에게 전달
        for(Map.Entry<String, WebSocketSession> oneClient : sessionRepository.getClientList(roomId).entrySet()){
            sendMessage(oneClient.getValue(),
                    new WebSocketResponseMessage().builder()
                            .type("leave")
                            .sender(session.getId())
                            .receiver(oneClient.getKey())
                            .build());
        }
        Optional<Member> member = repositoryService.findMemberByNickname(nickname);
        List<GameRoomAttendee> gameRoomAttendeeList = repositoryService.findAttendeeByRoomId(roomId);
        for(GameRoomAttendee gameRoomAttendee : gameRoomAttendeeList) {
            if(nickname.equals(gameRoomAttendee.getMemberNickname())){
            	// 브라우저를 강제로 닫을 경우 종료시키는 웹소켓 연결 끊기
                gameRoomService.roomExit(roomId, member.get());
            }
        }
    }

    // 메세지 발송
    private void sendMessage(WebSocketSession session, WebSocketResponseMessage message) {
        try {
            String json = objectMapper.writeValueAsString(message);
            synchronized (session){
                session.sendMessage(new TextMessage(json));
            }
        }
        catch (IOException e) {
        }
    }
}
  • 내가 이해한 바로는, 백엔드에서는 시그널링 서버 역할을 하는 코드를 구현해준다. 웹소켓연결을 할 때, 제일 처음엔 HTTP통신으로 한번 클라이언트끼리 연결을 해줘야 하는데 이때 사용된다. 한번 연결된 클라이언트는 이 이후엔 서버없이 서로 연결이 되어 통신을 한다.
  • 프론트엔드 팀원분의 요청으로 MSG_TYPE_JOIN_ROOM, MSG_TYPE_OFFER, MSG_TYPE_ANSWER, MSG_TYPE_CANDIDATE 라는 타입을 만들어서 차례로 프론트에서 이러한 타입들로 들어오는 요청을 그대로 반환해 주는 코드이다. 
  • 백엔드 코드만 봐선 사실 이해가 잘 되지 않는다. 아래 팀원분의 블로그를 참고해서 흐름을 좀 더 쉽게 이해했다 ! 
  • https://littlezero48.tistory.com/260
 

WebRTC 시그널링 서버 (Spring Boot로 구현)

📌 이 기술을 공부한 계기! 항해99 실전 프로젝트에서 게임 사이트를 제작하면서 화상 및 음성 채팅이 필요했고 우리는 WebRTC를 채택했다. 그런데 구현하는 과정에서 백엔드 입장에서 구현해야

littlezero48.tistory.com

 

4. 셀프칭찬 (오늘 잘한 일) 

  • 다른 팀원들의 코드를 이해하기위해 노력했다. 이렇게 시간이 남을 때 풀어지지 않기 위해 계속 우테코 영상도 찾아보고 개념공부를 하려고 노력하고 있다. 존버하는 코린이 

 

5. 내일 할 일 : 서비스 배포 전 마케팅 회의 / 트러블슈팅 정리 / CD 꼭 성공하기 ! 


[오늘 공부한 부분] 

  • 코드리뷰

OAuth 란?

  • OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다
  • OAuth가 사용되기 전에는 인증방식의 표준이 없었기 때문에 기존의 기본인증인 아이디와 비밀번호를 사용하였는데, 이는 보안상 취약한 구조이다.
  • OAuth를 이용하면 인증을 공유하는 애플리케이션끼리는 별도의 인증이 필요없다. 따라서 여러 애플리케이션을 통합하여 사용하는 것이 가능하게 된다. 

 

생활코딩 :&nbsp;https://www.youtube.com/watch?v=BofCK1oWAyc&list=PLuHgQVnccGMA4guyznDlykFJh28_R08Q-&index=6

 

용어 설명

  • Resource Owner : 리소스의 권한을 가진 사용자. 사용자 혹은 유저(User)라고도 한다. ex) 나몰닭 유저
  • Client : 리소스 서버에 접근을 요청하는 어플리케이션 혹은 웹 사이트. 즉, 서비스를 제공자이며 Third Party Application 라고도 한다. ex) 나몰닭이 여기에 해당
  • Authorization Server : 액세스 토큰(Access Token)을 클라이언트에게 발급해주는 서버. 인가의 주체. ex) 카카오톡, 구글, 네이버 등
  • Resource Server : Authorization Server가 발급한 액세스 토큰(Access Token)을 확인하고 리소스를 제공. API 서버. ex) 이름, 나이 등 아이디 관련 정보(리소스)를 제공하는 서버. 카카오톡, 구글, 페이스북, Github 등
  • cf.) Authorization Server와 Resource Server를 묶어서 OAuth Provider라고도 한다.

  • Client ID : 우리가 운영하는 애플리케이션을 식별하는 아이디
  • Client Secret : Client ID에 대한 비밀번호 - 외부에 노출되어선 안된다.
  • Authorized redirect URIs : Resource Server로 부터 Authorized code를 받을 주소

 

OAuth2.0 승인 방식(Grant Type)의 종류

Authorization Code Grant Type

  • OAuth2.0 Grant Type에서 가장 잘 알려진 방식이며 중요한 보안 이점을 제공하는 방법
  •   클라이언트가 사용자 대신 특정 리소스에 접근을 요청할 때 사용된다. 클라이언트는 Access Token을 발급 받기 전, 인가코드(Authorization Code)를 사용자에 의해 받게 되고, 그 인가코드(Authorization Code)를 가지고 인가 서버(Authorization Server)에 요청을 보내면 리소스에 대한 Access Token을 발급 받을 수 있다.

 

Implicit Grant Type

  • Authorization Code Grant Type과 다르게, 인가코드(Authorization Code) 교환 단계 없이 Access Token을 즉시 반환받아 이를 인증에 이용하는 방식

Resource Owner Password Credentials Grant Type

  • 클라이언트가 암호를 사용하여 Access Token에 대한 사용자의 자격 증명을 교환하는 방식

Client Credentials Grant Type

  • 클라이언트가 컨텍스트 외부에서 Access Token을 얻어 특정 리소스에 접근을 요청할 때 사용하는 방식

 

OAuth 2.0 흐름

 

Sequence Diagram&nbsp;of Authorization Code Grant

 

 

1. Resoruce Owner인 User가 Client를 통해서 인가(Authorization)를 시작한다.

2. Client는 User를 Authorization Server로 포워드 해준다.

3. Authorization Server는 User에게 로그인창을 제공하여 User를 인증한다.

4. User가 ID/Password를 입력하여 로그인 하면, Client가 Resource에 대한 접근 권한을 요청한다.

5. Authorization Server는 Authorization Code를 Client에게 제공해준다.

6. Client는 Authorization Code로 Authorization Server에 Access Token을 요청한다.

7. Authorization Server는 Client에게 Access Token을 발급해준다.

8. Access Token을 이용하여 Resource Server의 자원에 접근할 수 있게 된다.

9. 이후 Access Token이 만료되면 Refresh Token을 이용하여 Access Token을 재발급 받을 수 있다.

 


[ 참고 자료 ]

 

https://thatisgood.tistory.com/entry/OAuth20-OAuth20-%EC%9D%B4%EB%A1%A0-%EC%A0%95%EB%A6%AC-01

 

[OAuth2.0] OAuth2.0 이론 정리 #01

OAuth란? OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는,

thatisgood.tistory.com

https://opentutorials.org/course/3405

 

WEB2 - OAuth 2.0 - 생활코딩

수업소개 사용자가 가입된 서비스의 API에 접근하기 위해서는 사용자로부터 권한을 위임 받아야 합니다. 이 때 사용자의 패스워드 없이도 권한을 위임 받을 수 있는 방법이 필요합니다. 이를 위

opentutorials.org

https://data-jj.tistory.com/53

 

REST-API 활용한 카카오 소셜 로그인 구현(feat. React)

프로젝트를 진행하면서 소셜 로그인 구현을 맡게 되었다. 다들 프론트엔드는 소셜 로그인에서 할게 많이 없다 쉽다~, 그중에서 카카오가 가장 쉽다~ 이렇게 얘기해서 방심했다. 그렇게 6일간의

data-jj.tistory.com

 

 

 

 

1. 어려웠던 부분 

  • 오늘 팀원A분이 CI/CD하는 것을 화면공유로 볼 수 있었다. 지난주부터 계속 해결되지 않던 오류들이 있어서 아직 성공하지 못했는데, 오늘 팀원B분이 성공하셨다. 사실 나도 직접해봐야 더 도울 수 있고, 이해도 될텐데 의욕이 없다. 

 

2. 느낀 점 : 

  • 벌써 항해를 시작한지 3달정도 되었다. 첫 시작부터 카운트 하자면 3달 반이 지났다. 그리고 이제 3주가량 남았다. 그동안 열심히 해왔다고 생각했지만, 아직 턱없이 부족하다. 처음 항해99를 시작했을 땐, 취업률 90프로 이상, 그리고 기술매니저님들도 수료만 하면 일단 취업은 된다고 하시길래 그동안 힘들고 포기하고 싶던 적이 많았지만 배수진을 쳤다 생각하고 꾹 참고 버텨왔다. 
  • 그런데, 이제 곧 수료가 다가오고 채용사이트를 보면서 객관적으로 나의 실력에 대해 생각해보니 이대로는 어디도 갈 수 없을 것 같다. 항해99가 끝나면 물론 지원은 해보겠지만, 꽁꽁 얼어버린 채용시장에서 과연 지금의 내가 경쟁력이 있을까. 그렇다면 더 공부를 해야할까? 더 공부를 한다고 꽁꽁얼어버린 개발자 취업시장에서 취업할 수 있을까? 등 생각이 많아지는 요즘이다. 
  • 그리고 3개월 전에 수료를 마친 8기 분들의 취업률이 58%라고 들었다. 이런 경제상황 속에 나쁘지 않은 취업률이라는 생각이 들다가도, 어 그럼 세달이나 지났는데 40프로는 아직도 취준이겠구나. 이게 나의 미래인가 하는 생각도 들었다. 착잡하다.

 

3. 새로 알게 된 내용 :

  • 오늘 백엔드 팀원들이랑 지난주에 고려했던 동시성제어 테스트를 했다. 근데 생각보다 짜여지 로직이 괜찮은지 조금씩 고치다보니 우려했던 동시성 제어문제는 발생하지 않았다.
  • 게임방 입장과 퇴장만 리더분이 동시성제어로 비관적락을 걸어 주셨고, 그 외의 로직엔 동시성제어와 관련된 코드가 필요하지 않았다.(아직까지는)
  • 그리고 오늘 그동안 테스트하느라 여기저기 중구난방으로 붙여놨던 로그도 지우고 불필요한 주석이나 코드를 정리하는 리팩토링 작업을 했다. 게임로직도 게임과 게임레어 두 가지 서비스로 나눠있었는데, Repository Service 클래스를 만들어 그와 관련된 로직을 다 분리하니 코드가 생각보다 길지 않아 하나로 통합했다. 

 

4. 셀프칭찬 (오늘 잘한 일) 

  • 오늘은 아침 점심 저녁 산책을 3번 나갔다. 생각이 많을 땐 걷는게 최고다.
  • 최고로 의욕이 없고 집중이 잘 되지 않는 날, 자신감이 최저인 오늘, 그래도 조금이라도 잘 보내고 싶어서 개발자 취업 관련된 영상도 많이 보고 미뤄왔던 혼공자 마지막 챕터를 공부했다. 

 

5. 내일 할 일 : 코드리뷰 + 리팩토링


[오늘 공부한 부분] 

 

  • 코드 리팩토링 - 관심사 분리 + 게임 로직 합치기
  • 게임방을 게임중에 그냥 창 자체를 닫아버렸을 경우 혹은 그냥 닫아버릴 경우 에러없이 DB에서도 잘 삭제되도록 하는 로직 추가
  • 자바 보조스트림 공부
  • 팀원들이 진행하는 CI/CD 관찰

[33] JAVA 보조 스트림

 

[33] JAVA 보조 스트림

보조 스트림 다른 스트림과 연결이 되어 여러 가지 편리한 기능을 제공해주는 스트림 자체적으로 입출력을 수행할 수 없기 때문에 입출력 소스와 바로 연결되는 InputStream, OutputStream, Reader, Writer

leejincha.tistory.com

[34] JAVA 입출력 API

 

보조 스트림 

  • 다른 스트림과 연결이 되어 여러 가지 편리한 기능을 제공해주는 스트림
  • 자체적으로 입출력을 수행할 수 없기 때문에 입출력 소스와 바로 연결되는 InputStream, OutputStream, Reader, Writer 등에  연결해서 입출력을 수행한다.
  • 문자 변환, 입출력 성능 햐상, 기본 타입 입출력 등의 기능을 제공한다.

https://hudi.blog/java-filter-stream/

 

문자 변환

  • 소스 스트림이 바이트 기반 스트림(InputStream, OutputStream, FileInputStream, FileOutputStream)이면서 입출력 데이터가 문자라면 Reader와 Writer로 변환해서 사용하는 것을 고려할 수 있다.
  • 그 이유는 문자 입출력은 Reader와 Writer가 편리하기 때문
  • OutputStreamWriter는 Writer로 변환하는 보조 스트림이고, InputStreamReader는 Reader로 변환하는 보조 스트림이다.

 

성능 향상

  • 기본적으로 출력 스트림은 내부에 작은 버퍼를 가지고 있다. 하지만 이것만으로는 불충분하다.
  • 보조 스트림 중에서는 메모리 버퍼를 추가로 제공하여 프로그램의 실행 성능을 향상시키는 것들이 있다.
  • 바이트 기반 스트림에서느 BufferedInputStream, BuffuredOutputStream이 있고 문자 기반 스트림에는 BuffuerdReader, BuffuredWriter가 있다.

 

기본 타입 입출력 

  • DataInputStream과 DataOutputStream 보조  스트림을 연결하면 기본타입인 boolean, char, short, int, long, float, double을 입출력할 수 있다.

개행 출력

  • PrintStream/PrintWriter의 pringln() 메소드는 출력할 데이터 끝에 개행 문자인 '\n'을 추가한다.
  • 그래서 출력 시 콘솔이나 파일에서 줄 바꿈이 일어난다.

'Coding > Java' 카테고리의 다른 글

[32] JAVA 입출력 스트림  (0) 2022.12.28
[31] JAVA LIFO와 FIFO 컬렉션  (0) 2022.12.25
[30] JAVA 컬렉션 프레임워크  (0) 2022.12.25
[29] JAVA 스레드 제어  (0) 2022.12.21
[28] JAVA 멀티스레드  (0) 2022.12.21

+ Recent posts