JDK動態代理和CGLIB代理

JDK動態代理爲什麼必須針對接口?其與CGLIB的對比

示例代碼:

public interface AopService {

       public void doService();

}

public class AopServiceImpl implements AopService {

       private String singal;

       public String getSingal() {

              return singal;

       }

       public AopServiceImpl(){}

       public AopServiceImpl(Stringsingal){

              this.singal = singal;

       }

       @Override

       public void doService() {

              System.out.println("doservice method is being invoked now!!!");

       }

 

}

public class ProxyFactory implements InvocationHandler {

      

       private Object targetObject;

       public ObjectcreateTargetObject(Object targetObject){

              this.targetObject =targetObject;

              returnProxy.newProxyInstance(this.targetObject.getClass()

                            .getClassLoader(),

                            this.targetObject.getClass().getInterfaces(),this);

       }

 

       @Override

       public Object invoke(Objectarg0, Method method, Object[] args)

                     throws Throwable{

        AopServiceImpl aopservice =(AopServiceImpl)this.targetObject;

        Object result = null;

        if(aopservice.getSingal()!=null){

             result= method.invoke(targetObject, args);

        }

              return result;

       }

}

測試代碼:

public class AopTests {

 

       @Test

       public void Tests(){

              ProxyFactory pf = newProxyFactory();

//            AopService as =(AopService)pf.createTargetObject(new AopServiceImpl());

              AopService as =(AopService)pf.createTargetObject(new AopServiceImpl("Frank"));

        as.doService(); 

       }

}

JDK動態代理爲什麼必須用接口以及與CGLIB的對比 

這 兩天對AOP原理感興趣了,試驗了JDK動態代理與CGLIB動態代理。從Spring的AOP框架介紹中得知對於使用接口的類,Spring使用JDK 動態代理(原來做項目中試圖從Bean強制轉換爲實現類,結果報錯,原來是這麼回事),沒有接口的就使用別的AOP框架aspectj,但這些都是依賴於 Java字節碼工具ASM生成一個原類的新類,調用Callback

但是JDK動態代理爲什麼必須使用接口一直很疑惑,難道原理不是像ASM一樣修改字節碼嗎?帶着這個疑問,開始看JDK的Proxy代碼。使用JDK動態代理的代碼代碼

ITestBean tb= (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(),tb.getClass().getInterfaces(), new TestBeanHander(tb));

於是從創建代理函數看起,即publicstatic Object newProxyInstance(ClassLoader loader,
   Class<?>[] interfaces, InvocationHandler h)
   throws IllegalArgumentException , 

通過源碼可以看到,這個類第一步生成一個代理類(注意,這裏的參數就是接口列表),

Class cl =getProxyClass(loader, interfaces);

然後通過代理類找到構造參數爲InvocationHandler的構造函數並生成一個新類。

Constructorcons = cl.getConstructor(constructorParams);//這個有用,在後面細說
return (Object) cons.newInstance(new Object[] { h });  

接口起什麼作用呢,於是又看getProxyClass方法的代碼,這個源碼很長,就不細說了。大致分爲三段:

第一:驗證

第二:緩存創建新類的結構,如果創建過,則直接返回。(注意:這裏的KEY就是接口列表)

第三:如果沒有創建過,則創建新類

創建代碼如下

   long num;
   //獲得代理類數字標識 

  synchronized (nextUniqueNumberLock) {
     num = nextUniqueNumber++;
    }

   //獲得創建新類的類名$Proxy,包名爲接口包名,但需要注意的是,如果有兩個接口而且不在同一個包下,也會報錯

    StringproxyName = proxyPkg + proxyClassNamePrefix + num;
    //調用class處理文件生成類的字節碼,根據接口列表創建一個新類,這個類爲代理類,
    byte[] proxyClassFile =ProxyGenerator.generateProxyClass(
      proxyName, interfaces);
    //通過JNI接口,將Class字節碼文件定義一個新類

     proxyClass= defineClass0(loader, proxyName,
       proxyClassFile, 0,proxyClassFile.length);

根據前面的代碼Constructorcons = cl.getConstructor(constructorParams);

可以猜測到接口創建的新類proxyClassFile 不管採用什麼接口,都是以下結構

public class$Proxy1 extends Proxy implements 傳入的接口{

    

}
生成新類的看不到源代碼,不過猜測它的執行原理很有可能是如果類是Proxy的子類,則調用InvocationHandler進行方法的Invoke

到現在大家都應該明白了吧,JDK動態代理的原理是根據定義好的規則,用傳入的接口創建一個新類,這就是爲什麼採用動態代理時爲什麼只能用接口引用指向代理,而不能用傳入的類引用執行動態類。

