1. 程式人生 > >JAVA提高八:動態代理技術

JAVA提高八:動態代理技術

構造 abc app sna 字節碼 代理技術 偽代碼 art object

對於動態代理,學過AOP的應該都不會陌生,因為代理是實現AOP功能的核心和關鍵技術。那麽今天我們將開始動態代理的學習:

一、引出動態代理

生活中代理應該是很常見的,比如你可以通過代理商去買電腦,也可以直接找廠商買電腦,最終都是買到了電腦。程序中也一樣存在代理的情況,比如要為已經存在的多個具有相同接口的目標類的各個方法增加一些系統功能,例如:異常處理、日誌、計算方法耗時等等,那麽我們會怎麽做呢?

1.會編寫一個與目標類擁有相同接口的代理類,代理類的每個方法調用目標類的相同方法,然後在調用方法前後加上系統功能所需要的代碼。

2.如果采用工廠模式或者配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類、還是代理類,這樣以後很容易切換。

樣例如下:

public class X{
 public void sayHello(){
 syso:say hello;
 }
}

現在我要在這個方法之前添加一個時間,方法之後添加一個時間,計算這個方法執行的時間一共是多少.如果我沒有得到sayHello源碼,那麽我怎麽做呢?寫一個代理:

public class XProxy
{
   private X x;
   public void sayHello
  { 
     startTime:
     x.syHello();
     endTime;
  }
}

說明:上面的是偽代碼。

把開始時間和結束時間放在這個方法的前後就可以了. 通常我們讓兩個方法實現同一個接口.那麽client想用X也可以,想用XProxy也可以了.具體的原理圖,如下圖所示: 技術分享

二、創建動態代理類

現在試想一下,上面只是代理了一個目標類,如果多個目標類,那麽是不是要創建N多個代理類呢?那樣不是代碼太不靈活且笨重了。當然不會。

java虛擬機可以在運行期間動態生成類,這種類是以字節碼的形式生成出來的。這種動態生成的類往往呢就是代理類。即動態代理類。

JVM生成的動態代理類必須滿足一定的條件,這就是必須實現一個或多個接口。所以JVM生成的動態代理只能用作具有相同接口的目標類的代理。(動態生成的類不是代理,我們只是吧這個類當成代理來用。)

Proxy動態代理的API:

技術分享

兩個參數應該很容易理解:

第一個參數:我們知道任何一個字節碼都是需要通過類加載器來加載的,那麽這個動態生成的字節碼也不例外,需要給它一個類加載器。

第二個參數:就是動態代理類生成,必須滿足的一個條件,需要實現一個或者多個接口,否則這個生成的類字節碼中就沒有方法了,沒有方法就失去了其功能意義。

下面我們動手來創建一個動態的代理類,大體思路為:

1.創建實現Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的各個參數

2.編碼列出動態類中的所有構造方法和參數簽名

3.編碼列出動態類中的所有方法和參數簽名

4.創建動態類的實例對象:1)用反射獲取構造方法 2)編寫一個最簡單的invocationHandle類 3)調用構造方法創建動態類的實例對象,並將編寫的InvocationHandle類的實例對象傳遞進去 4)打印創建對象和調用對象的沒有返回的方法和getClass方法,演示調用其他有返回值方法報告了異常。

5)將創建動態類的實例對象的代理寫成匿名內部類方式,簡化代碼。

樣例分步實現如下:

(1)首先我們來完成前面3步:

package study.javaenhance;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

public class ProxyTest
{
    public static void main(String[] args) 
    {
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
        System.out.println(clazzProxy1.getName());
        
        //上面輸出的為一個類,那麽一個類肯定有其構造方法和方法,下面我們來列出來
        
        System.out.println("----------begin constructors list----------");
        //1.列出構造方法
        Constructor[] constructors = clazzProxy1.getConstructors();
        for (Constructor constructor : constructors) 
        {
            String name = constructor.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append(‘(‘);
            Class[] clazzParams = constructor.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(‘,‘);
            }
            if(clazzParams!=null && clazzParams.length != 0)
                sBuilder.deleteCharAt(sBuilder.length()-1);
            sBuilder.append(‘)‘);
            System.out.println(sBuilder.toString());    
        }
        
        //2.列出這個類字節碼中的所有方法
        System.out.println("----------begin methods list----------");
        Method[] methods = clazzProxy1.getMethods();
        for(Method method : methods){
            String name = method.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append(‘(‘);
            Class[] clazzParams = method.getParameterTypes();
            for(Class clazzParam : clazzParams){
                sBuilder.append(clazzParam.getName()).append(‘,‘);
            }
            if(clazzParams!=null && clazzParams.length != 0)
                sBuilder.deleteCharAt(sBuilder.length()-1);
            sBuilder.append(‘)‘);
            System.out.println(sBuilder.toString());            
        }
        
        
    }

}

