動態代理與AOP----6
動態代理實例化的過程升級--目標對象+系統功能的參數化
實現類似Spring的可配置AOP框架
----------- android培訓、java培訓、java學習型技術博客、期待與您交流! ------------
上次課基本上推理出動態生成的代理類的結構以及InvocationHandler的結構。這次針對InvocationHandler的結構進行改進。
1. 動態代理類實例化過程升級--目標對象+系統功能的參數化
1). 現有InvocationHandler實現類存在的問題
(1). 現有InvocationHandler的實現類的一般結構
[1]. InvocationHandler實現類的代碼如下
Object proxyInstance=con.newInstance(new InvocationHandler() {
private ArrayList target =new ArrayList();
public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable {
//交叉業務
Object retVal = method.invoke(target, args);
//交叉業務
return retVal;
}
});
(2). 存在的問題如下:
[1]. 目標類對象已經固化到代碼中,沒有辦法由用戶傳入進行設置
[2]. 交叉業務也同樣被固化到代碼中,無法由用戶自由配置。【交叉業務也叫系統功能】
目標:將目標類對象移動到匿名局部類newInvocationHandler(){}的外部進行配置
將系統功能 (交叉業務) 通過參數傳入InvocationHandler的局部匿名類
2). 目標對象和系統功能的參數化
(1). 目標對象和系統功能獨立應注意的問題
[1]. 目標類對象從InvocationHandler的匿名局部類中獨立出來
【注意】局部類只能訪問外部final變量
[2]. 系統功能從InvocationHandler的匿名局部類中獨立出來
{1}. 系統功能是代碼塊,要獨立出去並且以參數的形式傳入InvocationHandler的invoke方法,就必須要包裝成獨立的對象的方法纔可以。
{2}. 系統功能以獨立對象的方法被封裝到InvocationHandler匿名局部類的外面之後,又需要被這個匿名局部類引用。那麼封裝着系統功能的對象就必須也是final的。
(2). 封裝系統功能的對象所屬的類的設計思路
{1}. 系統功能的位置
[1]. 前面[2]. 後面[3]. 前面+後面[4]. catch塊中
這四個位置的代碼一定都要被封裝成四個方法並且一定要實現。因此這是一種強制性的工作並且系統功能也並不是確定的。所以向上抽取成有含有四個方法的接口更合理
{1}. 接口的命名
Spring將系統功能看做是用戶對代碼的建議,所以接口命名成Advice
【注意】這裏面僅僅定義兩個方法作爲示例方法前 + 方法後
{2}. 接口中方法的命名
[1]. 目標類對象代碼前面--- beforeMethod
[2]. 目標類對象代碼後面--- afterMethod
{3}. 接口中方法的參數列表
Spring規定封裝業務代碼的接口的方法應該傳入目標類對象、目標類對象調用的方法和目標類對象調用的方法對應的參數
[3]. 對交叉業務 (系統功能) 封裝的接口代碼如下
public interface Advice {
//目標代碼之前的交叉業務
void beforeMethod(Object target, Methodmethod, Object[] args);
//目標代碼之後的交叉業務
void afeterMethod(Object target, Methodmethod, Object[] args);
}
(3). 構造既可以生成代理類對象又可以插入系統功能的通用方法
[1]. 構建封裝了交叉業務的接口Advice的實現類MyAdvice
實現Advice的接口中要求的具體交叉業務 (系統功能)
這裏假設交叉業務的功能是計算目標方法運行的時間
public class MyAdvice implements Advice{
private long beginTime;
private long endTime;
@Override
public void beforeMethod(Object target, Methodmethod, Object[] args) {
//交叉業務
System.out.println("到黑馬學習啦!");
beginTime =System.currentTimeMillis();
}
@Override
public void afeterMethod(Object target, Methodmethod, Object[] args) {
//交叉業務
System.out.println("從黑馬馬上畢業了!");
endTime =System.currentTimeMillis();
System.out.println(method.getName() +" using "+ (endTime -beginTime)/1000+ "s");
}
}
[2]. 編寫可以插入系統功能和目標類對象的代理類實例生成器
public static Object getProxy(final Object target, final Advice advice){
ClassLoaderloader =target.getClass().getClassLoader();
Class<?>[]interfaces =target.getClass().getInterfaces();
InvocationHandlerh =new InvocationHandler() {
@Override
public Object invoke(Object proxy, Methodmethod, Object[] args)
throws Throwable {
advice.beforeMethod(target,method, args);
ObjectretVal =method.invoke(target, args);
advice.afterMethod(target,method, args);
return retVal;
}
};
ObjectproxyObj =Proxy.newProxyInstance(loader, interfaces, h);
return proxyObj;
}
[3]. 測試
Collection target =new ArrayList();
Advice advice =new MyAdvice();
Collection proxyInstance =(Collection)getProxy(target,advice);
proxyInstance.add("123");
System.out.println("*************************");
proxyInstance.add("456");
System.out.println("*************************");
System.out.println(proxyInstance.size());
【打印結果】
【總結】
{1}. 將目標和系統功能均抽取到外面之後,如果再向生成代理類實例的話,直接傳入目標類對象和封裝有交叉業務的對象。
{2}. getProxy()方法可以固定下來做框架使用。
【經驗】使用Spring僅僅需要做的事情:在配置文件中配置目標對象 + 在Advice的方法實現中編寫業務代碼。
2. 實現類似Spring的可配置的AOP框架
需求:通過配置文件配置出想要實例化的對象的所屬類,通過反射技術在代碼中獲取配置文件的信息並實例化要配置的類。
【附加需求】實例化的對象既可以是有字節碼的文件存在的預先通過編譯的類對象,也可以是沒有字節碼文件的動態生成的代理類對象。
1). 配置文件的設計
[1]. 動態生成類和普通類生成的區別
{1}. 普通類直接給出類名就可以在程序中實例化這個類的對象
{2}. 動態代理類的生成需要InvocationHandler的實現子類 ( 封裝了系統功能的類 ) 和代理的目標類這兩個額外的信息來生成動態代理類對象。
[2]. 現在採用簡單工廠模式生成普通類的實例。爲了統一起見將生成動態代理類對象的任務交給指定名稱的自定義的普通的Java類來實現。
{1}. 這個指定的普通類名叫spring.ProxyFactoryBean
一旦在配置的類名是這個類的名字,那麼工廠類就會實例化ProxyFactoryBean類的實例。這個類被實例化之後,就要調用內部編好的方法(直接可以調用第一部分的getProxy方法)來實例化動態代理類對象。
{2}. 如果是其他的類名,就直接實例化就可以了。
[3]. 配置文件內容的構成
{1}. 普通類的類名:設置爲beanName
{2}. 如果普通類的類名是spring.ProxyFactoryBean,標誌着這個實例對象是用來生成動態代理類的普通類。此時要額外配置代理類需要的代理類目標類類名和封裝業務代碼的Advice子類的類名
{2}1. 代理類代理的目標類類名:beanName.target
{2}2. 代理類需要的交叉業務所在的類名:beanName.advice
[4]. 配置文件舉例如下:
2). Bean工廠的設計 ----BeanFactory
通過字符串封裝的類名來獲取類對象 ----getBean(String className)
無論是普通類對象還是動態代理類對象都通過這個類BeanFactory的getBean(String)來獲取相應的對象。
【流程】. 這樣無論是代理類對象的實例化還是普通類對象的實例化都首先經過Class對象進行實例化。一旦出現指定的普通類ProxyFactoryBean類出現在配置文件中並創建Class對象實例化之後,就要調用這個ProxyFactoryBean類實例的方法生成動態代理類對象。
3). 工程測試
(1). 工程配置
(2). ProxyFactoryBean類代碼示例
package spring;
importjava.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean {
private Advice advice;
private Object target;
//生成動態代理類對象
public Object getProxyBean(){
ObjectproxyInstance =Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Methodmethod,
Object[]args) throws Throwable {
advice.beforeMethod(target, method, args);
ObjectretVal =method.invoke(target,args);
advice.afterMethod(target, method, args);
return retVal;
}
});
return proxyInstance;
}
//Setter & Getter
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
}
(3). BeanFactory示例代碼
package spring;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
Propertiesprops =new Properties();
public BeanFactory(InputStream ips){
try {
props.load(ips);
}catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name) throws ClassNotFoundException,InstantiationException, IllegalAccessException{
StringbeanName =props.getProperty(name);
ClassbeanClass =Class.forName(beanName);
//JavaBean必須有一個無參的構造函數
Objectbean =beanClass.newInstance();
//看看是否是動態代理類的普通標誌對象
if(bean instanceof ProxyFactoryBean){
//從配置文件中獲取交叉業務的配置
StringadviceName =props.getProperty(name+".advice");
//從配置文件中獲取目標類
StringtargetName =props.getProperty(name+".target");
Adviceadvice =(Advice)Class.forName(adviceName).newInstance();
Objecttarget =Class.forName(targetName).newInstance();
ProxyFactoryBeanproxyFactoryBean =(ProxyFactoryBean)bean;
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
//實例化動態代理類對象
ObjectproxyBean =proxyFactoryBean.getProxyBean();
return proxyBean;
}
return bean;
}
}
(4). 測試類代碼
package spring;
import java.io.InputStream;
import java.util.Collection;
//客戶端程序
public class AOPFrameWorkTest {
public static void main(String[] args) {
InputStreamips =null;
try {
ips=AOPFrameWorkTest.class.getResourceAsStream("config.properties");
Collectionbean =(Collection)new BeanFactory(ips).getBean("beanName");
StringclassName =bean.getClass().getName();
if(className.equals("$Proxy0"))
System.out.println("動態代理對應的普通類:"+ className);
else
System.out.println("普通類:"+className);
//客戶端調用功能
bean.add("Benjamin");
System.out.println("******");
bean.add("Zhang");
System.out.println("******");
bean.add("will finish the blog stagein some days....");
System.out.println("******");
System.out.println(bean.size());
System.out.println("******");
System.out.println(bean);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(5). 測試I ----普通類
[1]. config.properties配置文件內容
beanName=java.util.ArrayList
#beanName=spring.ProxyFactoryBean
#The following items used forcreating dynamic proxy objects
beanName.target=java.util.ArrayList
beanName.advice=spring.MyAdvice
[2]. 測試結果
(6). 測試II ----動態代理類
[1]. config.properties配置文件內容
beanName=spring.ProxyFactoryBean
#beanName=java.util.ArrayList
#The following items used forcreating dynamic proxy objects
beanName.target=java.util.ArrayList
beanName.advice=spring.MyAdvice
[2]. 測試結果
【解釋】原本就是Collection實例的add和獲取集合元素個數size()
但是這回在這些方法之前增加了交叉業務:打印系統信息並且計算目標方法的運行時間所以打印結果如上所示。
(7). Spring的兩大核心
[1]. Bean工廠
[2]. AOP框架
----------- android培訓、java培訓、java學習型技術博客、期待與您交流! ------------