## 38. DAO 프록시 객체를 자동 생성하기
- java.lang.reflect.Proxy 클래스 사용법
- 프록시 객체의 구동원리 이해
ㄴ bitcamp 패키지에 dao 패키지 추가
ㄴ bitcamp.dao 패키지에 DaoInvocationHandler 클래스를 추가
DaoInvocationHandler.java
ㄴ InvocationHandler 인터페이스는 Java의 리플렉션을 이용하여 동적으로 메서드 호출을 처리함
DaoInvocationHandler.java
=>
DaoInvocationHandler.java
ㄴ invoke 메서드를 오버라이딩하여 실제 메서드 호출을 처리하는 로직을 구현
=> invoke 메서드는 proxy 객체, 호출된 메서드, 전달된 인자들을 파라미터로 받아서 필요한 작업을 수행한 후 결과를 반환함
ㄴ proxy: 프록시 객체. 호출된 메서드가 속한 인터페이스를 구현한 프록시 객체
ㄴ method: 호출된 메서드 객체. 호출된 메서드의 정보를 담고 있는 Method 객체
ㄴ args: 전달된 인자 배열. 호출된 메서드에 전달된 인자들을 담고 있는 배열
DaoInvocationHandler.java
ㄴ 메서드의 리턴타입이 int 타입의 경우 0을, void 타입의 경우 null을 반환하고, 그 외의 경우에는 null을 반환하도록 함
DaoInvocationHandler.java
ㄴ DaoInvocationHandler 클래스를 활용하여 BoardDao 인터페이스를 구현한 프록시 객체 boardDaoProxy를 생성
ㄴ Proxy.newProxyInstance() 메서드의 인자로는 다음과 같은 정보가 전달됨
=> ClassLoader: 동적으로 생성된 프록시 클래스의 로딩을 담당할 클래스 로더
ㄴ 주로 getClassLoader() 메서드를 통해 현재 클래스의 클래스 로더를 사용함
=> Interfaces: 프록시 객체가 구현할 인터페이스들의 배열
ㄴ BoardDao 인터페이스를 구현하기 위해 배열에 해당 인터페이스를 추가함
=> InvocationHandler: 프록시 객체의 메서드 호출을 처리할 InvocationHandler 구현 객체
ㄴ DaoInvocationHandler 클래스의 인스턴스를 전달하여 메서드 호출을 처리함
=> boardDaoProxy 객체는 BoardDao 인터페이스를 구현한 프록시 객체로, 메서드 호출 시 DaoInvocationHandler 클래스의 invoke() 메서드가 동적으로 호출되어 원하는 로직을 처리함
DaoInvocationHandler.java
ㄴ boardDaoProxy를 통해 다양한 메서드를 호출하고 있는데, 이는 프록시 객체를 통해 메서드 호출이 발생하면 DaoInvocationHandler의 invoke 메서드가 실행되어 원하는 작업을 수행함
=> 각각의 메서드 호출에 대해 DaoInvocationHandler의 invoke 메서드에서 정의한 작업을 수행함
ㄴ 메서드 호출 시 DaoInvocationHandler의 invoke 메서드가 실행되어 프록시 객체의 동작을 확인가능
DaoInvocationHandler 의 main 메서드 실행
DaoInvocationHandler.java
ㄴ dataName 인스턴스 변수 선언
DaoInvocationHandler.java
ㄴ DaoInvocationHandler 클래스의 생성자 추가
=> dataName 초기화
DaoInvocationHandler.java
ㄴ DaoInvocationHandler 생성자에 "board" 전달하도록 함
DaoInvocationHandler.java
ㄴ DataInputStream, DataOutputStream 을 사용하기 위한 인스턴스 변수 선언
DaoInvocationHandler.java
ㄴ 생성자에서 DataInputStream, DataOutputStream 정보를 받도록 함
DaoInvocationHandler.java
ㄴ 해당 코드들은 테스트를 위해 생성한 것이므로 제거함
DaoInvocationHandler.java
ㄴ 아규먼트 타입을 알아내기 위해 해당 코드 추가
DaoInvocationHandler.java
ㄴ BoardNetworkDao 클래스의 list 메서드에서 해당 코드 복사해오기
DaoInvocationHandler.java
ㄴ RequestEntity 객체를 생성해줌
DaoInvocationHandler.java
ㄴ requestEntity 객체를 이용하여 RequestEntity 클래스의 command 메서드에 dataName + "/" + method.getName() 을 넘겨줌
DaoInvocationHandler.java
ㄴ 넘어오는 아규먼트의 크기가 0보다 클 경우
ㄴ args 배열은 프로그램 실행 시 명령줄 인수로 전달된 값들을 담고 있음(args.length는 전달된 인수의 개수를 나타냄)
ㄴ args.length가 0보다 큰 경우, requestEntity 객체의 data 메서드를 호출하여 첫 번째 인수를 전달함
=> 프로그램 실행 시 첫 번째 인수가 requestEntity 객체의 data에 설정됨
=> 프로그램 실행 시 명령줄에서 인수를 전달하면 해당 인수를 requestEntity의 data로 설정하게 되어 데이터를 전달
DaoInvocationHandler.java
ㄴ new RequestEntity().command(dataName + "/list").toJson() => reuqestEntity.toJson() 으로 변경
=>
DaoInvocationHandler.java
DaoInvocationHandler.java
ㄴ 위쪽에 있던 리턴타입 정보를 꺼내는 코드를 아래쪽으로 이동시킴
DaoInvocationHandler.java
ㄴ 해당 코드를 BoardNetworkDao 클래스의 list 메서드에서 복사해오기
ResponseEntity.java
ㄴ FAILURE 는 삭제해주기 => ERROR 로 통일
BoardNetworkDao.java
ㄴ FAILURE => ERROR 로 변경
DaoInvocationHandler.java
ㄴ FAILURE => ERROR 으로 변경
DaoInvocationHandler.java
ㄴ returnType이 int 형식일 때, response에서 getObject 메서드를 호출하여 int 형식의 결과를 반환
ㄴ response.getObject(int.class)는 response에서 getObject 메서드를 호출하여 반환된 객체를 int 형식으로 형변환하여 반환함
=> int 형식의 결과를 프록시 객체의 호출자에게 반환함
ㄴ returnType이 void 형식일 때는 null 을 반환함
DaoInvocationHandler.java
=>
DaoInvocationHandler.java
ㄴ returnType이 List 형식일 때, response에서 getList 메서드를 호출하여 List 형식의 결과를 반환하도록 함ㄴ response.getList(returnType.getComponentType())는 response에서 getList 메서드를 호출하여 반환된 리스트를 반환함 => getList 메서드는 인자로 리스트 요소의 타입 정보를 전달받음
ㄴ returnType.getComponentType()을 사용하여 returnType의 요소 타입을 얻어서 해당 타입의 리스트를 반환함
=> returnType이 List<Board>인 경우 getComponentType()은 Board.class를 반환함
=> 만약 returnType이 Board[]인 경우에도 getComponentType()은 Board.class를 반환함
=> List 형식의 결과를 프록시 객체의 호출자에게 반환
DaoInvocationHandler.java
ㄴ DaoInvocationHandler 클래스의 list 메서드의 리턴 타입이 List인지 확인하도록 함
ㄴ DaoInvocationHandler 클래스의 list 메서드는 List<Board>를 리턴하도록 선언되어 있음
=> Method 클래스의 getReturnType 메서드를 사용하여 list 메서드의 리턴 타입을 얻을 수 있음
ㄴ 그 후, returnType == List.class를 비교하여 리턴 타입이 List인지 확인
=>
DaoInvocationHandler.java
ㄴ list 메서드의 반환 타입이 List<T>인 경우를 가정합니다. 여기서 T는 리스트의 항목 타입을 의미함
ㄴ ParameterizedType을 사용하여 제네릭 타입 정보에 액세스하고, 실제 타입 인수 중 인덱스 0에 해당하는 항목 타입을 가져옴
ㄴ 그런 다음 항목 타입의 간단한 이름을 출력합니다.
ㄴ method.getReturnType()은 Method 객체가 나타내는 메서드의 반환 타입을 반환하여 returnType 변수에 해당 값을 저장하고, returnType == List.class를 통해 반환 타입이 List 클래스인지 확인하고 결과를 출력
ㄴ method.getGenericReturnType()은 Method 객체가 나타내는 메서드의 반환 타입에 대한 제네릭 타입 정보를 반환함
ㄴ paramType 변수에 해당 값을 저장하고, paramType.getActualTypeArguments()를 통해 실제 타입 인자의 배열을 가져옴
ㄴ 배열의 첫 번째 원소를 가져와서 해당 타입의 getSimpleName() 메서드를 호출하여 실제 타입의 간단한 이름을 출력
=> 여기서 제네릭타입은 List<Board> 이므로 하나 밖에 없으므로 0번째(첫번째 원소)를 출력
=>
DaoInvocationHandler.java
ㄴ main 메서드에서 테스트한 해당 코드 복사해오기
ㄴ ParameterizedType 인터페이스의 getActualTypeArguments() 메서드는 실제 타입 인자들의 배열을 반환함
ㄴ 배열의 인덱스를 이용하여 원하는 위치의 실제 타입 인자를 가져올 수 있음
ㄴ 첫 번째 실제 타입 인자를 가져오기 위해 paramType.getActualTypeArguments()[0]를 사용
ㄴ 해당 실제 타입 인자를 itemType 변수에 할당하고, response.getList() 메서드를 호출하여 실제 타입에 해당하는 리스트를 가져옴
DaoInvocationHandler.java
ㄴ returnType이 int, void, List 타입 중 어느 것도 아닌 경우 response.getObject() 메서드에 returnType을 전달하여 해당 타입의 객체를 반환하도록 함
DaoInvocationHandler.java
ㄴ 테스트 시 사용한 해당 코드 삭제
ㄴ XxxNetworkdao 클래스들은 이제 필요 없으므로 삭제
ClientApp.java
ㄴ Proxy.newProxyInstance() 메서드를 사용하여 DAO 인터페이스에 대한 프록시 객체를 생성
ㄴ 각각의 DAO 인터페이스에 대해 DaoInvocationHandler를 사용하여 프록시를 생성하고, 프록시 객체를 해당 DAO 변수에 할당
=> 프록시 객체는 실제 DAO 인터페이스의 메서드 호출을 가로채서 필요한 작업을 수행하고, 실제 DAO 객체에게 전달하여 처리함
=> 클라이언트 애플리케이션은 DAO 객체를 직접 다루지 않고, 프록시 객체를 통해 DAO 기능을 사용
ServerApp.java
ㄴ response.status(ResponseEntity.FAILURE).result("해당 번호의 게시물이 없습니다!");
=> response.status(ResponseEntity.SUCCESS)
ServerApp.java
ㄴ response.status(ResponseEntity.FAILURE).result("해당 번호의 직원이 없습니다!");
=> response.status(ResponseEntity.SUCCESS)
ServerApp.java
ㄴ response.status(ResponseEntity.FAILURE).result("해당 번호의 공지가 없습니다!");
=> response.status(ResponseEntity.SUCCESS)
ServerApp.java
ㄴ response.status(ResponseEntity.FAILURE).result("해당 번호의 물품이 없습니다!");
=> response.status(ResponseEntity.SUCCESS)
테스트 실행
=>
DaoInvocationHandler.java
ㄴ args.length > 0 => args != null 로 코드 변경
=>
ㄴ 목록 잘 출력됨을 확인
리팩토링
ㄴ report-client 의 bitcamp.dao 패키지에 DaoBuilder 클래스 생성
DaoBuilder.java
ㄴ DaoBuilder 클래스에서 build() 메서드 생성
DaoBuilder.java
ㄴ 인스턴스 변수 생성 및 생성자 추가
=>
ㄴ DaoBuilder 클래스가 dataName, DataInputStream, DataOutputStream 객체를 생성자에서 받아 프록시 객체 생성에 활용
DaoBuilder.java
ㄴ ClientApp 클래스에서 해당 코드 복사해오기
DaoBuilder.java
ㄴ 파라미터 값에 dataName 추가
ㄴ T 타입으로 형변환해주고 "member" 는 dataName 으로 변경해주기
ㄴ 경고 무시하도록 어노테이션 추가
ㄴ MemerDao.class => type 으로 변경해주기
ClientApp.java
ㄴ build 메서드의 인자로 MemberDao.class를 전달하여 해당 타입의 프록시 인스턴스를 생성하도록 함
ㄴ DaoBuilder의 생성자에서 전달된 dataName, in, out 정보는 DaoInvocationHandler로 전달되어 프록시 객체의 동작에 활용
ClientApp.java
ㄴ DaoBuilder 객체 따로 생성해주기
ClientApp.java
ㄴ DaoBuilder 이용하도록 변경해줌
DaoBuilder.java
ㄴ DaoInvocationHandler 클래스 전체를 복사해옴
DaoBuilder.java
ㄴ DaoBuilder 클래스 내부에서만 사용되는 클래스이기 때문에 static 붙여주기
ㄴ 이제 DaoInvocationHandler 클래스는 DaoBuilder 클래스 안에 종속되어있으므로 제거
DaoBuilder.java
ㄴ 위에 DataInputStream, DataOutputStream 선언되어있으므로 밑에서 선언할 필요 없음
DaoBuilder.java
ㄴ in, out 은 제거
DaoBuilder.java
ㄴ DaoInvocationHandler 클래스를 builde 클래스의 로컬클래스로 만들기
DaoBuilder.java
ㄴ 로컬 클래스로 변경되었으므로 해당 코드도 필요 없으므로 제거
DaoBuilder.java
ㄴ dataName 도 넘겨줄 필요 없으므로 제거
익명 클래스로 만든 후 람다식으로 변경하기
DaoBuilder.java
=>
...
=>
=>
ㄴ obj 변수를 생성할 필요 없도록 현재 밑에 있는 obj 자리에 넣어주기
=>
ㄴ 필요 없으므로 해당 코드 제거
=>
ㄴ 메서드 명과 파라미터 타입 제거 후 -> 붙여주기
=> Exception 발생하여 아래 코드 수정
ResponseEntity.java
RequestEntity.java
ㄴ obj 가 null 일 경우를 체크 해주도록 코드 변경