輸出結果如下:

$Proxy0
----------begin constructors list----------
$Proxy0(java.lang.reflect.InvocationHandler)
----------begin methods list----------
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()

可以看到所有的方法均是來自Collection 和父類Object中的方法,符合我們的預期結果。接下來我們進入第四步和第五步的實現:

首先,我們來創建這個動態代理類的實例。那麽直接clazzProxy1.newInstance();可不可以呢?顯然是不可以的嘛.我們剛剛說了,動態生成的這個代理類只有一個構造方法,有沒有無參構造方法呢?沒有啊.所以,創建 一個參數的構造方法.參數類型是java.lang.reflect.InvocationHandler。

技術分享

下面我們來實現:

1.定義一個上述接口的實例:

package study.javaenhance;

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

public class MyInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        return null;
    }

}

2.調用實現創建實例動態類:

//3.創建實例對象
        System.out.println("----------begin create instance object----------");
        Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        Collection collection = (Collection) constructor.newInstance(myInvocationHandler);
        System.out.println(collection);
        collection.clear();
        //collection.size(); //報錯,異常會發生,產生異常的原因在於其返回值為int類型,但是每一次調用一個方法都會調用到invoke方法,我們此時的invoke返回的為null,所以是沒有辦法轉換為int類型的。

3.我們采用匿名內部類的方式優化:

//3.1 采用匿名內部類方式進行創建
        Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler()
        {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
            
        });
        System.out.println(collection2);
        collection2.clear();
        //collection2.size();

4.繼續優化:思考如果我們每次都想上面方式去創建動態的代理類實在有點重復,那麽這個是JDK的Proxy類中提供了簡單的方法直接去創建動態代理類,方式如下:

//3.3 采用Proxy 中提供的簡單方法創建
        Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
                new Class[]{Collection.class},
                new InvocationHandler()
        {
            private ArrayList target = new ArrayList();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                return method.invoke(target, args);
            }
            
        });
        //System.out.println(collection3);
        collection3.add("abc");
        collection3.add("def");
        collection3.add("hij");
        System.out.println(collection3.size());

三、動態代理的原理簡單分析

上面我們創建了動態代理類,下面我們分析下代理的原理:

技術分享

技術分享

技術分享

下面在來看一個問題:

技術分享

動態代理的工作原理圖:

技術分享

對上面的這個圖,我們簡單來說說:客戶端動態生成代理類,然後調用代理類的方法,代理類內部調用handler.invoke()方法,在invoke中呢,我們又指向的目標類.這樣就實現了代理了.我客戶端調用代理的什麽方法,invoke就只向目標類的同一個方法.而在指定目標類方法的前後呢,我們還可以做其他的操作,比如記錄日誌.圖中用圈圈出來的部分就是代理類自己實現的功能了.這就是代理類的原理.

技術分享


我們來做最後一步,將上面的動態生成的代理類,編寫可生成代理和插入通告的通用方法:

技術分享

test代碼:

package study.javaenhance;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

public class ProxyTest
{
    public static void main(String[] args) throws Exception
    {
        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
        System.out.println(clazzProxy1.getName());
        
        //上面輸出的為一個類,那麽一個類肯定有其構造方法和方法,下面我們來列出來
        
        System.out.println("----------begin constructors list----------");
        //1.列出構造方法
        Constructor[] constructors = clazzProxy1.getConstructors();
        for (Constructor constructor : constructors) 
        {
            String name = constructor.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append(‘(‘);
            Class[] clazzParams = constructor.getParameterTypes();
            for (Class clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(‘,‘);
            }
            if(clazzParams!=null && clazzParams.length != 0)
                sBuilder.deleteCharAt(sBuilder.length()-1);
            sBuilder.append(‘)‘);
            System.out.println(sBuilder.toString());    
        }
        
        //2.列出這個類字節碼中的所有方法
        System.out.println("----------begin methods list----------");
        Method[] methods = clazzProxy1.getMethods();
        for(Method method : methods){
            String name = method.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            sBuilder.append(‘(‘);
            Class[] clazzParams = method.getParameterTypes();
            for(Class clazzParam : clazzParams){
                sBuilder.append(clazzParam.getName()).append(‘,‘);
            }
            if(clazzParams!=null && clazzParams.length != 0)
                sBuilder.deleteCharAt(sBuilder.length()-1);
            sBuilder.append(‘)‘);
            System.out.println(sBuilder.toString());            
        }
        
        //3.創建實例對象
        System.out.println("----------begin create instance object----------");
        Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        Collection collection = (Collection) constructor.newInstance(myInvocationHandler);
        System.out.println(collection);
        collection.clear();
        //collection.size(); //報錯,異常會發生,產生異常的原因在於其返回值為int類型,但是每一次調用一個方法都會調用到invoke方法,我們此時的invoke返回的為null,所以是沒有辦法轉換為int類型的。
        
        //3.1 采用匿名內部類方式進行創建
        Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler()
        {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
            
        });
        System.out.println(collection2);
        collection2.clear();
        //collection2.size();
        