cglib採用的是用創建一個繼承實現類的子類,用asm庫動態修改子類的代碼來實現的,所以可以用傳入的類引用執行代理類

JDK動態代理與CGLIB對比如下:

//JDK動態代理測試代碼

ITestBean tb= new TestBean();
tb = (ITestBean) Proxy.newProxyInstance(tb.getClass().getClassLoader(),tb.getClass().getInterfaces(), new TestBeanHander(tb));//這句用接口引用指向,不會報錯

TestBean tmp= (TestBean) tb;//強制轉換爲實現類,將拋出類強制轉換異常

//CGLIB測試代碼

TestProxy tp= new TestProxy();
tb = (ITestBean) tp.getProxy(TestBean.class);

tmp =(TeatBean) tb;//強制轉換爲實現類,不會拋出異常

補充說明,如果在實現類中,接口定義的方法互相調用不會在調用InvocationHandler的invoke方法,JDK動態代理應該不是嵌入到Java的反射機制中,而是在反射機制上的一個調用。 

 

應用舉例如下:

JDK動態代理的簡單使用示例:

如有業務類:
package com.proxy;


public class ForumServiceImplimplements ForumService{
    public void removeTopic(int topicId){

       System.out.println("模擬刪除記錄"+topicId);
        try{
           Thread.currentThread().sleep(20);
        }catch(Exception e){
            throw newRuntimeException(e);
        }

    }

    public voidremoveForum(int forumId){
        System.out.println("模擬刪除記錄"+forumId);
        try{
           Thread.currentThread().sleep(20);
        }catch(Exception e){
            throw newRuntimeException(e);
        }
    }

}

1、創建一個實現java.lang.reflect.InvocationHandler接口的代理類,如:

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

public class PerformanceHandlerimplements InvocationHandler{
    private Object target; //要進行代理的業務類的實例
    public PerformanceHandler(Object target){
        this.target = target;
    }
//覆蓋java.lang.reflect.InvocationHandler的方法invoke()進行織入(增強)的操作
    public Object invoke(Object proxy, Method method, Object[]args)
    throws Throwable{
        System.out.println("Objecttarget proxy:"+target);
        System.out.println("模擬代理加強的方法...");
        Object obj = method.invoke(target,args); //調用目標業務類的方法
        System.out.println("模擬代理加強的方法執行完畢...");
        return obj;
    }
}

2、用java.lang.reflect.Proxy.newProxyInstance()方法創建動態實例來調用代理實例的方法:

import java.lang.reflect.Proxy;

public class TestForumService {
    public static void main(String args[]){
        ForumService target = newForumServiceImpl();//要進行代理的目標業務類

       PerformanceHandler handler = new PerformanceHandler(target);//用代理類把目標業務類進行編織
 
//創建代理實例,它可以看作是要代理的目標業務類的加多了橫切代碼(方法)的一個子類
        ForumService proxy =(ForumService)Proxy.newProxyInstance(
               target.getClass().getClassLoader(),
               target.getClass().getInterfaces(), handler);

       proxy.removeForum(10);
        proxy.removeTopic(20);
    }
}


CGLib動態代理示例:

1、創建一個實現net.sf.cglib.proxy.MethodInterceptor接口的實例來爲目標業務類加入進行代理時要進行的操作或增強:

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
/**
 *CGlib採用非常底層的字節碼技術,可以爲一個類創建子類,
 並在子類中採用方法攔截技術攔截父類方法的調用,並順勢進行增強,即是織入橫切邏輯
 * @author tufu
 */
public class CglibProxy implements MethodInterceptor{
    private Enhancer enhancer = new Enhancer();
    //覆蓋MethodInterceptor接口的getProxy()方法,設置
    public Object getProxy(Class clazz){
        enhancer.setSuperclass(clazz); //設者要創建子類的類
        enhancer.setCallback(this); //設置回調的對象
        return enhancer.create(); //通過字節碼技術動態創建子類實例,
    }

    public Objectintercept(Object obj,Method method,Object[] args,
            MethodProxyproxy) throws Throwable {
        System.out.println("模擬代理增強方法");

       //通過代理類實例調用父類的方法,即是目標業務類方法的調用
        Object result =proxy.invokeSuper(obj, args);

       System.out.println("模擬代理增強方法結束");
        return result;
    }
}

2、通過java.lang.reflect.Proxy的getProxy()動態生成目標業務類的子類,即是代理類,再由此得到代理實例:

import com.proxy.ForumServiceImpl;
import java.lang.reflect.Proxy;

public class TestCglibProxy {
    public static void main(String args[]){
        CglibProxy proxy = new CglibProxy();

       //動態生成子類的方法創建代理類
        ForumServiceImpl fsi =
               (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);

       fsi.removeForum(10);
        fsi.removeTopic(2);
    }
}

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章