實現動態代理
根據網絡上很多資料,實現一個業務接口的動態代理只需要三步:
- 定義業務接口
- 定義實現業務接口的業務類
- 根據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原生代理並實現註解自動注入