프록시
어떤 객체에 대한 대리자를 통해 접근을 제어하여 객체의 생성을 지연초기화 하거나, 캐싱, 객체의 행동 전후에 행동제어를 수행할 수 있는 패턴
코드
public class FruitRepository{
private static final Map<String, Fruit> fruitList = new HashMap<>(Map.of(
"orange",new Fruit("orange",10000),
"strawberry", new Fruit("strawberry", 20000),
"mango", new Fruit("mango",30000)
));
public Fruit getFruitByName(String name) {
System.out.println("Repository에서 " + name +" 조회");
return fruitList.getOrDefault(name,null);
}
}
@Controller
@RequestMapping("/ex")
@RequiredArgsConstructor
public class FruitController {
private final FruitRepository fruitRepository;
@GetMapping("/fruit")
public ResponseEntity<Fruit> getFruit(@RequestParam String name) {
return ResponseEntity.ok(fruitRepository.getFruitByName(name));
}
}아주 단순하게 구현하기 위해 map을 통한 데이터 접근을 구현하며 이를 db를 대체한다고 하자. 이런 객체를 controller에서 이용하여 데이터를 조회하는 프로그램이 있다고하자. 그런데 조회할때 같은 과일이름의 조회결과는 캐싱을 추가하려고 한다. 그러면 아래와 같이 코드를 작성할 수 있을 것이다.
그런데 만약 Repository가 우리가 구현한 객체가 아니라 수정이 불가능하다면 캐싱을 이렇게 직접 적용이 불가능 할 것이다. 이를 프록시를 통해 해결해보자.
자식클래스는 부모클래스로 업캐스팅이 될 수 있다는 점을 이용하여 위와 같이 프록시를 구현할 수 있다.
만일 Repository를 수정할 수 있어 Interface를 구현하도록 만들 수 있다면 아래와 같이 구현을 할 수도 있다.
적용할 수 있는 곳
지연 초기화 : 시스템 리소스를 많이 차지하는 서비스 객체가 존재할때 앱이 시작되는 시점에 객체를 생성하는 것이 아니라 실제로 사용하는 시점에 사용하도록하여 성능향상을 꾀하고자 할때
접근 제어 : 특정 클라이언트만 서비스 객체를 사용할 수 있도록 하려는 경우
로깅 요청 : 실제 서비스 객체에 요청을 전달하기 전에 제어할 수 있다는 점을 이용하여 요청을 로깅하려고 하는 경우
캐싱 : 서비스 로직을 통한 반환값이 일정하며 처리시간이 긴 경우에 결과를 캐시하고 캐시의 수명주기를 관리하고자 하는 경우
자원 해제 : File, datasource등 자원을 해제하지 않으면 많은 리소스를 잡아먹는 객체의 경우 자원해제를 사용자가 아닌 프록시객체에게 위임하고자 하는 경우
다른 패턴들과 비교
Adapter : 랩핑된 객체와 다른 인터페이스를 제공하지만, 프록시는 동일한 인터페이스를 제공한다.
Facade : 객체를 버퍼링하고 자체적으로 초기화한다는 점은 비슷하지만 프록시는 해당 서비스 객체와 동일한 인터페이스를 갖는다.
Decorator : 구조가 매우 비슷하며 특정 작업을 다른 객체에게 위임하는 점은 비슷하나 프록시는 객체 자체적으로 서비스 객체의 수명주기, 행동을 관리하지만 데코레이터는 행동의 제어가 클라이언트에게 있다.
데코레이터는
런타임에기능을 추가하는 것이 목적, Proxy는컴파일 타임에행동을 제어하는 것이 목적
Spring 프로젝트에 Proxy를 통해 서비스를 확장해보자!
위와 같은 controller-service-repository 계층의 앱이 존재할때 각 계층의 log를 깊이별로 tracing하는 기능을 추가하고자 할때 아래와 같이 추가할 수 있다.
하지만 이는 로깅을 적용하고 싶은 빈마다 LogTrace를 주입시켜주고 비즈니스로직을 try로 감싸 기능을 추가해주어야 한다. 굉장히 보일러플레이트도 늘어날뿐 아니라 단일책임원칙에도 위배되고 있다.
Proxy를 시작으로 AOP까지 확장해가며 서비스를 확장시켜보자.
1. Concreate Proxy
2. Interface Proxy
3. DynamicProxy
Proxy.newProxyInstance()를 통해 Proxy 객체를 런타임에 생성할 수 있는데, 이때 인자로 InvocateionHandler를 넘겨주어 ClassLoader의 객체가 load될때 해당 핸들러로 행동을 제어할 수 있다.
패턴 매칭
4. ProxyFactory
여기서부터는 AOP를 이용한 방법이다. spring에서 제공하는 aop api를 이용해보자.
5. postProcessor
6. AutoProxy
여기서부터는 pointcut을 표현식을 통해서 적용하는 패턴으로 implementation 'org.springframework.boot:spring-boot-starter-aop'를 추가해주어야 한다.
7. AOP (pointcut expression)
8. AOP (annotation)
Reference
Last updated