pungjoo

작성중 - Proxy ( java.lang.reflect.Proxy ) 본문

JAVA

작성중 - Proxy ( java.lang.reflect.Proxy )

pungjoo.kim 2009.08.21 17:10
0. 들어 가면서.

흔히들 유연성을 갖기 위해서 요즘은 interface를 작성하고 해당 interface를 implement한 본연의 class를 작성을 많이 합니다.
어쩌구 저저구 - 작성중...


1. 준비 운동

다음과 같은 Foo interface가 있을 경우 개발을 할 당시 초기화를 어떻게 할까?
package info.yeonwoo.edu.proxy;

public interface Foo {

public void setName( String name );
public String getName();

public void setAddress( String address);
public String getAddress();

}

일반적으로 다음과 같이 Foo interface를 상속해 class를 생성합니다.
package info.yeonwoo.edu.proxy;

public class FooImpl implements Foo {

private String name;
private String address;

@Override
public String getAddress() {
return address;
}

@Override
public String getName() {
return name;
}

@Override
public void setAddress(String address) {
this.address = address;
}

@Override
public void setName(String name) {
this.name = name;
 }

}

실질적으로 Foo / FooImpl을 사용해 보겠습니다.
package info.yeonwoo.edu.proxy;

public class FooTest {

private static void print(Foo foo) {

foo.setName("pungjoo");
foo.setAddress("pegasus");
 
System.out.println(foo.getName() + " / " + foo.getAddress());

}

public static void main(String[] args) {
 
Foo foo = new FooImpl();

print(foo);
}
 
}

다른 방법은 없을까요? 객체를 생성하는 방법에 있어서요...
여러 가지 방법이 있겠지만 우선 'anonymous class'라는 형태로 다시 Foo을 구현해 보겠습니다.
package info.yeonwoo.edu.proxy;

public class FooTest {

private static void print(Foo foo) {

foo.setName("pungjoo");
foo.setAddress("pegasus");
 
System.out.println(foo.getName() + " / " + foo.getAddress());

}

public static void main(String[] args) {

Foo foo = new Foo() {

private String name;
private String address;

@Override
public String getAddress() {
return address;
}

@Override
public String getName() {
return name;
}

@Override
public void setAddress(String address) {
this.address = address;
}

@Override
public void setName(String name) {
this.name = name;
}

};

print(foo);
}

}

흔히들 inteface는  new 할 수 없다고 알고 있으나 위와 같이 new 가 가능합니다. 단일 블럭 안에서 사용하는 class라면 위와 같은 형태도 그리 나쁘지 않은 구조입니다. 그러나 빈번하게 객체 생성하거나 다른 블럭에서 생성하는  것이 라면 위와 같은 형태로는 곤란하겠지요.

어쩌구 저쩌구


2. Proxy

위 익명 클래스와는 다른 형태지만 느낌 상으로 비슷한 일을 하고 싶을 경우가 있습니다. method 기능인 서비스를 동적으로 만들고 싶거나 애초에 interface를 구현한 class의 method를 호출 하기 전/후에 다른 일을 하고 싶고 또는 해당 method의 내용을 말그대로 동적으로 변화를 주고 싶거나...

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

proxy를 사용하기 위해서는 항상 interface가 있어야 합니다. implement class는 상황에 따라서 필요합니다.

Proxy를 사용해 위 FooImpl의 기능을 비슷하게 만들어 보겠습니다.
package info.yeonwoo.edu.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class FooProxyHandler implements InvocationHandler {

private Map<String, Object> datas = new HashMap<String, Object>();

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

String methodName = method.getName();

if (methodName.startsWith("set")) {
datas.put(methodName.substring(3), args[0]);
} else if (methodName.startsWith("get") && !methodName.equals("getClass")) {
return datas.get(methodName.substring(3));
}

return null;
}

}

위와 같이 FooProxyHandler를 만들고 기 만들었던 FooTest를 다음과 같이 수정합니다.
package info.yeonwoo.edu.proxy;

import java.lang.reflect.Proxy;

public class FooTest {

private static void print(Foo foo) {

foo.setName("pungjoo");
foo.setAddress("pegasus");
 
System.out.println(foo.getName() + " / " + foo.getAddress());
}

public static void main(String[] args) {
 
Foo foo = (Foo) Proxy.newProxyInstance(
Foo.class.getClassLoader(), new Class[] { Foo.class },
new FooProxyHandler() );
 
print(foo);

}

}



3. 심화 학습....

FooProxyHandler는 setter/getter에 평이한 형태와 단지 value를 저장하는 용도 정도의 내용이므로 단순합니다. 그러나 실제 사용되는 class는 단순하지 않죠.

FooImpl을 그대로 사용하고 즉, 수정하지 않고 각 method를 수행하는데 있어서 얼마의 시간이 소요되는가 로그를 찍고 싶을 경우를 생각해 보겠습니다.  그래도 시간이 걸리는 차이를 인지 하기 위해서 FooImpl의 viod setName() mehotd를 다음과 같이 변경하겠습니다. 혼돈이 없게 FooImpl을 extent한 ExtFooImpl로 작성합니다.

