使用Java動態代理技術實現AOP

實現動態代理

根據網絡上很多資料,實現一個業務接口的動態代理只需要三步:
- 定義業務接口
- 定義實現業務接口的業務類
- 根據Proxy類創建任何接口的代理類

第一:定義業務接口

AnimalInterface.java

package proxy.imp;

/**
 * 動態代理的業務接口定義
 * 
 * @ClassName: AnimalInterface
 * @Description: TODO(這裏用一句話描述這個類的作用)
 * @author PengRong
 * @date 2017年3月5日 下午3:52:15
 *
 */
public interface AnimalInterface {
    // 設置名字
    void setName(String name);

    // 獲取名字
    String getName();

    // 叫聲
    void say();

    // 獲取棲性
    void getProperty();

    // 設置棲性
    void setProperty(String Property);
}

第二:定義一個實現業務接口的具體類,也叫委託類,等下被代理的類

DogImp.java

package proxy;

import proxy.annon.Seven;
import proxy.imp.AnimalInterface;

/**
 * 實現接口的具體業務類
 * 
 * @ClassName: DogImp
 * @Description: TODO(這裏用一句話描述這個類的作用)
 * @author PengRong
 * @date 2017年3月5日 下午3:53:12
 *
 */
public class DogImp implements AnimalInterface {

    @Seven(value = "Lumia")
    private String name;

    private String Property;

    public DogImp() {
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void say() {
        System.out.println("小狗:汪汪汪汪.....");
    }

    @Override
    @Seven(Property = "水陸兩棲戰士")
    public void setProperty(String Property) {
        this.Property = Property;
    }

    @Override
    public void getProperty() {
        System.out.println(this.name + "= " + this.Property);
    }
}

可以看到我這裏使用了註解給具體業務類屬性賦值的技術,所以就引入了註解的定義和解析;詳情可看下面

第三步:先要實現一個調用處理器,然後Proxy類動態生成代理類。

實現調用處理器

AOPHandle.java

package proxy;

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

import proxy.imp.AOPMethod;

/*
 *   調用處理器,每一個代理類都必須有一個關聯的調用處理器,當代理類上的一個方法被調用都會被分發到這個調用處理器上
 *   的invoke方法上面;  InvocationHandler接口被實現可以作爲代理類的調用處理器功能
    * @ClassName: AOPHandle 
    * @Description: TODO(這裏用一句話描述這個類的作用) 
    * @author PengRong
    * @date 2017年3月5日 下午3:55:29 
    *
 */
public class AOPHandle implements InvocationHandler {
    // 保存AOP切入的接口引用
    private AOPMethod method;
    /**
     * 被代理的對象
     */
    private Object o;

    /**
     * 
     * 創建一個新的實例 AOPHandle.
     * 
     * @param o
     *            委託類實例引用
     * @param method
     */
    public AOPHandle(Object o, AOPMethod method) {
        this.o = o;
        this.method = method;
    }

    /**
     * 這個方法會自動調用,Java動態代理機制 會傳入下面是個參數
     * 
     * @param Object
     *            proxy 代理對象的接口,不同於對象
     * @param Method
     *            method 被調用方法業務接口
     * @param Object[]
     *            args 方法參數 不能使用invoke時使用proxy作爲反射參數時,因爲代理對象的接口,不同於對象
     *            這種代理機制是面向接口,而不是面向類的
     **/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = null;
        if (this.method != null) {
            // 修改的地方在這裏哦
            this.method.before(proxy, method, args);
            // 反射調用方法
            ret = method.invoke(o, args);
            // 修改的地方在這裏哦
            this.method.after(proxy, method, args);
        } else {//無AOP的路徑
            System.out.println("invocation handler before");
            ret = method.invoke(o, args);
            System.out.println("invocation hander after");
        }
        return ret;
    }
}

代理類動態生成:這個是生成動態代理類的核心

AnimalFactory.java

package proxy;

import java.lang.reflect.Proxy;

import proxy.annon.AnnoInjection;
import proxy.imp.AOPMethod;

/**
 * 根據 傳進來的委託實例引用創建並返回代理類引用
 * 
 * @ClassName: AnimalFactory
 * @Description: TODO(這裏用一句話描述這個類的作用)
 * @author PengRong
 * @date 2017年3月5日 下午4:13:37
 *
 */
public class AnimalFactory {

    /***
     * 獲取對象方法
     * 
     * @param obj
     * @return
     */
    private static Object getAnimalBase(Object obj, AOPMethod method) {
        // 獲取代理對象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                new AOPHandle(AnnoInjection.getBean(obj), method));
    }

