7 Kasım 2018 Çarşamba

Proxy Sınıfı - Aslında Interceptor Yaratır

Giriş
Şu satırı dahil ederiz
import java.lang.reflect.Proxy;
Not : Eğer Spring kullanıyorsak CGLIB tercih edilebilirdi. Ancak CGLIB de deprecate edildi

Açıklaması şöyle. InvocationHandler aslında Interceptor olarak düşünülebilir.
There two critical classes: Proxy and InvocationHandler

Proxy: It provides you a static factory method to create a proxy instance on which you make a call. The proxy could delegate your call to the real class

InvocationHandler: It defines an invoke method which will be triggered when you make a call on the proxy intance
Gerçek bir nesneyi sarmalayarak yeni metod ekleyebilmeyi sağlar. Önce elimizde bir arayüz olmalı. 

newProxyInstance metodu - ClassLoader +  interfaces+ InvocationHandler
InvocationHandler içinde Proxy nesnesinin bir metodu çağrılınca ne yapmak istiyorsak onu yaparız

Örnek - Bir Parametreli Proxy
Elimizde şöyle bir arayüz ve gerçekleştirimi olsun
public class User {
  private String name;
}
public interface UserService {
  void addUser(User user);
}
public class UserServiceImpl implements UserService {
  @Override
  public void addUser(User user) {
    ...
  }
}
InvocationHandler için şöyle yaparız
public class UserServiceJdkInterceptor implements InvocationHandler {
  private Object realObj;

  public UserServiceJdkInterceptor(Object realObject) {
    super();
    this.realObj = realObject;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result = null;
    if (args != null && args.length > 0 && args[0] instanceof User) {
      User user = (User)args[0];
      result = method.invoke(realObj, args);
    }
    return result;
  }
}
Kullanmak için şöyle yaparız
UserService target = new UserServiceImpl(); // The real object
InvocationHandler interceptor = new UserServiceJdkInterceptor(target);
UserService proxy = (UserService) Proxy.newProxyInstance(
  target.getClass().getClassLoader(), 
  target.getClass().getInterfaces(), 
  interceptor);
Örnek - Parametresiz ve Method İsmine Bakan Proxy
Elimizde şöyle bir arayüz ve gerçekleştirimi olsun
interface Task {
  void save();
  int load();
}

class DefaultTask implements Task {

  @Override
  public void save() {
    System.out.println("save");
  }

  @Override
  public int load() {
    System.out.println("load");
    return new Random().nextInt();
  }
}
InvocationHandler nesnesini yaratalım
class TaskInvocationHandler implements InvocationHandler {

  private final Object target;

  public TaskInvocationHandler(Object target) {
    this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("save".equals(method.getName())) {
     System.out.println("Method name is save");
    } else if ("load".equals(method.getName())) {
      System.out.println("Method name is load");
    }
    return method.invoke(target, args);
  }
}
Şimdi bir Proxy yaratalım.
Task task = (Task) Proxy.newProxyInstance(
  Task.class.getClassLoader(), 
  new Class[]{Task.class}, 
  new TaskInvocationHandler(new DefaultTask()));
Proxy nesnesini çağıralım
task.save();
task.load();
Çıktı olarak şunu alırız
Method name is save
save
Method name is load
load
Örnek - InvocationHandler Olarak Lambda
Kendi arayüzümüzü yazdığımızı varsayalım.
public class Streams {

  public interface EnhancedStream<T> extends Stream<T> {

    List<T> toList();

    Iterable<T> toIterable();
  }

  public static <T> EnhancedStream<T> enhance(Stream<T> stream) {

    return (EnhancedStream<T>) Proxy.newProxyInstance(
            EnhancedStream.class.getClassLoader(),
            new Class<?>[] {EnhancedStream.class}, 
            (proxy, method, args) -> {

            ...
    });
}
Kullanmak için şöyle yaparız.
Stream<Integer> stream1 = ...
List<Integer> list = Streams.enhance (stream1).toList();

Stream<Integer> stream2 = ...;
Iterable<Integer> iterable = Streams.enhance(stream2).toIterable();
InvocationHandler olarak verilen Lambda içinde şöyle yaparız.
if ("toList".equals(method.getName())) {

  return stream.collect(Collectors.toList());

} else if ("toIterable".equals(method.getName())) {

  return (Iterable<T>) stream::iterator;

} else {
  // invoke method on the actual stream
  return method.invoke(stream, args);
}
Örnek
Elimizde şöyle bir kod olsun
interface StateEventListener {
  void onEventX();
  void onEventY(int x, int y);
  void onEventZ(String s);
}
Normalde şöyle yapmamız gerekir. Ancak çok fazla delegation kodu yazmak gerekiyor.
class StateMachine implements StateEventListener {
  State currentState;

  @Override
  public void onEventX() {
    currentState.onEventX();
  }

  @Override
  public void onEventY(int x, int y) {
    currentState.onEventY(x, y);
  }

  @Override
  public void onEventZ(String s) {
    currentState.onEventZ(s);
  }
}
Delegation yapan basit bir Proxy üreterek kodu azaltabiliriz. Şöyle yaparız.
State currentState = ...;

final StateEventListener stateEventPublisher = buildStateEventForwarder(); 

StateEventListener buildStateEventForwarder() {
  Class<?>[] interfaces = {StateEventListener.class};
  return (StateEventListener) Proxy.newProxyInstance(getClass().getClassLoader(),
 interfaces, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      try {
        return method.invoke(currentState, args);
      } catch (InvocationTargetException e) {
        throw e.getCause();
      }
    }
  });
}





Hiç yorum yok:

Yorum Gönder