Java 동적 에이전트 및 CGLIB 상세 설명

정적 에이전트 모드
일부 함수에 대해 2차 처리를 해야 하거나 일부 함수가 외부에 알려지지 않을 때 에이전트 모드를 사용하여 제3자를 방문하고 원 함수에 간접적으로 접근하는 방식으로 상기 목적을 달성할 수 있다.

interface Hosee{
  String sayhi();
}

class Hoseeimpl implements Hosee{

  @Override
  public String sayhi()
  {
    return "Welcome oschina hosee's blog";
  }

}

class HoseeProxy implements Hosee{

  Hosee h;

  public HoseeProxy(Hosee h)
  {
    this.h = h;
  }

  @Override
  public String sayhi()
  {
    System.out.println("I'm proxy!");
    return h.sayhi();
  }

}


public class StaticProxy
{

  public static void main(String[] args)
  {
    Hoseeimpl h = new Hoseeimpl();
    HoseeProxy hp = new HoseeProxy(h);
    System.out.println(hp.sayhi());
  }

}

1.1 정적 에이전트의 폐단
여러 클래스를 위해 에이전트를 하려면 여러 개의 에이전트 클래스를 만들어야 하기 때문에 유지보수의 난이도가 높아진다.
생각해 보면 왜 정적 에이전트에 이런 문제가 있는지 생각해 보자. 에이전트는 컴파일러에 이미 결정되었기 때문이다. 만약에 에이전트 중 어느 것이 운행기에 발생한다면 이런 문제들은 해결하는 것이 비교적 간단하기 때문에 동적 에이전트의 존재는 매우 필요하다.
2. 동적 에이전트

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

interface HoseeDynamic
{
  String sayhi();
}

class HoseeDynamicimpl implements HoseeDynamic
{
  @Override
  public String sayhi()
  {
    return "Welcome oschina hosee's blog";
  }
}