    /***
     * 獲取對象方法
     * 
     * @param obj
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAnimal(Object obj, AOPMethod aopMethod) {
        return (T) getAnimalBase(obj, aopMethod);
    }

    /***
     * 獲取對象方法
     * 
     * @param className
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAnimal(String className, AOPMethod method) {
        Object obj = null;
        try {
            obj = getAnimalBase(Class.forName(className).newInstance(), method);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) obj;
    }

    /***
     * 獲取對象方法
     * 
     * @param clz
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAnimal(Class<?> clz, AOPMethod method) {
        Object obj = null;
        try {
            obj = getAnimalBase(clz.newInstance(), method);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) obj;
    }
}

然後就可以進行測試了
先進行註解的測試,看我在代碼中註解的值到底有沒有賦值到類實例中;

註解測試


package proxy;

import proxy.annon.AnnoInjection;

public class TestInjection {
    /***
     * 創建一個實例然後,通過注入邏輯自動將註解的內容賦值給實例屬性
     */
    public static void main(String[] args) throws InterruptedException {
        DogImp dogImp = (DogImp) AnnoInjection.getBean(new DogImp());//等下還會介紹其中的處理邏輯
        Thread.sleep(100);
        System.out.println(dogImp.getName());
        dogImp.getProperty();
    }
}

輸出結果:可以看到輸出結果我沒有對實例屬性進行任何的賦值操作,但是最後兩句輸出已經可以看出屬性已經有值了。前兩句是注入邏輯中輸出的log,可以看到進行了一次屬性注入,一次方法注入。

注入 name 屬性      Lumia
注入 setProperty 方法註解 水陸兩棲戰士

Lumia
Lumia= 水陸兩棲戰士

代理類測試

上面我們是建立了一個簡單的基於接口的動態代理技術框架,動態代理技術主要有委託接口,以及委託類,調用處理器,代理類動態生成這些技術組成;下面給出代理類的測試案例。


package proxy;

import java.lang.reflect.Method;

import proxy.imp.AOPMethod;
import proxy.imp.AnimalInterface;

public class AOPTest {

    public static void main(String[] args) {


        /**
         * 返回的dog爲代理實例
         */
        AnimalInterface dog = AnimalFactory.getAnimal(DogImp.class,  null);//通過這個函數調用返回DogImp類的代理類;
        //注意下這裏爲什麼第二個參數爲null,這個就是AOP切入的位置
        dog.say();// 實體的一個行爲
        String name1 = "我的名字是= " + dog.getName();// 通過這個可以看到可以將註解注入到屬性中
        System.out.println(name1);
        dog.setName("二狗子");
        String name2 = "我的名字是" + dog.getName();
        System.out.println(name2);
        dog.getProperty();
    }
}

輸出結果:看到註解注入成功,然後在註解處理器中走得是無AOP的路徑。

注入 name 屬性      Lumia
注入 setProperty 方法註解 水陸兩棲戰士
invocation handler before
小狗:汪汪汪汪.....
invocation hander after
invocation handler before
invocation hander after
我的名字是= Lumia
invocation handler before
invocation hander after
invocation handler before
invocation hander after
我的名字是二狗子
invocation handler before
二狗子= 水陸兩棲戰士
invocation hander after

自定義註解並用於給屬性賦值

定義註解

Seven.java文件

package proxy.annon;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定義的註解
 * 
 * @ClassName: Seven
 * @Description: TODO(這裏用一句話描述這個類的作用)
 * @author PengRong
 * @date 2017年3月5日 下午4:20:44
 *
 */
@Retention(RetentionPolicy.RUNTIME) // 表示這個註解可以生存到運行期
@Target({ ElementType.FIELD, ElementType.METHOD }) // 指定註解的使用範圍
public @interface Seven {
    // 設定註解的方法,註解方法沒有方法體,可以設置默認值
    public String value() default "小黑";

    public String Property() default "無屬性";

}

註解注入

註解自動注入是在程序定義一個空實例後,根據註解進行自動注入。根據剛纔自定義的註解可以這個註解只能用於字段,函數中,所有自動注入邏輯主要是遍歷一個變量的所有字段和函數,然後查看是否具有指定的Seven註解,然後讀取我們在源碼中設置的值並通過反射機制屬性對應的setXXX方法完成初始化。

package proxy.annon;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/*
 * 註解注入,將一個業務類實例的註解自動賦值給類屬性
    * @ClassName: AnnoInjection 
    * @Description: TODO(這裏用一句話描述這個類的作用) 
    * @author PengRong
    * @date 2017年3月5日 下午4:22:52 
    *
 */
public class AnnoInjection {

