프록시는 기존 코드의 영향을 주지 않으면서 기능을 확장하거나 접근 방법을 제어할 수 있는 방법
프록시(Proxy): 대리인, 무언가를 대신 처리해준다는 의미

이렇게 자신이 클라이언트가 사용하려는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 것을 대리자, 대리인과 같은 역할을 한다고 해서 프록시라고 부른다. 프록시를 통해 최종적으로 요청을 위임받아 처리하는 오브젝트를 타겟(Target) 또는 실체(Subject)라고 부른다.
예제를 통해 코드를 확인해보자
interface BookService {
void rent();
void save();
}
위와 같은 인터페이스를 구현한 클래스가 있다.
public class BookServiceTarget implements BookService {
@Override
public void rent() {
// 트랜잭션 시작
System.out.println("Renting a book...");
// 트랜잭션 커밋 or 롤백
}
@Override
public void add() {
// 트랜잭션 시작
System.out.println("Adding a book...");
// 트랜잭션 커밋 or 롤백
}
}
rent() ,add() 두 메서드에는 트랜잭션 기능이 필요하기 때문에 위와 같이 구현했지만, 비즈니스 로직과 트랜잭션 처리 로직이 섞여있어 보기 좋지 않다. 따라서 프록시 객체를 만들어서 부가 기능인 트랜잭션 처리 로직을 추가하고, 나머지 비즈니스 로직은 Target 객체에게 위임한다.
public class BookServiceProxy implements BookService {
private BookService bookService;
@Override
public void rent() {
// 트랜잭션 시작
bookService.rent()
// 트랜잭션 커밋 or 롤백
}
@Override
public void add() {
// 트랜잭션 시작
bookService.add()
// 트랜잭션 커밋 or 롤백
}
}
프록시 객체에게 트랜잭션 처리 로직을 추가하고 나머지 비즈니스 로직은 타겟 객체에게 위임했다. 타겟 객체는 비즈니스 로직에 집중할 수 있게 변경이 되었다. 하지만 위와 같이 구현한 프록시 객체에 두 가지 문제점이 존재한다.
이러한 문제점은 다이나믹 프록시를 통해 해결할 수 있다.
다이나믹 프록시는 java.lang.reflect 내에 있는 Proxy객체를 통해 생성할 수 있다.
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
InvocationHandler에서 프록시의 메서드가 호출될 때 메서드를 어떻게 처리할 지 결정한다.
BookService bookService = (BookService) Proxy.newProxyInstance(
BookService.class.getClassLoader(),
new Class[]{BookService.class},
new TransactionHandler());
class TransactionHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 트랜잭션 시작
Object invoke = method.invoke(new BookServiceImpl(), args);
// 트랜잭션 종료
return invoke;
}
}
위 코드는 자바에서 Proxy 클래스를 통해 직접 동적으로 프록시 객체를 생성하는 방법이다. 다만, 인터페이스 기반의 프록시 객체만 생성할 수 있다.

InvocationHandler 클래스의 invoke() 메서드를 구현을 통해 부가기능을 추가시킬 수 있다. 하나의 핸들러를 통해 수많은 메서드에 부가기능을 추가할 수 있다.다이나믹 프록시을 적용함으로써 인터페이스의 구현 메서드가 늘어나도 전혀 손댈 부분이 없어진다. 하지만 리플랙션 API를 사용한 만큼 타입에 대한 주의를 해야한다. 예를 들어 모든 리턴 타입이 String인 메서드에서 String 이 아닌 메서드가 추가되면 캐스팅 오류가 발생할 것이다.
https://www.inflearn.com/course/the-java-code-manipulation/dashboard
토비의 스프링 3.1 - 이일민