1. 程式人生 > >Java學習之jdk與cglib動態代理

Java學習之jdk與cglib動態代理

宣告:參考部分部落格做記錄

1.jdk動態代理實現

介面:

public interface UserService {
    String getName();
}

介面實現:

public class UserServiceImpl implements UserService {
    public String getName() {
        System.out.println("-----getName-----");
        return "john";
    }
}

代理類:

public class UserProxy implements
InvocationHandler {
// jdk動態代理需要實現InvocationHandler介面 // 傳入目標類 private Object target; public UserProxy() { } public UserProxy(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if
("getName".equals(method.getName())) { System.out.println("-----before------" + method.getName() + "------------"); // 反射執行方法,引數1目標類,引數2是方法引數 Object result = method.invoke(target, args); System.out.println("-----after------" + method.getName() + "------------"
); return result; } else { Object result = method.invoke(target, args); return result; } } }

執行類:

public class AppMain {

    public static void main(String[] args) {
        // 例項化
        UserService userService = new UserServiceImpl();
        // 建立代理
        InvocationHandler invocationHandler = new UserProxy(userService);
        // jdk動態代理通過newProxyInstance實現,引數1是目標類ClassLoader,引數2是目標類介面,引數3是代理類
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(), userService.getClass()
                        .getInterfaces(), invocationHandler);
        System.out.println(userServiceProxy.getName());
    }

}

執行結果:

-----before------getName------------
-----getName-----
-----after------getName------------
john

2.cglib動態代理實現

jdk與cglib動態代理主要是代理類的不同

代理類:

public class UserCglibProxy implements MethodInterceptor {// cglib動態代理要實現MethodInterceptor介面

    // 引數1是目標類,引數2是方法,引數3是引數,引數4是為了得到介面的子類例項 
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        // getSuperName呼叫父類建立的合成方法的名稱(非攔截)
        System.out.println("-----before------" + proxy.getSuperName()
                + "------------");
        System.out.println(method.getName());
        // 執行父類方法
        Object object = proxy.invokeSuper(obj, args);
        System.out.println("-----after------" + proxy.getSuperName()
                + "------------");
        return object;
    }
}

執行類:

public class AppMain {

    public static void main(String[] args) {
        UserCglibProxy proxy = new UserCglibProxy();

        // 主要的增強類
        Enhancer enhancer = new Enhancer();
        // 設定父類
        enhancer.setSuperclass(UserServiceImpl.class);
        // 設定回撥,次回撥為代理類
        enhancer.setCallback(proxy);
        // 建立代理
        UserService userService = (UserService) enhancer.create();
        userService.getName();
    }

}

執行結果:

-----before------CGLIB$getName$0------------
getName
-----getName-----
-----after------CGLIB$getName$0------------

3.原理介紹

代理模式:
代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,而是通過呼叫委託類的物件的相關方法,來提供特定的服務。

區別:
1. jdk動態代理要依靠介面實現,而cglib則不需要
2. cglib對指定目標類生產一個子類,並覆蓋其中方法實現增強

動態代理:

java.lang.reflect.Proxy:這是 Java 動態代理機制的主類,它提供了一組靜態方法來為一組介面動態地生成代理類及其物件
Proxy 的靜態方法:

// 方法 1: 該方法用於獲取指定代理物件所關聯的呼叫處理器
static InvocationHandler getInvocationHandler(Object proxy) 

// 方法 2:該方法用於獲取關聯於指定類裝載器和一組介面的動態代理類的類物件
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 

// 方法 3:該方法用於判斷指定類物件是否是一個動態代理類
static boolean isProxyClass(Class cl) 

// 方法 4:該方法用於為指定類裝載器、一組介面及呼叫處理器生成動態代理類例項
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
    InvocationHandler h)

java.lang.reflect.InvocationHandler:這是呼叫處理器介面,它自定義了一個 invoke 方法,用於集中處理在動態代理類物件上的方法呼叫,通常在該方法中實現對委託類的代理訪問。
InvocationHandler 的核心方法

