## 37. 네트워킹을 이용하여 데이터 공유하기 : Client/Server 아키텍처로 전환(이어서 계속)
- 네트워크 프로그래밍 방법
- Client와 Server 개념
- 프로토콜에 따라 애플리케이션 간에 데이터를 주고 받기
BoardNetworkDao.java
ㄴ 서버에서 보낼 명령과 데이터를 Map 객체에 담기
BoardNetworkDao.java
ㄴ 주어진 코드에서 request 객체의 "data" 키에 board 객체를 JSON 형식으로 변환한 값을 설정
=> 이를 위해 Gson 라이브러리를 사용하여 board 객체를 JSON 문자열로 변환한 후, 해당 JSON 문자열을 "data" 키에 설정
BoardNetworkDao.java
ㄴ Map 객체에 담은 정보를 JSON 문자열로 변환하여 서버에 보내기
BoardNetworkDao.java
ㄴ Gson 객체를 맨 위에서 한 번만 생성하여 이용
BoardNetworkDao.java
ㄴ 해당 코드 제거
BoardNetworkDao.java
ㄴ 주석으로 잠시 막아두고 해당 코드 추가
BoardNetworkDao.java
ㄴ GsonBuilder 의 setPrettyPrinting 메서드를 이용해서 gson.toJson(request) 값 정갈하게 출력해보기
=>
ㄴ JSON 형식의 값 확인 가능
BoardNetworkDao.java
ㄴ 다시 GsonBuilder => Gson 으로 변경
BoardNetworkDao.java
ㄴ 주석 다시 풀어주기
BoardNetworkDao.java
ㄴ message => result 로 변경해주기
BoardNetworkDao.java
ㄴ board 를 JSON 으로 바꾸는 이유는 꺼낼 때 쉽게 꺼내기 위해서임
BoardNetworkDao.java
ㄴ list 메서드에서는 insert 메서드에서 코드 복사 후 insert => list 로 변경
ㄴ insert 메서드와 동일하게 RuntimeException 에는 message 대신 result 를 넘겨주도록 함
BoardNetworkDao.java
ㄴ execute() 메서드에서 "quit" 명령을 서버로 전송하여 클라이언트 애플리케이션을 종료하도록 작성
ㄴ "quit" 명령을 서버에 전송하기 위해 HashMap인 request를 생성
=> "command" 키에는 "quit" 값을 설정
ㄴ out.writeUTF(new Gson().toJson(request))를 사용하여 request 맵을 JSON 형식의 문자열로 변환하고, 변환된 문자열을 DataOutputStream을 통해 서버로 전송
ㄴ 전송 중 발생하는 예외는 catch 블록에서 처리되며, "종료 오류!" 메시지를 출력하고 예외의 스택 트레이스를 출력
ㄴ 서버에 "quit" 명령을 전송함으로써 클라이언트 애플리케이션이 종료됨
BoardNetworkDao.java
ㄴ while 루프를 사용하여 계속해서 서버에서 전송된 JSON 문자열을 읽어옴
ㄴ in.readUTF()를 사용하여 DataInputStream을 통해 JSON 문자열을 읽어옴
ㄴ 읽어온 JSON 문자열은 gson.fromJson()을 사용하여 Map으로 변환
=> 변환할 클래스 타입으로 Map.class를 전달
ㄴ 변환된 Map에서 "command" 키를 사용하여 실제 명령을 추출
=> 서버로부터 전송된 JSON 문자열을 읽어와서 명령을 추출
BoardNetworkDao.java
ㄴ 오류 무시를 위해 @SuppressWarnings("unchecked") 추가
BoardNetworkDao.java
ㄴ 아까 위에서 result 로 넘겨주기로 했으므로 키 값을 result 로 변경
ㄴ data => result 로 변경
ㄴ message => result 로 변경
ㄴ report-client 의 bitcamp 패키지에 net 이라는 이름으로 패키지 생성
ㄴ bitcamp.net 패키지에 RequestEntity 클래스 생성
RequestEntity.java
ㄴ command, data 인스턴스 변수 생성
메서드 체이닝
RequestEntity.java
ㄴ RequestEntity 클래스의 command() 메서드 생성
ㄴ 해당 메서드는 command 필드 값을 설정하고, RequestEntity 객체 자체를 반환함
=> 메서드 체이닝(메서드 호출을 연결하여 연속적으로 호출할 수 있는 구조)을 지원
=> RequestEntity 객체를 생성하고 command() 메서드를 체인으로 호출하여 command 값을 설정할 수 있음
RequestEntity.java
ㄴ RequestEntity 클래스의 data() 메서드 생성
ㄴ 해당 메서드는 data 필드 값을 설정하고, RequestEntity 객체 자체를 반환함
ㄴ 메서드 내부에서는 주어진 data 객체를 JSON 형식으로 변환하여 data 필드에 저장함(Gson 라이브러리 사용)
=> RequestEntity 객체를 생성하고 data() 메서드를 체인으로 호출하여 data 값을 설정할 수 있음
RequestEntity.java
ㄴ getter 만 생성
ClientApp.java
ㄴ 해당 코드 제거 후 아래처럼 변경 가능
=>
ClientApp.java
ㄴ RequestEntity 에 생성한 command 메서드 이용
=>
ClientApp.java
ㄴ 한 줄로 변경 가능 => 체이닝 기법
=>
ClientApp.java
ㄴ request 변수 생성할 필요 없도록 코드 변경
BoardNetworkDao.java
ㄴ HashMap 대신 RequestEntity 에 생성한 command, data 메서드 이용
=>
BoardNetworkDao.java
ㄴ request 변수 생성하지 않아도 되도록 코드 변경
RequestEntity.java
ㄴ JSON 으로 바꾸는 코드 추가
BoardNetworkDao.java
ㄴ 체이닝 기법을 이용해 RequestEntity 클래스의 toJson 메서드를 이용함
ㄴ bitcamp.net 패키지에 ResponseEntity 클래스 생성
ResponseEntity.java
ㄴ status, result 인스턴스 변수 생성
ResponseEntity.java
ㄴ RequestEntity 클래스의 status 메서드 생성
ㄴ 해당 메서드는 status 필드 값을 설정하고, RequestEntity 객체 자체를 반환함
=> 메서드 체이닝(메서드 호출을 연결하여 연속적으로 호출할 수 있는 구조)을 지원
=> RequestEntity 객체를 생성하고 status 메서드를 체인으로 호출하여 status 값을 설정할 수 있음
ResponseEntity.java
ㄴ ResponseEntity 클래스의 result() 메서드
ㄴ 해당 메서드는 result 필드 값을 설정하고, ResponseEntity 객체 자체를 반환
ㄴ 메서드 내부에서는 주어진 obj 객체를 JSON 형식으로 변환하여 result 필드에 저장(Gson 라이브러리 사용)
ResponseEntity.java
ㄴ 만약 obj 객체의 클래스가 String 클래스와 동일하다면, obj를 그대로 result 필드에 저장하도록 코드 작성
RequesetEntity.java
ㄴ RequestEntity 클래스에서도 ResponseEntity 클래스처럼 조건문 추가해주기
=> RequsetEntity 클래스에서는 객체를 JSON 형식으로 변환하여 전송해야 함
ResponeEntity.java
ㄴ getter 만 생성
ResponeEntity.java
ㄴ fromJson 메서드를 생성하여 JSON 문자열을 ResponseEntity 객체로 변환하여 반환하는 역직렬화(deserialization) 작업을 수행하는 코드 작성
=> Gson 라이브러리의 fromJson 메서드는 JSON 문자열을 자바 객체로 변환하는 기능을 제공
ㄴ fromJson 메서드는 주어진 JSON 문자열 json을 ResponseEntity 클래스의 객체로 변환하여 반환
=> 클라이언트나 서버에서 전송된 JSON 응답을 다시 ResponseEntity 객체로 변환하여 필요한 데이터를 추출하고 처리
BoardNetworkDao.java
ㄴ 이제 Gson 객체는 필요 없으므로 제거
ㄴ ResponseEntity 객체로 만들어서 response 변수에 저장하도록 함
ㄴ Map 사용 시 이용했던 get("status") => getStatus() 로 변경 (getter 이용)
ㄴ Map 사용 시 이용했던 get("result") => getResult() 로 변경 (getter 이용)
ResponeEntity.java
ㄴ 개발자가 오타를 낼 수도 있으므로 상수로 정의해주기
BoardNetworkDao.java
ㄴ ResponseEntity 에 정의한 FAILURE 상수 값 이용
ㄴ !response ... ("success") => response ... (ResponseEntity.FAILURE) 로 변경
BoardNetworkDao.java
ㄴ 해당 코드 필요 없으므로 제거
BoardNetworkDao..java
ㄴ Gson 필요 없으므로 제거
ㄴ Map 대신 RequestEntity 클래스 이용
BoardNetworkDao..java
ㄴ Map 대신 ResponseEntity 클래스 이용
ㄴ ResponseEntity 에 정의한 FAILURE 상수 값 이용
ㄴ !response ... ("success") => response ... (ResponseEntity.FAILURE) 로 변경
ㄴ getResult() 는 String 값이므로 형변환 필요 없음
BoardNetworkDao..java
ㄴ ResponseEntity 클래스에 getList 메서드 추가할 것임
=>
ResponseEntity.java
ㄴ 제네릭 이용하여 getList 메서드 추가
ResponseEntity.java
ㄴ result 필드에 저장된 JSON 문자열을 주어진 클래스(clazz)의 타입으로 변환하여 해당 타입의 리스트로 반환
=> Gson 라이브러리의 fromJson 메서드를 사용
ResponseEntity.java
ㄴ BoardNetworkDao 클래스에서 해당 코드 가져옴
=>
ResponseEntity.java
ㄴ TypeToken import 해주기
ㄴ Board => clazz 로 변경
BoardNetworkDao..java
ㄴ 해당 코드 필요 없으므로 제거
ClientApp.java
ㄴ result 변수 생성할 필요 없도록 toJson 메서드에 new RequestEntity().command("quit") 코드를 넣어줌
ㄴ new Gson().toJson() => RequestEntity 클래스의 toJson() 이용하기
실행 test
ㄴ 실행 잘 됨을 확인
ServerApp.java
ㄴ insert 추가해주기
ㄴ app-client 복사하여 app-common 생성하기
ㄴ settings.gradle 에서 app-common 추가
ㄴ application 필요 없으므로 삭제
ㄴ java-library 이용하기 위해 applicatioin 제거 후 'java-library' 플러그인 추가
=> java-library: Java 라이브러리 프로젝트를 구성하기 위한 기능을 제공
ㄴ Java 라이브러리를 컴파일하고 패키징하는 데 필요한 태스크와 설정이 자동으로 추가됨
ㄴ build.gradle 스크립트 파일에서 프로젝트 이름 report-common 으로 수정
ㄴ gradle 재설정 해주기
ㄴ app-common 에 존재하는 ClientApp 삭제해주기
ㄴ 생성한 app-command 워크스페이스에 import 해주기
ㄴ app-client 를 복사한 프로젝트이므로 삭제할 파일들 삭제해주기
ㄴ app-client 에서도 net 패키지 삭제
report-client 프로젝트의 build.gradle
ㄴ 서브 프로젝트의 클래스를 사용하기 위해 해당 코드 추가
ㄴ gradle 재설정 해주기
=>
ㄴ 에러 없어짐
=> report-server 도 같은 방법으로 적용해주기
ServerApp.java
ㄴ Map 대신 myapp-common 프로젝트(서브 프로젝트)의 bitcamp.net 패키지에 있는 RequestEntity 사용
RequestEntity.java
ㄴ Gson 라이브러리를 사용하여 JSON 문자열을 RequestEntity 객체로 변환하는 fromJson 메서드 생성
ServerApp.java
ㄴ Map 이 아니므로 Map.class 제거
ServerApp.java
ㄴ Map 이 아닌 myapp-common 프로젝트(서브 프로젝트)의 bitcamp.net 패키지에 있는 RequestEntity 사용하므로 해당 클래스에 있는 getter 메서드인 getCommand() 이용
ServerApp.java
ㄴ Map 이 아닌 myapp-common 프로젝트(서브 프로젝트)의 bitcamp.net 패키지에 있는 RequestEntity 사용
ServerApp.java
ㄴ Map 이 아닌 myapp-common 프로젝트(서브 프로젝트)의 bitcamp.net 패키지에 있는 RequestEntity 사용
ㄴ status, result 메서드 이용
ServerApp.java
ㄴ Map 이 아닌 myapp-common 프로젝트(서브 프로젝트)의 bitcamp.net 패키지에 있는 RequestEntity 사용
ㄴ status, result 메서드 이용
ServerApp.java
ㄴ ResponseEntity 클래스에 toJson 메서드 추가해줄 것
ResponseEntity.java
ㄴ ResponseEntity 클래스에 toJson 메서드 추가함
ServerApp.java
ㄴ 해당 코드 필요 없으므로 제거
ServerApp.java
ㄴ insert 메서드 작성해주기
RequestEntity.java
ㄴ clazz가 String.class와 같다면, 데이터는 이미 문자열로 저장되어 있으므로 그대로 반환하도록 함
=> 이 경우 데이터는 T 타입으로 캐스팅하여 반환됨
ㄴ clazz가 String.class와 다르다면, Gson 라이브러리를 사용하여 데이터를 역직렬화하여 해당 클래스 타입으로 변환하여 반환함
=> 이 경우 Gson의 fromJson 메서드를 사용하여 JSON 문자열을 clazz의 인스턴스로 변환함
RequestEntity.java
ㄴ @SuppressWarnings("unchecked") 어노테이션은 경고를 억제하는 용도로 작성
RequestEntity.java
ㄴ ResponseEntity 클래스에 존재하는 getList 메서드 복사해오기
=>
RequestEntity.java
ㄴ result => data 로 변경
=> Request 에서는 data 로 되어있기 때문
ResponseEntity.java
ㄴ RequestEntity 클래스에 존재하는 getObject 메서드 복사해오기
=>
RequestEntity.java
ㄴ data => result 로 변경
=> Response 에서는 result 로 되어있기 때문
ServerApp.java
ㄴ boardDao.insert(request.getObject(Board.class))는 boardDao를 사용하여 요청으로 전달된 Board 객체를 데이터베이스에 삽입하는 작업을 수행
ㄴ response.status(ResponseEntity.SUCCESS)는 응답 객체인 response의 상태를 "success"로 설정하는 메서드
=> 클라이언트에게 성공 상태를 전달함
=> Gson 객체가 필요 없음
Client
Server
BoardNetworkDao.java
ㄴ findBy 메서드는 list 메서드와 비슷하므로 list 메서드 복사해서 붙여넣은 후 수정하기
=>
BoardNetworkDao.java
ㄴ .data(no): data 메서드를 호출하여 요청의 데이터(data)를 설정함 (no는 조회할 게시글의 번호)
ServerApp.java
ㄴ request.getObject(Integer.class)는 요청의 데이터를 Integer 객체로 변환하여 반환하는 메서드임
=> 요청으로 전달된 게시글 번호를 가져올 수 있음
ㄴ boardDao.findBy(request.getObject(Integer.class))는 boardDao를 사용하여 요청으로 전달된 게시글 번호에 해당하는 게시글을 데이터베이스에서 조회하는 작업을
ㄴ response.status(ResponseEntity.SUCCESS).result(board)는 응답 객체인 response의 상태를 "success"로 설정하고, 조회된 게시글을 응답 결과로 설정함
=> 클라이언트에게 성공 상태와 조회된 게시글을 전달함
ServerApp.java
ㄴ board 객체에 아무것도 담겨있지 않다면 응답 객체인 response의 상태를 "failure"로 설정하고, "해당 번호의 게시물이 없습니다!" 출력하도록 설정
ㄴ board 객체에 데이터가 담겨있다면 응답 객체인 response의 상태를 "success"로 설정하고, 조회된 게시글을 응답 결과로 설정함
BoardNetworkDao.java
ㄴ ResponseEntity 클래스에 있는 getObject(Board.class) 메서드를 이용하여 Board 객체로 변환하여 반환하도록 함
ResponseEntity.java
ㄴ error 를 상수 값으로 정의해두기
ServerApp.java
ㄴ "해당 명령을 지원하지 않습니다." 문장을 출력하는 것을 응답이 "error" 인 경우로 변경해줌
BoardNetworkDao.java
ㄴ FAILURE => ERROR 로 변경
ㄴ ResponseEntity.ERROR: 에러가 발생한 경우, 에러 메시지를 RuntimeException으로 던지도록 함
ㄴ ResponseEntity.FAILURE: 실패한 경우, null을 반환하도록 함
BoardNetworkDao.java
ㄴ ResponseEntity.ERROR인 경우에는 서버에서 발생한 에러 메시지를 RuntimeException으로 던지도록 함
ㄴ IOException이 발생한 경우에는 RuntimeException으로 래핑하여 던지도록 함
BoardNetworkDao.java
ㄴ ResponseEntity.FAILURE인 경우에는 서버에서 발생한 에러 메시지를 RuntimeException으로 던지도록 함
ㄴ IOException이 발생한 경우에는 RuntimeException으로 래핑하여 던지도록 함
BoardNetworkDao.java
ㄴ Exception => IOException 으로 변경
ㄴ IOException이 발생한 경우에는 RuntimeException으로 래핑하여 던지도록 함
실행 test
ㄴ 잘 실행됨을 확인
BoardNetworkDao.java
ㄴ update 는 insert 와 비슷하므로 해당 코드 insert 메서드에서 복사해오기
BoardNetworkDao.java
ㄴ 해당 코드는 findBy 메서드와 비슷하므로 findBy 메서드에서 복사해오기
ㄴ Board.class => Integer.class 로 변경
=> 리턴 값이 int 이기 때문
ㄴ response.getObject(Integer.class)를 호출하여 서버에서 받은 업데이트된 게시글의 번호를 반환함
=> Integer 는 자동 언박싱 되어 int 로 변환됨
(Java에서는 기본 타입(primitive type)을 클래스로 표현하기 위해 래퍼 클래스를 사용함)
=> response.getObject(Integer.class) 에서 .intValue() 가 생략된 것 (컴파일 시 컴파일러가 자동으로 삽입함)
ㄴ response.getObject(Integer.class)는 ResponseEntity 객체에서 Integer 형식의 데이터를 가져옴
=> getObject() 메서드는 Object 타입의 데이터를 반환하므로, Integer 형식으로 언박싱이 필요
ㄴ response.getObject(Integer.class)가 Integer 객체를 반환할 경우 자동으로 언박싱되어 int 값으로 변환
ㄴ ex) response.getObject(Integer.class)가 10을 반환한다면, 자동으로 언박싱되어 int 값인 10으로 사용 가능
ServerApp.java
ㄴ if 문을 switch 문으로 변경
ㄴ request.getObject(Board.class)는 RequestEntity 객체에서 data 필드를 가져와서 해당 필드 값을 Board 클래스로 변환하도록 함
ㄴ getObject() 메서드는 요청받은 데이터를 주어진 클래스 타입으로 변환하여 반환도록 함
=> request.getObject(Board.class)는 Board 객체를 얻기 위해 data 필드의 값을 Board 클래스로 변환하는 것을 의미함
실행 test
BoardNetworkDao.java
ㄴ findBy 메서드에서 해당 코드 복사해와서 수정하기
ㄴ findby => delete
BoardNetworkDao.java
ㄴ update 메서드에서 해당 코드 복사해오기
ServerApp.java
ㄴ delete 추가
ㄴ request 객체의 data 필드에 저장된 정수 값을 가져와서 Integer 객체로 변환
실행 test
ㄴ 잘 실행됨을 확인
=> 나머지 ItemNetworkDao 와 MemberNetworkDao 모두 BoardNetworkDao 와 동일하게 완성해보기
파일 정리
ㄴ report-common 의 bitcamp.report.vo 패키지를 생성하여 report-server 의 bitcamp.vo 패키지에 있는 파일들 모두 복사하여 붙여넣기
ㄴ report-server 와 report-client 에 있는 bitcamp.report.vo 패키지는 삭제하기
ㄴ report-common 의 bitcamp.report 패키지에 dao 패키지 추가
ㄴ report-server 의 bitcamp.report.dao 패키지에서 각각의 XxxDao 파일만 복사하기
ㄴ report-common 에서 생성한 dao 에 복사해둔 XxxDao 파일들 붙여넣기
ㄴ 이제 report-server 의 bitcamp.report.dao 패키지에서 각각의 XxxDao 파일은 삭제
ㄴ report-client 의 bitcamp.report.dao 패키지에서 각각의 XxxDao 파일은 삭제
ㄴ report-client 의 bitcamp.report.dao 패키지에서 각각의 XxxListDao 파일들도 삭제
ㄴ report-server 의 bitcamp.report.handler 패키지 통째로 삭제
ㄴ report-server 의 bitcamp.test 패키지 통째로 삭제
ㄴ report-server 의 bitcamp.util 패키지에서 JsonDataHelper, Prompt 제외하고 모두 삭제
ㄴ report-client 의 bitcamp.test 패키지 통째로 삭제
ㄴ report-client 의 bitcamp.util 패키지에서 JsonDataHelper.java 삭제
ㄴ report-client 에서 build.gradle 을 제외한 데이터 파일 모두 삭제
ㄴ report-server 에서 build.gradle 파일과 json 파일 제외한 나머지 데이터 파일 모두 삭제