public class HelloImpl implements Hello {
public String sayHello(String name) {
return "hello " + name;
}
public String sayHi(String name) {
return "hi " + name;
}
public String sayBye(String name) {
return "bye " + name;
}
}
이런 추상 인터페이스와 구현체가 존재한다.
HelloImpl을 건드리지 않고 반환되는 값을 모두 대문자로 변경하고 싶다면 이렇게 HelloImpl을 멤버로 가져와서 사용하는 다음과 같은 클래스를 만들어야 한다.
@RequiredArgsConstructor
public class HelloUpperCase implements Hello {
private final Hello hello;
@Override
public String sayHello(String name) {
return this.hello.sayHello(name).toUpperCase();
}
@Override
public String sayHi(String name) {
return this.hello.sayHi(name).toUpperCase();
}
@Override
public String sayBye(String name) {
return this.hello.sayBye(name).toUpperCase();
}
}
이거를 InvocationHandler를 사용해 바꿔보자.
@RequiredArgsConstructor
public class HelloUpperCase implements InvocationHandler {
private final Hello target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getReturnType().equals(String.class)) {
String returnValue = (String)method.invoke(target, args);
return returnValue.toUpperCase();
}
else return method.invoke(target, args);
}
}
이렇게 호출받은 메서드를 실행하면서 전후로 무언가 추가적인 작업을 할 수 있다.
나는 여기서 리턴의 타입이 String이면 대문자로 넘겨줄 수 있도록 만들었다.
@Test
public void simpleProxy2(){
Hello proxyHello = (Hello) Proxy.newProxyInstance(
getClass().getClassLoader(),
//구현할 인터페이스
new Class[]{Hello.class},
//부가기능을 담은 InvocationHandler 구현체
new HelloUpperCase(new HelloImpl())
);
Assertions.assertEquals("HELLO SEUNGKYU", proxyHello.sayHello("seungkyu"));
Assertions.assertEquals("HI SEUNGKYU", proxyHello.sayHi("seungkyu"));
Assertions.assertEquals("BYE SEUNGKYU", proxyHello.sayBye("seungkyu"));
}
Proxy.newProxyInstance를 통해 프록시가 설정된 클래스를 생성한다.
파라미터를 하나씩 살펴보자.
첫번째 파라미터
그냥 클래스로더이다. 다이나믹 프록시가 적용되는 클래스 로더를 지정하는 것이라고 한다.
두번째 파라미터
다이나믹 프록시가 구현할 인터페이스이다. 여기서 배열을 사용하는 이유는 하나 이상의 인터페이스를 구현하기 때문이다.
세번째 파라미터
부가기능을 가지고 있는 InvocationHandler 구현 오브젝트이다.
여기서 invoke 메서드는 요청받은 클래스의 메서드만 사용 가능하다.
만약 이렇게 FakeHello가 존재하고, FakeHello에도 동일한 메서드들이 존재한다면
@RequiredArgsConstructor
public class HelloUpperCase implements InvocationHandler {
private final Hello target;
private final FakeHello fakeTarget;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getReturnType().equals(String.class)) {
String returnValue = (String)method.invoke(target, args);
return returnValue.toUpperCase();
}
else return method.invoke(target, args);
}
}
FakeHello를 통해서 invoke 하려고 해도, 에러가 발생한다는 것이다.
이렇게 해서 simpleProxy2를 테스트해보니 다음과 같이 성공하는 것을 볼 수 있었다.
그리고 또 궁금한 점이 생겼다.
만약 타입에 맞지 않는 값을 반환한다면?
UserService에 정수를 반환하는 메서드를 추가하고, invocationHandler를 아래와 같이 바꾸고
@RequiredArgsConstructor
public class HelloUpperCase implements InvocationHandler {
private final Hello target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String returnValue = (String)method.invoke(target, args);
return returnValue.toUpperCase();
}
}
다음의 테스트를 실행해보았다.
@Test
public void return1Test(){
Hello proxyHello = (Hello) Proxy.newProxyInstance(
getClass().getClassLoader(),
//구현할 인터페이스
new Class[]{Hello.class},
//부가기능을 담은 InvocationHandler 구현체
new HelloUpperCase(new HelloImpl())
);
proxyHello.return1();
}