package info.yeonwoo.edu.proxy;

public class ExtFooImpl extends FooImpl implements Foo {

@Override
public void setName(String name) {

try {

java.util.Random random = new java.util.Random();

int sleep = random.nextInt(1000);

Thread.sleep(sleep);

} catch (InterruptedException e) {
}
  
super.setName(name);
  
}
}

 

FooProxyHandler를 다음과 같이 변경합니다.
package info.yeonwoo.edu.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class FooProxyHandler implements InvocationHandler {

private Foo foo = new ExtFooImpl();
 
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
long time = System.currentTimeMillis();
 
try {

System.out.println(">> " + method.toGenericString() + " start : " + new Date());

return method.invoke(foo, args);

} finally {

System.out.println(">> " + method.toGenericString() + " end   : " +
(System.currentTimeMillis() - time) + "ms");

}
}
}



ProxyHandler는 Foo / FooImpl / ExtFooImpl / class에 종속적이므로 이 부분을 종속적이 않게 해 봅시다.


3. 끝을 향해 달려 보자..

새로이 proxyHandler를 다음과 같이 만듭니다. FooProxyHandler와 크게 다르지는 않습니다.
package info.yeonwoo.edu.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;

public class EduProxyHandler implements InvocationHandler {

private Object obj = null;
 
private EduProxyHandler(Object obj) {
this.obj = obj;
}
 
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

long time = System.currentTimeMillis();
 
 try {
System.out.println(">> " + method.toGenericString() + " start : " + new Date());
 
return method.invoke(obj, args);

} finally {

System.out.println(">> " + method.toGenericString() + " end   : " +
(System.currentTimeMillis() - time) + "ms");

}
}

public static Object newInstance(Object obj) throws IllegalArgumentException{

return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new EduProxyHandler(obj));

}
}

크게 다르지 않겠지만 Test class도 다시 만들어 봅시다.
package info.yeonwoo.edu.proxy;

public class EduProxyTest {

private static void print(Foo foo) {
 
foo.setName("pungjoo");
foo.setAddress("pegasus");
 
 System.out.println(foo.getName() + " / " + foo.getAddress());

}

public static void main(String[] args) {
 
Foo foo = (Foo) EduProxyHandler.newInstance(new ExtFooImpl());
 
print(foo);

}

}


4. Stack trace 살펴 보기

java.lang.Exception: Stack trace
 at java.lang.Thread.dumpStack(Unknown Source)
 at info.yeonwoo.edu.proxy.FooImpl.dump(FooImpl.java:42)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at info.yeonwoo.edu.proxy.EduProxyHandler.invoke(EduProxyHandler.java:23)
 at $Proxy0.dump(Unknown Source)

 at info.yeonwoo.edu.proxy.EduProxyTest.main(EduProxyTest.java:23)



5. 흉내내기...

다음과 같이 EduProxyManager를 하나 생성합니다. 단, classloader에 따른 싱글인스턴가 되므로 Thread-safe하지 않은 모습이 되므로 주의 해야 합니다.

package info.yeonwoo.edu.proxy;

import java.util.HashMap;
import java.util.Map;

public class EduProxyManager {

final static private EduProxyManager INSTANCE = new EduProxyManager();

private Map<String, Object> serviceStore = new HashMap<String, Object>();
 
private EduProxyManager(){
}

private void register0(String serviceName, String serviceClazz) throws IllegalArgumentException,
InstantiationException, IllegalAccessException, ClassNotFoundException {
 
serviceStore.put(serviceName,
EduProxyHandler.newInstance(Class.forName(serviceClazz).newInstance()));
 
}

public static void register(String serviceName, String serviceClazz) throws IllegalArgumentException,
InstantiationException, IllegalAccessException, ClassNotFoundException {

INSTANCE.register0(serviceName, serviceClazz);

}

private Object getService0(String serviceName) {

return serviceStore.get(serviceName);

}

 
public static Object getService(String serviceName) {

return INSTANCE.getService0(serviceName);

}

}

EduProxTest를 다음과 같이 수정합니다.

public static void main(String[] args) throws IllegalArgumentException, InstantiationException,
IllegalAccessException, ClassNotFoundException {  

EduProxyManager.register("Foo", "info.yeonwoo.edu.proxy.ExtFooImpl");  

Foo foo = (Foo) EduProxyManager.getService("Foo");  

print(foo);

}

Classloader에 따른 싱글인스턴이므로 다음과 같은 결과에 주의를 해야 합니다.

public static void main(String[] args) throws IllegalArgumentException, InstantiationException,
IllegalAccessException, ClassNotFoundException {

 
EduProxyManager.register("Foo", "info.yeonwoo.edu.proxy.ExtFooImpl");  

Foo foo = (Foo) EduProxyManager.getService("Foo");  
 
print(foo);
  
Foo foo2 = (Foo) EduProxyManager.getService("Foo");

System.out.println( foo2.getName());
  
}


3 Comments
댓글쓰기 폼