본문 바로가기
네이버클라우드/JAVA 웹 프로그래밍

JAVA 33일차 (2023-07-07) 자바 프로그래밍_GoF의 프록시 패턴의 원리 이해 및 적용/분산 컴퓨팅의 개념과 주요 기술 이해_개인프로젝트 - 마트 관리 시스템

by prometedor 2023. 7. 8.
## 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 일 경우를 체크 해주도록 코드 변경