    public static Object getBean(Object obj) {
        try {
            // 獲得類屬性
            Field f[] = obj.getClass().getDeclaredFields();
            // 遍歷屬性,查找所有的屬性註解
            for (Field ff : f) {
                // 獲得屬性上的註解
                Seven s = ff.getAnnotation(Seven.class);// 返回ff屬性的Seven類型的註解
                if (s != null) {
                    System.err.println("注入 " + ff.getName() + " 屬性" + "\t\t" + s.value());
                    // 反射調用public set方法,如果訪問級別爲private,那麼可以直接使用屬性的set(obj,
                    // value);
                    obj.getClass()
                            .getMethod("set" + ff.getName().substring(0, 1).toUpperCase() + ff.getName().substring(1), // 組配函數名稱出來
                                    new Class<?>[] { String.class })
                            .invoke(obj, s.value());// 通過反射調用屬性對應的setXXX函數將註解的值賦值給類屬性
                }
            }
            // 獲得所有方法,查找方法註解
            Method m[] = obj.getClass().getDeclaredMethods();
            for (Method mm : m) {
                // 獲得方法註解
                Seven s = mm.getAnnotation(Seven.class);
                if (s != null) {
                    System.err.println("注入 " + mm.getName() + " 方法註解" + "\t" + s.Property());
                    mm.invoke(obj, s.Property());// 通過方法注入註解的值
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

}

定義一個AOP接口用於AOP切面編程

上面只是使用了動態代理技術和註解技術,下面通過定義一個AOP接口,將AOP接口的能力添加到代理類中

AOP接口

package proxy.imp;

import java.lang.reflect.Method;

/**
 * 這是一個AOP的一個切面;在每個接口方法中可以做一些類似於日誌處理的功能
 * 
 * @ClassName: AOPMethod
 * @Description: TODO(這裏用一句話描述這個類的作用)
 * @author PengRong
 * @date 2017年3月5日 下午4:18:20
 *
 */
public interface AOPMethod {
    // 實例方法執行前執行的方法,比如執行方法前,記錄類狀態,寫入log.監控xx變量,,,
    void after(Object proxy, Method method, Object[] args);

    // 實例方法執行後執行的方法
    void before(Object proxy, Method method, Object[] args);
}

通過在生產代理類的代碼中將實現AOP接口的實例傳遞進去。

package proxy;

import java.lang.reflect.Method;

import proxy.imp.AOPMethod;
import proxy.imp.AnimalInterface;

public class AOPTest {

    public static void main(String[] args) {

        /**
         * new AOPMethod() { // 這裏寫方法執行前的AOP切入方法
         * 
         * @Override public void before(Object proxy, Method method, Object[]
         *           args) { if (method.getName().equals("getProperty")) {
         *           System.err.println("成功攔截" + method.getName() + "方法,啓動"); }
         *           }
         * 
         *           // 這裏系方法執行後的AOP切入方法
         * @Override public void after(Object proxy, Method method, Object[]
         *           args) { if (method.getName().equals("getProperty"))
         *           System.err.println("成功攔截" + method.getName() + "方法,結束");
         * 
         *           } }
         */
        /**
         * 返回的dog爲代理實例
         */
        AnimalInterface dog = AnimalFactory.getAnimal(DogImp.class, new AOPMethod() { // 這裏寫方法執行前的AOP切入方法

            @Override
            public void before(Object proxy, Method method, Object[] args) {
                if (method.getName().equals("getProperty")) {
                    System.err.println("成功攔截" + method.getName() + "方法,啓動");
                }
            }

            // 這裏系方法執行後的AOP切入方法
            @Override
            public void after(Object proxy, Method method, Object[] args) {
                if (method.getName().equals("getProperty"))
                    System.err.println("成功攔截" + method.getName() + "方法,結束");

            }
        });// 返回一個代理類
        dog.say();// 實體的一個行爲
        String name1 = "我的名字是= " + dog.getName();// 通過這個可以看到可以將註解注入到屬性中
        System.out.println(name1);
        dog.setName("二狗子");
        String name2 = "我的名字是" + dog.getName();
        System.out.println(name2);
        dog.getProperty();
    }
}

執行結果:通過實現AOP接口實例傳遞進去,那麼將可以在實際業務方法執行前進行很多其他操作,比如統計,監控,日誌功能。

注入 name 屬性      Lumia
注入 setProperty 方法註解 水陸兩棲戰士
成功攔截getProperty方法,啓動
成功攔截getProperty方法,結束
小狗:汪汪汪汪.....
我的名字是= Lumia
我的名字是二狗子
二狗子= 水陸兩棲戰士

綜上:動態代理類技術是實現AOP的技術基礎。
源代碼
使用Java原生代理並實現註解自動注入

使用Java原生代理實現AOP

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