黑馬程序員:jdk1.5新特性5(代理)

 

類加載器
1 java 虛擬機中可以安裝多個類加載器,系統默認三個主要類加載器,每個類負責加載特定位置的類:
 BootStrap,ExtClassLoader,AppClassLoader
2 類加載器也是java類,第一個類加載器BootStrap不是java類,它是java虛擬機中的一段二進制代碼,與java虛擬機一同加載。
3 java虛擬機中的所有類裝載器 採用具有父子關係的樹形結構進行組織,在實例化每個類加載器對象時需要爲其制定一個父級類裝載器對象或者默認採用系統類裝載器爲其父級類加載。


類加載器的委託機制
 當java虛擬機要加載一個類時,到底派出哪個類加載器去加載呢?
  1 首先當前線程的類加載器去加載線程中的第一個類。
  2 如果類A中引用了類B,java虛擬機將使用加載了類A的裝載器去加載類B。
  3 還可以調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類
 每個類加載器加載類時 ,又先委託給其上級類加載器
  1 當所有祖宗類加載器沒有加載到類,回到發起者類加載器,還加載不了,則拋出ClassNotFoundException
  2 對着類加載器的層次結構圖和委託加載原理,解釋先前將ClassLoaderTest輸出成jre/lib/ext目錄下的itcast.jar包中後,運行結果爲ExtClassLoader的原因。


代理的概念與作用:
 1 程序中的代理:
  爲已存在多個具有相同接口的,目標類的各個方法增加一些系統功能,例如:異常處理,日誌,計算方法的運行時間,事物管理等等 如何做?
  1 編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,並在調用方法時加上系統功能代碼
 2 如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是 使用目標類還是使用代理類,這樣以後很容易切換,譬如,想要日誌功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以後運行一段時間後,想去掉也很容易。

AOP(面向方面的編程)
安全,事務,日誌等功能要貫穿到好多個模塊中,所有,他們就是交叉業務。
系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面,如下:
                  安全     事務    日誌        
StudentService  ----|--------|-------|-----
CourseService   ----|--------|-------|-----
MiscService     ----|--------|-------|-----
 用具體的程序代碼描述交叉業務:
  method1     method2     method3
  {             {           {
 ----------------------------------切面
 ....         ....       ....
  ---------------------------------切面
   }           }             }
交叉業務的編程問題極爲面向方面的編程(Aspect oriented program 簡稱AOP),AOP的目標就是要使交叉業務模塊化,可以採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面的代碼運行效果是一樣的,如下
--------------------------切面
 func1   func2   func3
  {        {       {
  ...     ...     ...
  }        }        }
-------------------------切面
使用代理技術正好可以解決這個問題 ,代理是實現AOP功能的核心的關鍵技術。

動態代理技術


JVM可以在運行期動態的生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類
JVM生成的動態類必須實現一個或多個接口,所以,JVM生成的動態類只能用作具有相同接口的目標類的代理
CGLIB庫可以動態生成一個類的子類,一個類的子類也可以作爲該類的代理,所以,如果要爲一個沒有實現接口的類生成動態代理類,那麼可以使用CGLIB庫
代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果外,還可以在代理方法中的如下四個位置駕駛系統功能代碼。
 <1 在調用目標方法之前
 <2 在調用目標方法之後
 <3 在調用目標方法前後
 <4 在處理目標方法異常的catch塊中

JVM動態生成類
 Proxy 類能夠動態生成類,編寫一個動態類,獲得它的構造方法和 其他方法
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 clazzProxy=Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
 
 System.out.println("---------bengin constructors list--------");
 Constructor[] constructors=clazzProxy.getConstructors(); 
    for(Constructor constructor:constructors){
     String name=constructor.getName();
     StringBuilder sBuilder=new StringBuilder();
     sBuilder.append(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);
    }
 System.out.println("---------bengin methods list--------");
 Method[] methods=clazzProxy.getMethods();
   
    for(Method method:methods){
     String name1=method.getName();
     StringBuilder sBuilder1=new StringBuilder();
     sBuilder1.append(name1);
     sBuilder1.append("(");
        Class[] clazzParams=method.getParameterTypes();
        for(Class clazzParam:clazzParams){
         sBuilder1.append(clazzParam.getName()).append(",");
         if(clazzParams!=null && clazzParams.length!=0){
          sBuilder1.deleteCharAt(sBuilder1.length()-1);   }    } 
     sBuilder1.append(")");
     System.out.println(sBuilder1);
    } }    }