// 該方法負責集中處理動態代理類上的所有方法呼叫。第一個引數既是代理類例項,第二個引數是被呼叫的方法物件
// 第三個方法是呼叫引數。呼叫處理器根據這三個引數進行預處理或分派到委託類例項上發射執行
Object invoke(Object proxy, Method method, Object[] args)

建立過程:
1. 通過實現 InvocationHandler 介面建立自己的呼叫處理器;
2. 通過為 Proxy 類指定 ClassLoader 物件和一組 interface 來建立動態代理類;
3. 通過反射機制獲得動態代理類的建構函式,其唯一引數型別是呼叫處理器介面型別;
4. 通過建構函式建立動態代理類例項,構造時呼叫處理器物件作為引數被傳入。

動態生成的代理類本身的一些特點:
1)包:如果所代理的介面都是 public的,那麼它將被定義在頂層包(即包路徑為空),如果所代理的介面中有非 public 的介面(因為介面不能被定義為 protect 或
private,所以除 public 之外就是預設的 package 訪問級別),那麼它將被定義在該介面所在包(假設代理了 com.ibm.developerworks 包中的某非 public 介面 A,那麼新生成的代理類所在的包就是com.ibm.developerworks),這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義並訪問;
2)類修飾符:該代理類具有final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;
3)類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並不是每次呼叫 Proxy 的靜態方法建立動態代理類都會使得 N 值增加,原因是如果對同一組介面(包括介面排列的順序相同)試圖重複建立動態代理類,它會很聰明地返回先前已經建立好的代理類的類物件,而不會再嘗試去建立一個全新的代理類,這樣可以節省不必要的程式碼重複生成,提高了代理類的建立效率。

//此函式會被AbstractClassGenerator.create間接呼叫,並會在create函式中將結果快取.  
Enhancer.generateClass(ClassVisitor v)   

//AbstractClassGenerator.create(Object key)  
protected Object create(Object key) {  
    try {  
        Class gen = null;  

        synchronized (source) {  
            ClassLoader loader = getClassLoader();  
            Map cache2 = null;  
            cache2 = (Map)source.cache.get(loader);  
            if (cache2 == null) {  
                cache2 = new HashMap();  
                cache2.put(NAME_KEY, new HashSet());  
                source.cache.put(loader, cache2);  
            } else if (useCache) {  
                Reference ref = (Reference)cache2.get(key);  
                gen = (Class) (( ref == null ) ? null : ref.get());   
            }  
            if (gen == null) {  
                Object save = CURRENT.get();  
                CURRENT.set(this);  
                try {  
                    this.key = key;  

                    if (attemptLoad) {  
                        try {  
                            gen = loader.loadClass(getClassName());  
                        } catch (ClassNotFoundException e) {  
                            // ignore  
                        }  
                    }  
                    if (gen == null) {  
                        //生成的Class的二進位制儲存  
                        byte[] b = strategy.generate(this);  
                        String className = ClassNameReader.getClassName(new ClassReader(b));  
                        //將類名放入Cache中,Cache實際上是一個Hashset的例項  
                        getClassNameCache(loader).add(className);  
                        //將生成的類裝載路JVM  
                        gen = ReflectUtils.defineClass(className, b, loader);  
                    }  

                    if (useCache) {  
                        //將裝載的類放入cache  
                        cache2.put(key, new WeakReference(gen));  
                    }  
                    return firstInstance(gen);  
                } finally {  
                    CURRENT.set(save);  
                }  
            }  
        }  
        return firstInstance(gen);  
    } catch (RuntimeException e) {  
        throw e;  
    } catch (Error e) {  
        throw e;  
    } catch (Exception e) {  
        throw new CodeGenerationException(e);  
    }  
}  

proxy如何生產位元組碼,請參原始碼:

