다이나믹 프록시란?
런타임 시점에(컴파일 시점이 아닌) 특정 인터페이스들을 구현하는 클래스 혹은 인스턴스를 만드는 기술
https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
이전의 코드는 컴파일 타임에 인스턴스가 결정되어 버린다.
public class BookServiceTest {
BookService bookService = new BookServiceProxy(new RealSubjectBookService());
@Test
public void di() {
Book book = new Book();
book.setTitle("Toby Spring");
bookService.rent(book);
}
}
런타임 시에 결정 나도록 변경해보자 (With Proxy)
Proxy.newProxyInstance 사용
Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
Proxy.newProxyInstance
(구현할 대상의 클래스 로더 , 클래스 배열 선언 -> 인터페이스 목록 : 어떤 인터페이스 타입의 구현체 인가를 선언, InvocationHandler())
//BookService bookService = new BookServiceProxy(new RealSubjectBookService());
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
InvocationHandler 구현 (이제 BookServiceProxy는 필요 없음)
BookServiceProxy에서 했던 기능을 InvocationHandler에서 구현해보자
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
new InvocationHandler() {
BookService bookService = new RealSubjectBookService();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("11111");
Object invoke = method.invoke(bookService, args);
System.out.println("22222");
return invoke;
}
});
직접 리얼 타입 서브젝트 인스턴스를 생성하였음.
이렇게 된다면 bookService의 인터페이스에서 또 다른 메서드를 추가했을 때 똑같은 내용이 중복된다.
@Test
public void di() {
Book book = new Book();
book.setTitle("Toby Spring");
bookService.rent(book);
System.out.println();
bookService.returnBook(book);
}
invoke에서 분기 처리
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
new InvocationHandler() {
BookService bookService = new RealSubjectBookService();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName() == "rent") {
System.out.println("11111");
Object invoke = method.invoke(bookService, args);
System.out.println("22222");
return invoke;
}
return method.invoke(bookService, args);
}
});
결과
이와 같이 구현하면
이전의 코드와 같이 프록시 클래스를 매번 생성하는 것에 대한 부담은 없지만
InvocationHandler의 유연함이 떨어진다는 단점이 따른다.
이러한 단점을 보완하여 스프링 AOP에서는 해당 구조를 스프링이 정의한 인터페이스로 정의하였다.
그래서 스프링 AOP를 Proxy 기반 AOP라고 불리는 것이다.
또한 자바의 Proxy의 가장 큰 문제점은 클래스 타입으로 인터페이스를 선언하지 않으면 안 된다는 커다란 단점 또한 존재한다.
(클래스 타입으로 적용 불가!)
인터페이스인 BookService에서 클래스인 RealSubjectBookService로 타입 변환을 했을 때
/*인터페이스인 BookService에서 클래스인 RealSubjectBookService로 타입 변환을 했을때*/
RealSubjectBookService bookService = (RealSubjectBookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{RealSubjectBookService.class},
new InvocationHandler() {
RealSubjectBookService bookService = new RealSubjectBookService();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName() == "rent") {
System.out.println("11111");
Object invoke = method.invoke(bookService, args);
System.out.println("22222");
return invoke;
}
return method.invoke(bookService, args);
}
});
현재 인터페이스가 없고 클래스 밖에 없다면 다이내믹 프록시를 구현하지 못하는 것일까?
YES!
하지만 동적으로 프록시를 만드는 또 다른 방법이 있다.
Code Link
https://github.com/mike6321/PURE_JAVA/tree/master/TheJava/proxy-pattern
'Java > Java' 카테고리의 다른 글
(JAVA) 애노테이션 프로세서 (0) | 2019.12.28 |
---|---|
(JAVA) 클래스의 프록시 생성 (0) | 2019.12.24 |
(JAVA) 프록시 패턴 (0) | 2019.12.24 |
(JAVA) 리플렉션 API(4) - 나만의 DI 프레임워크 만들어보기 (0) | 2019.12.24 |
(JAVA) 리플렉션 API(3) - 클래스 정보 수정 또는 실행 (0) | 2019.12.23 |