        //3.3 采用Proxy 中提供的簡單方法創建
        Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
                new Class[]{Collection.class},
                new InvocationHandler()
        {
            private ArrayList target = new ArrayList();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                return method.invoke(target, args);
            }
            
        });
        //System.out.println(collection3);
        collection3.add("abc");
        collection3.add("def");
        collection3.add("hij");
        System.out.println(collection3.size());
        System.out.println(collection3.getClass().getName());//這個返回的是
        
        System.out.println("----------begin create instance object  抽化----------");
        //3.4抽出動態代理讓目標對象和切面對象都是傳入進去的
        
        final ArrayList target = new ArrayList();
        Collection collection4 = (Collection)getProxy(target,new MyAdvice());
        collection4.add("test1");
        collection4.add("test2");
        System.out.println(collection4.size());
    }

    private static Object getProxy(final Object target,final Advice advice) {
        Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler()
        {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                advice.beforeMethod(method);
                Object retValue = method.invoke(target, args);
                advice.afterMethod(method);
                return retValue;
            }
            
        });
        return object;
    }

}

四、實現類似Spring的可配置的AOP框架

首先,我們要完成的要求如下:

技術分享

我們來模擬Spring的工廠模式讀取從配置文件傳遞過來的類。如果這個類是一個普通類則直接返回。如果是一個代理類,則創建代理對象,返回代理類。
具體的理論知識可以看上面的圖片
首先創建一個BeanFactory.java類。這是一個Bean工廠,用於讀取配置文件中的類

package study.javaenhance.aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import study.javaenhance.Advice;

public class BeanFactory 
{
    private Properties properties = new Properties();
    
    
    public BeanFactory(InputStream inStream)
    {
        try 
        {
            properties.load(inStream);
        }
        catch (IOException e) 
        {
            e.printStackTrace();
        }
        finally
        {
            if(inStream != null)
            {
                try 
                {
                    inStream.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

    
    public Object getBean(String name)
    {
        String className = properties.getProperty(name);
        Object bean = null;
        try
        {
            Class clazz = Class.forName(className);
            bean = clazz.newInstance();
            if(bean instanceof ProxyFactoryBean)
            {
                ProxyFactoryBean proxyBean = (ProxyFactoryBean) bean;
                Advice advice = (Advice)Class.forName(properties.getProperty(name + ".advice")).newInstance();
                Object target = Class.forName(properties.getProperty(name + ".target")).newInstance();
                proxyBean.setAdvice(advice);
                proxyBean.setTarget(target);
                Object proxy = proxyBean.getProxy();
                return proxy;
            }
        } 
        catch (Exception e) 
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return bean;
    }
}
如果這個類中包含ProxyFactoryBean,則調用ProxyFactoryBean中的getProxy方法。動態生成代理類。 ProxyFactoryBean.java
package study.javaenhance.aopframework;

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

import study.javaenhance.Advice;

public class ProxyFactoryBean
{

    private Object target;
    
    private Advice advice;
    
    
    public Object getTarget() {
        return target;
    }


    public void setTarget(Object target) {
        this.target = target;
    }


    public Advice getAdvice() {
        return advice;
    }


    public void setAdvice(Advice advice) {
        this.advice = advice;
    }


    public Object getProxy() 
    {
        Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),
                new InvocationHandler()
        {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                advice.beforeMethod(method);
                Object retValue = method.invoke(target, args);
                advice.afterMethod(method);
                return retValue;
            }
            
        });
        return object;
    }

}

接下來創建一個config.Properties文件.config.Properties

xxx=java.util.ArrayList
#xxx=study.javaenhance.aopframework.ProxyFactoryBean
xxx.advice=study.javaenhance.MyAdvice
xxx.target=java.util.ArrayList

最後建立測試類:

package study.javaenhance.aopframework;

import java.io.InputStream;
import java.util.Collection;

public class AopFrameworkTest
{
    public static void main(String[] args)
    {
        InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
        Object bean = new BeanFactory(ips).getBean("xxx");
        System.out.println(bean.getClass().getName());
        ((Collection)bean).clear();
    }

}

測試OK

總結:整個java增強的視頻學習完成了,一共記住了多少我也不知道.但知道了很多內在的知識,如果有時間的話,或者說過一段時間可以拿出來問下,提供自己的技能。

參考資料:

張孝祥老師java 增強視頻

JAVA提高八:動態代理技術