private byte[] generateClassFile() {   
  /*  
   * Record that proxy methods are needed for the hashCode, equals,  
   * and toString methods of java.lang.Object.  This is done before  
   * the methods from the proxy interfaces so that the methods from  
   * java.lang.Object take precedence over duplicate methods in the  
   * proxy interfaces.  
   */  
  addProxyMethod(hashCodeMethod, Object.class);   
  addProxyMethod(equalsMethod, Object.class);   
  addProxyMethod(toStringMethod, Object.class);   
  /*  
   * Now record all of the methods from the proxy interfaces, giving  
   * earlier interfaces precedence over later ones with duplicate  
   * methods.  
   */  
  for (int i = 0; i < interfaces.length; i++) {   
      Method[] methods = interfaces[i].getMethods();   
      for (int j = 0; j < methods.length; j++) {   
    addProxyMethod(methods[j], interfaces[i]);   
      }   
  }   
  /*  
   * For each set of proxy methods with the same signature,  
   * verify that the methods' return types are compatible.  
   */  
  for (List<ProxyMethod> sigmethods : proxyMethods.values()) {   
      checkReturnTypes(sigmethods);   
  }   
  /* ============================================================  
   * Step 2: Assemble FieldInfo and MethodInfo structs for all of  
   * fields and methods in the class we are generating.  
   */  
  try {   
      methods.add(generateConstructor());   
      for (List<ProxyMethod> sigmethods : proxyMethods.values()) {   
    for (ProxyMethod pm : sigmethods) {   
        // add static field for method's Method object   
        fields.add(new FieldInfo(pm.methodFieldName,   
      "Ljava/lang/reflect/Method;",   
       ACC_PRIVATE | ACC_STATIC));   
        // generate code for proxy method and add it   
        methods.add(pm.generateMethod());   
    }   
      }   
      methods.add(generateStaticInitializer());   
  } catch (IOException e) {   
      throw new InternalError("unexpected I/O Exception");   
  }   
  /* ============================================================  
   * Step 3: Write the final class file.  
   */  
  /*  
   * Make sure that constant pool indexes are reserved for the  
   * following items before starting to write the final class file.  
   */  
  cp.getClass(dotToSlash(className));   
  cp.getClass(superclassName);   
  for (int i = 0; i < interfaces.length; i++) {   
      cp.getClass(dotToSlash(interfaces[i].getName()));   
  }   
  /*  
   * Disallow new constant pool additions beyond this point, since  
   * we are about to write the final constant pool table.  
   */  
  cp.setReadOnly();   
  ByteArrayOutputStream bout = new ByteArrayOutputStream();   
  DataOutputStream dout = new DataOutputStream(bout);   
  try {   
      /*  
       * Write all the items of the "ClassFile" structure.  
       * See JVMS section 4.1.  
       */  
          // u4 magic;   
      dout.writeInt(0xCAFEBABE);   
          // u2 minor_version;   
      dout.writeShort(CLASSFILE_MINOR_VERSION);   
          // u2 major_version;   
      dout.writeShort(CLASSFILE_MAJOR_VERSION);   
      cp.write(dout);   // (write constant pool)   
          // u2 access_flags;   
      dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);   
          // u2 this_class;   
      dout.writeShort(cp.getClass(dotToSlash(className)));   
          // u2 super_class;   
      dout.writeShort(cp.getClass(superclassName));   
          // u2 interfaces_count;   
      dout.writeShort(interfaces.length);   
          // u2 interfaces[interfaces_count];   
      for (int i = 0; i < interfaces.length; i++) {   
    dout.writeShort(cp.getClass(   
        dotToSlash(interfaces[i].getName())));   
      }   
          // u2 fields_count;   
      dout.writeShort(fields.size());   
          // field_info fields[fields_count];   
      for (FieldInfo f : fields) {   
    f.write(dout);   
      }   
          // u2 methods_count;   
      dout.writeShort(methods.size());   
          // method_info methods[methods_count];   
      for (MethodInfo m : methods) {   
    m.write(dout);   
      }   
             // u2 attributes_count;   
      dout.writeShort(0); // (no ClassFile attributes for proxy classes)   
  } catch (IOException e) {   
      throw new InternalError("unexpected I/O Exception");   
  }   
  return bout.toByteArray();