반응형
프록시 패턴
- 인터페이스를 구현을 통해 진짜 구현체를 대신하여 부가기능을 제공하거나 리턴 값을 변경할 수 있도록 대리자(surrogate)나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 패턴
각 클래스는 자신이 해야 할 일(SRP: Single Responsibility Principle)만 해야하는데 부가적인 기증을 제공할 때 이런 패턴을 주로 사용함
하지만 부가적인 기능을 추가할 때마다 위임하는 코드가 중복해서 발생할 수 있음
코딩 예시: https://bamdule.tistory.com/154
매번 클래스를 만드는게 아니라 동적으로 생성하는 방식
-> java reflextion을 활용한 다이내믹 프록시 사용
- (컴파일 타임이 아닌) 런타임에 인터페이스를 구현하는 클래스/프록시 인스턴스를 만들어 사용하는 프로그래밍 기법
프록시 인스턴스 만들기
- Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
- object로 리턴되기 때문에 type casting 필요
//BookService는 반드시 인터페이스여야 함
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
new InvocationHandler() {
BookService bookService = new DefaultBookService();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("rent")) { //rent 라는 method에만 적용하고 싶다면
System.out.println("aaaa");
Object invoke = method.invoke(bookService, args);
System.out.println("bbbb");
return invoke;
}
//나머지 method는 그대로 리턴
return method.invoke(bookService, args);
}
});
근데 매번 이렇게 구현하는 게 더 복잡, 유연한 구조가 아님.
그리고 클래스 기반의 (자바가 제공하는) proxy를 만들 수 없음.
-> 그래서 Spring AOP(프록시 기반의 AOP)가 등장
클래스의 프록시가 필요하다면?
-> subclass를 만들어서 프록시를 만드는 라이브러리를 사용
CGlib lib 사용
- https://github.com/cglib/cglib/wiki
- 스프링, 하이버네이트가 사용하는 라이브러리
- 버전 호환성이 좋지 않아서 서로 다른 라이브러리 내부에 내장된 형태로 제공되기도 한다.
MethodInterceptor handler = new MethodInterceptor() {
BookService bookService = new BookService(); //class
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if(method.getName().equals("rent")){ //method 이름이 rent 일 때만 작업
log.info("aaa");
Objet invoke = method.invoke(bookService, args);
log.info("bb");
return invoke;
}
//나머지 method는 그대로 리턴
return method.invoke(bookService, args);
}
};
BookService bookService = (BookService) Enhancer.create(BookService.class, handler);
ByteBuddy lib 사용
- https://bytebuddy.net/#
- 스프링에서 버전 관리해줌
- 바이트 코드 조작뿐 아니라 런타임(다이내믹) 프록시를 만들 때도 사용할 수 있다.
Class<? extends BookService> proxyClass = new ByteBuddy().subclass(BookService.class)
.method(named("rent")) //method 이름이 rent면 적용
.intercept(InvocationHanderAdaptor.of(new InvocationHander(){
BookService bookService = new BookService();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
log.info("aaa");
Object invoke = method.invoke(bookService, args);
log.info("Bbb");
return invoke;
}
}))
.make().load(BookService.class.getClassLoader()).getLoaded();
BookService bookService = proxyClass.getConstructor(null).newInstance();
서브 클래스를 만드는 방법의 단점
- 상속(extends)을 사용하지 못하는 경우 프록시를 만들 수 없음
- private 생성자(하위 클래스에서 항상 부모의 생성자를 호출한다)
- final 클래스
- 인터페이스가 있을 때는 인터페이스의 프록시를 만들어 사용할 것.
다이내믹 프록시 기법이 사용되는 곳
- 스프링 데이터 JPA
- 스프링 AOP
- Mockito
- 하이버네이트 lazy initialization(@OneToMany 등 entity 들고 있을 때 프록시 객체로 처음에 리턴)
728x90
반응형
'architecture > sw architecture' 카테고리의 다른 글
람다 아키텍처 vs 카파 아키텍처 (0) | 2023.03.22 |
---|