分析動態生成的類的內部代碼
 1 動態生成的類實現了Collection接口(可實現若干個接口),生成的類有Collection接口中的所有方法和一個如下接受InvocationHandler參數的構造方法
 2 構造方法接受一個InvocationHandler對象,接受對象要做什麼呢?(接受了對象就是要記住這個對象在以後使用!)
 3 InvocationHandler接口中定義的invoke方法接受的三個參數又是什麼意思?
  Client程序調用objProxy.add("abc")方法時,涉及到三要素:objProxy對象,add方法,"abc"參數
  它們分別對應InvocationHandler 接口唯一方法的三個參數invoke(Object proxy, Method method,Object[] args) {return null;} objProxy調用一個方法後,程序會直接調用InvocationHandler 實例的invoke方法,並傳遞三要素。invoke方法的返回值就是objProxy調用方法所要的結果。

讓動態生成的類稱爲目標類的代理
 1 分析動態代理的工作原理圖


 2 怎樣將目標類傳進去?
  <1 直接在InvocationHandler實現類中創建目標類的實例對象,可以看運行效果和加入日誌代碼,但沒實際意義
  <2 爲InvocationHandler實現類注入目標類的實例對象,不能採用匿名內部類的形式了
  <3 讓匿名的InvocationHandler實現類訪問外面方法中的目標類實例對象的final類型的引用變量
 3 將創建代理的過程改爲一種更優雅的方式,eclipse重構出一個getProxy方法綁定接受目標同時返回代理對象,讓調用者更懶惰,更方便,調用者甚至不用接觸任何代理的API
 4 將系統功能代碼模塊化,即將切面代碼也改爲通過參數形式提供,怎麼樣把要執行的系統功能代碼以參數形式提供?
  <1 把要執行的代碼裝到一個對象的某個方法裏,然後把這個對象作爲參數傳遞,接受者只要調用這個對象的方法,即等於執行了外界提供的代碼
  <2 爲bind方法增加一個Advice參數。
 調用:final ArrayList target=new ArrayList();
       Collection proxy3 = (Collection)getProxy(target,new MyAdvice());
   proxy3.add("qwe");
   proxy3.add("sdf");
   proxy3.add("cvb");
   System.out.println(proxy3.size());

代理:
private static Object getProxy(final Object target,final Advice advice) {
 Object proxy3=Proxy.newProxyInstance(
  target.getClass().getClassLoader(),
  //Collection.class.getClassLoader(),
  target.getClass().getInterfaces(),
  //new Class[] {Collection.class},
  new InvocationHandler(){
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {

    /*long beginTime=System.currentTimeMillis();
    Object retval=method.invoke(target, args);
    long endTime=System.currentTimeMillis();
    System.out.println(method.getName()+"running time is"+(endTime-beginTime));
        return retval;*/
       
    advice.beforeMethod(method);
     Object retval=method.invoke(target, args);
    advice.afterMethod(method);
       
 return retval;
         }  }
 );
 return proxy3;

實現AOP功能的封裝與配置

 1 工廠類BeanFactory負責創建目標類或代理類的實例對象,並通過配置文件實現切換,器getBean方法根據參數字符串返回一個相應的實例對象,如果參數字符串在配置文件中對應的類名不是ProxyFactoryBean,則返回該類的實例對象,否則 返回該類實例對象的getProxy方法返回的對象
 2 BeanFactory的構造方法接受代表配置文件的輸入流對象,配置文件格式如下:
#xxxjava.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
3 ProxyFactoryBean 充當封裝生成動態代理的工廠,需要爲工廠提供的配置信息:
  目標  target
  通知  advice
4 編寫客戶端應用:
編寫實現Advice接口的類和在配置文件中進行配置
調用BeanFactory獲取對象
代碼:workspace2/javaenhance/cn/itcast/day3/aopframexwork

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