class MyProxy implements InvocationHandler
{
  Object obj;
  public Object bind(Object obj)
  {
    this.obj = obj;
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable
  {
    System.out.println("I'm proxy!");
    Object res = method.invoke(obj, args);
    return res;
  }
}

public class DynamicProxy
{
  public static void main(String[] args)
  {
    MyProxy myproxy = new MyProxy();
    HoseeDynamicimpl dynamicimpl = new HoseeDynamicimpl();
    HoseeDynamic proxy = (HoseeDynamic)myproxy.bind(dynamicimpl);
    System.out.println(proxy.sayhi());
  }
}

클래스는 정적 에이전트보다 에이전트 클래스가 원 인터페이스를 실현하지 않고 InvocationHandler를 실현하는 것을 알 수 있습니다.통과

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
동적으로 프록시 클래스를 생성합니다. 이 클래스의 클래스 캐리어는 프록시 클래스와 같고 실현된 인터페이스는 프록시 클래스와 같습니다.
상술한 방법을 통해 생성된 에이전트 클래스는 정적 에이전트 중의 에이전트 클래스에 해당한다.
이렇게 하면 운행 기간에 에이전트 대상이 어떻게 되는지 결정하고 정적 에이전트의 폐단을 해결할 수 있다.
동적으로 생성된 프록시 클래스 호출 방법이 있을 때 invoke 방법을 터치하여 invoke 방법에서 피프록시 클래스의 방법을 강화할 수 있습니다.
동적 에이전트를 통해 그 장점을 뚜렷하게 볼 수 있다. 정적 에이전트를 사용할 때 서로 다른 인터페이스의 일부 클래스가 에이전트 모드를 사용하여 같은 기능을 실현하려면 여러 개의 에이전트 클래스를 실현해야 하지만 동적 에이전트에서는 하나의 에이전트 클래스만 있으면 된다.
에이전트 클래스를 작성하는 작업량을 절약하는 것 외에 동적 에이전트는 원시 클래스와 인터페이스가 아직 알 수 없을 때 에이전트 클래스의 에이전트 행위를 확정할 수 있다. 에이전트 클래스와 원시 클래스가 직접적인 관계를 벗어나면 서로 다른 응용 장면에 유연하게 다시 사용할 수 있다.
2.1 동적 에이전트의 폐단
대리류와 위탁류는 모두 같은 인터페이스를 실현해야 한다.즉, 어떤 인터페이스를 실현한 클래스만 자바 동적 프록시 메커니즘을 사용할 수 있다.그러나 사실상 사용 중 만나는 모든 종류가 인터페이스를 만들어 주는 것은 아니다.따라서 인터페이스를 실현하지 못한 종류에 대해서는 이 메커니즘을 사용할 수 없다.
CGLIB는 클래스에 대한 동적 에이전트를 구현합니다.
2.2 리셋 함수 원리
위에서 말한 바와 같이 동적으로 생성된 프록시 클래스 호출 방법이 invoke 방법을 촉발합니다.
분명히 invoke 방법은 호출된 것이 아닙니다. 호출 함수입니다. 그러면 호출 함수는 어떻게 호출됩니까?
상술한 동적 에이전트 코드 중 유일하게 명확하지 않은 곳은

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
이 방법의 원본 코드를 추적하면 프로그램이 검증, 최적화, 캐시, 동기화, 바이트 코드 생성, 디스플레이 클래스 불러오기 등 조작을 하는 것을 볼 수 있다. 앞의 절차는 우리가 주목하는 중점이 아니라 마지막에 호출되었다.

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces);
이 방법은 바이트 코드를 생성하는 동작을 완성하는데 이 방법은 실행할 때 프록시 클래스를 설명하는 바이트 코드byte[]수조를 만들 수 있습니다.
main 함수에 추가

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
이 코드를 추가한 후 프로그램을 다시 실행하면 디스크에 $Proxy () 라는 이름이 생성됩니다.class의 프록시 클래스 클래스 파일입니다. 역컴파일(역컴파일 도구는 JD-GUI를 사용합니다) 후 다음 코드를 볼 수 있습니다.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
 implements HoseeDynamic
{
 private static Method m1;
 private static Method m3;
 private static Method m0;
 private static Method m2;

 public $Proxy0(InvocationHandler paramInvocationHandler)
  throws 
 {
  super(paramInvocationHandler);
 }

 public final boolean equals(Object paramObject)
  throws 
 {
  try
  {
   return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final String sayhi()
  throws 
 {
  try
  {
   return (String)this.h.invoke(this, m3, null);
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final int hashCode()
  throws 
 {
  try
  {
   return ((Integer)this.h.invoke(this, m0, null)).intValue();
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final String toString()
  throws 
 {
  try
  {
   return (String)this.h.invoke(this, m2, null);
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }

 static
 {
  try
  {
   m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
   m3 = Class.forName("HoseeDynamic").getMethod("sayhi", new Class[0]);
   m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
   m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
   return;
  }
  catch (NoSuchMethodException localNoSuchMethodException)
  {
   throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
  }
  catch (ClassNotFoundException localClassNotFoundException)
  {
   throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }
 }
}

동적 프록시 클래스는 정의된 인터페이스를 표시하는 방법을 프록시할 뿐만 아니라 자바의 루트 클래스 Object에서 계승된 equals (), hashcode (), toString () 세 가지 방법을 프록시할 뿐만 아니라 이 세 가지 방법만 프록시합니다.
상기 코드에서 보듯이 어떤 방법을 사용하든지 InvocationHandler의 invoke 방법으로 호출됩니다. 단지 파라미터가 다를 뿐입니다.
2.3 동적 에이전트와 정적 에이전트의 차이
Proxy류의 코드가 고정되어 업무가 점점 거대하고 거대하지 않다.
AOP 프로그래밍을 실현할 수 있습니다. 이것은 정적 에이전트가 실현할 수 없는 것입니다.
결합을 풀고 웹 업무에 사용하면 데이터층과 업무층의 분리를 실현할 수 있다.
동적 에이전트의 장점은 무침입식 코드 확장을 실현하는 것이다.정적 에이전트 모델 자체에 큰 문제가 있다. 만약에 클래스 방법의 수량이 점점 많아지면 에이전트 클래스의 코드량은 매우 방대하다.그래서 동적 에이전트를 도입하여 이런 문제를 해결한다
3. CGLIB
cglib는 클래스를 대상으로 에이전트를 실현하는 것이다. 그의 원리는 지정된 목표 클래스에 대해 하위 클래스를 생성하고 그 중에서 덮어쓰는 방법으로 강화를 실현하는 것이다. 그러나 계승이기 때문에final 수식된 클래스에 대해 에이전트를 할 수 없다.

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class CGlibHosee
{
  public String sayhi()
  {
    return "Welcome oschina hosee's blog";
  }
}

class CGlibHoseeProxy
{
  Object obj;

  public Object bind(final Object target)
  {
    this.obj = target;
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(obj.getClass());
    enhancer.setCallback(new MethodInterceptor()
    {
      @Override
      public Object intercept(Object obj, Method method, Object[] args,
          MethodProxy proxy) throws Throwable
      {
        System.out.println("I'm proxy!");
        Object res = method.invoke(target, args);
        return res;
      }
    });
    return enhancer.create();
  }

}

public class CGlibProxy
{
  public static void main(String[] args)
  {
    CGlibHosee cGlibHosee = new CGlibHosee();
    CGlibHoseeProxy cGlibHoseeProxy = new CGlibHoseeProxy();
    CGlibHosee proxy = (CGlibHosee) cGlibHoseeProxy.bind(cGlibHosee);
    System.out.println(proxy.sayhi());
  }
}

cglib는 부류와 리셋 방법을 지정해야 합니다.물론 cglib도 자바 동적 에이전트와 마찬가지로 인터페이스를 향할 수 있다. 본질은 계승이기 때문이다.
읽어주셔서 감사합니다. 여러분에게 도움이 되었으면 좋겠습니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기