kapok

          垃圾桶,嘿嘿,我藏的這么深你們還能找到啊,真牛!

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            455 隨筆 :: 0 文章 :: 76 評論 :: 0 Trackbacks

          http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=17

          作者:sdj21,rocksun(dev2dev ID)
          摘要:

            本文介紹了如何使用JDK5.0的新特性Annotation和Spring框架來實(shí)現(xiàn)一種簡單的JavaControl框架,主要實(shí)現(xiàn)了成員變量的自動帶入,以及方法調(diào)用的權(quán)限檢查控制,同時(shí)實(shí)現(xiàn)了一個JavaDatabaseControl的擴(kuò)展,模仿workshop中的對應(yīng)功能。

          目錄:

          環(huán)境
          背景資料
          JavaControl最基本的功能--聲明注入
          使用動態(tài)代理來增加橫切
          Control方法的權(quán)限檢查
          一個DatabaseControl的特例
          關(guān)于Demo程序
          框架如何的加強(qiáng)
          感受
          代碼下載
          參考文檔

          環(huán)境 (目錄)

          由于使用了Annotation,所以必須準(zhǔn)備好JDK5.0

          背景知識 (目錄)

          1)Annotation簡介
            在Java領(lǐng)域,最早的Annotation就是JavaDoc了,將文檔直接寫在源程序里極大的方便了文檔的編寫,后來出現(xiàn)了許多有這種同步需求的工作,大家便發(fā)明了XDoclet,使用類似于JavaDoc的語法撰寫描述信息,并使用工具生成描述文件。到JDK5.0出現(xiàn)以前這種需求已經(jīng)更多了,許多工具和框架已經(jīng)通過各種方式實(shí)現(xiàn)自己的這種標(biāo)記,.NET更是率先推出了語言級的支持,所以JDK5.0終于推出了自己的Annotation。
            以下是兩個簡單的Annotation定義,定義的方式類似于接口,只是在interface前面多了個"@"

          public @interface SampleAnnotation{
                  String someValue;
          }
          public @interface NormalAnnotation{
                String value1;
                int value2;
          }

            然后我們在程序里可以這樣使用Annotation,我們的編程工具或者是運(yùn)行中的框架程序可以讀取這些內(nèi)容,生成代碼或者是添加動作。

          @SampleAnnotation (someValue ="Actual Value used in the program”)
          public void annotationUsingMethod(){
                …
          }

            Annotation主要是給工具開發(fā)商和框架開發(fā)者使用的工具,一般的編程人員可能僅僅是使用其他人開發(fā)的Annotation。Workshop在兩年前已經(jīng)開始嘗試在開發(fā)工具里運(yùn)用Annotaion,但當(dāng)時(shí)沒有語言級的支持,所有的Annotation都是以注釋的形式出現(xiàn),這樣雖然靈活但是不嚴(yán)謹(jǐn),也不適于推廣,所以Annotation的出現(xiàn)可以大大方便開發(fā)商的工作,使得許多小開發(fā)商以及一般的架構(gòu)設(shè)計(jì)人員也可以利用這種方式編程。
          注:本文中Annotation可能對應(yīng)的名字是“注釋”或者是“說明”

          2)Spring和DI(IOC)簡介
            
          在我看來,Spring和Annotation能完成很多相似的工作,它們之間的區(qū)別是在解決問題的位置并不相同。
            這是一個Spring的配置文件的信息,里面定義了三個bean,其中的exampleBean的屬性中引用了其他的bean




          bean="yetAnotherBean"/>

          1



            我們用以下方式調(diào)用

          InputStream is = new FileInputStream("beans.xml");
          XmlBeanFactory factory = new XmlBeanFactory(is);
          Object o = factory.get("exampleBean");

            通過以上方式我們可以得到一個exampleBean的實(shí)例,并且里面的一些屬性已經(jīng)被預(yù)先注入了。
            在Spring結(jié)合了一些動態(tài)代理的以后,我們完全可以實(shí)現(xiàn)Annotation所能做到的許多功能。但是我們可以看出兩種方式是不一樣的,使用Spring面臨著同步問題,維護(hù)配置文件比較的麻煩;而使用Annotation時(shí),我們通常不容易在不改變原代碼的時(shí)候改變一些特性,而且有時(shí)候也面臨著代碼復(fù)用的問題。本文并不討論這些內(nèi)容,本文里Spring只是一個bean的容器,用來存放JavaControl的注冊信息,不會涉及依賴注入,而使用Annotation來完成相應(yīng)的功能。
            注:在本文里使用了兩個配置文件,bean.xml放置了我們測試用的Control信息,在實(shí)際環(huán)境中可能需要不斷添加新的Control。另一個配置文件是config.xml,里面是我們定義的ControlWrapper,每當(dāng)我們增加一種Control的時(shí)候,如DatabaseControl的時(shí)候,就需要添加一個Wrapper。你應(yīng)該首先看看這兩個文件,里面有demo程序的配置以及注冊的Control。

          JavaControl最基本的功能--聲明注入(目錄)
            
          在JavaControl里編程的時(shí)候,我們通常并不會顯式的初始化JavaControl,因?yàn)榫唧w的實(shí)現(xiàn)不應(yīng)該在程序里綁定,而應(yīng)該完全的面向接口編程,我們只是簡單的在聲明Control的地方加一句簡單的注釋:

              /**
              * @common:control
              */
             private myJavaControl.JCSecond jCSecond;   

            然后在我們的Control里就可以直接使用jCSecond,如下: 

              /**
              * @common:operation
              */
             public String serviceA()
             {
                 System.out.println("This is serviceA");
                return "This is serviceA"+":"+jCSecond.serviceB();
             }

            Workshop>平臺會根據(jù)這些注釋,自動生成代碼,來完成初始化jCSecond的動作,來完成注入的工作。而我們的程序會在程序運(yùn)行中讀取注釋信息,來完成注入工作,這與workshop不同。 
            至于這個接口具體要使用哪個實(shí)現(xiàn)類,大家可以看看workshop的打包文件的META-INFjc-jar.xml,里面有所有JavaControl的說明,包括接口和實(shí)現(xiàn),這說明了我們以后可以改變實(shí)現(xiàn)。
            下面我們實(shí)現(xiàn)自己的聲明注入,這里我們需要一個JavaControlFactory類,作為進(jìn)入Control環(huán)境的接口。兩個Annotation,用來說明Control的實(shí)現(xiàn)類和包裝器。JavaControlFactory類來讀取Control中的注釋信息,完成包裝等工作。

          1)首先我們需要定義一個修飾成員變量的Annotation,所有被當(dāng)作Control的成員變量都可以使用這個annotation來說明,完整的定義如下:

          package net.rocksun.tiffany.annotation;
          import java.lang.annotation.*;

          @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)

          @Target({java.lang.annotation.ElementType.FIELD})

          public @interface Control {
                 public String name();
          }

            這是個簡單的annotation,@Retention說明本Annotation要保留到什么時(shí)候,因?yàn)槲覀冃枰诔绦蜻\(yùn)行時(shí)動態(tài)的讀取,所以設(shè)置為RUNTIME。@Target說明了本annotation所針對的對象,是FIELD。

            我們在Control里這樣使用這個annotation:

              @Control(name="second")
             private SecondControl second;  

            name是SecondControl的實(shí)現(xiàn)類名稱,我們在這里說明,然后通過JavaControlFactory來根據(jù)名字自動的將SecondControl實(shí)例化。

          2)我們還需要一個說明類的ControlType,用來注釋所有的Control類:

          package net.rocksun.tiffany.annotation;

          import java.lang.annotation.*;

          @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
          @Target({java.lang.annotation.ElementType.TYPE})
          public @interface ControlType {
             public String name();
          }

            它也是運(yùn)行級別保留,但它針對的對象是類,只有類可以使用這個annotation注釋。name表示這個類需要的包裝器,也就是用來對這個類進(jìn)行特殊處理的類。
            使用方式如下

          .............
          @ControlType(name="DefaultControl")
          public class FirstControlImpl implements FirstControl {
          .............

          @ControlType(name="DefaultControl")說明這個類需要使用DefaultControl類型的包裝器包裝一下。

          3)ControlFactory實(shí)現(xiàn)
            
          我們希望從JavaControl中取得類的方式為FirstControl first = (FirstControl)factory.getControl("first");并且first中所有的標(biāo)記為Control的成員變量都可以被正確的初始化。
            所以我們的ControlFactory首先應(yīng)該遍歷類first的所有成員變量,當(dāng)遇到使用Control注釋的成員變量,根據(jù)它的類型進(jìn)行實(shí)例化。以下是遍歷所有成員變量的過程:

                   for (int i = 0; i < fields.length; i++) {
                     //判斷是否為Control,也就是檢查是否使用@Control注釋
                     if(isControl(fields[i])){
                         Object tempBean = getControlInstance(fields[i]);
                         if(tempBean==null){
                             throw new IllegalArgumentException(bean.getClass()+":"+fields[i].getName()+"’s annotation error");
                         }else{
                             //如果檢查配置沒有問題,就設(shè)置這個值,同時(shí)檢查這個Bean的成員是不是也需要實(shí)例化
                             wrappedBean.setPropertyValue(fields[i].getName(), tempBean);
                             initBean(tempBean);
                         }
                     }
                 }

            需要注意的是,所有的Control應(yīng)該使用JavaBean的方式,所有成員變量應(yīng)該有無參的構(gòu)造方法,并且成員都有g(shù)et和set方法。
            其中isControl方法如下:

              private boolean isControl(Field field){
                 Annotation[] annotation = field.getDeclaredAnnotations();
                 for (int i = 0; i < annotation.length; i++){
                     if(annotation[i] instanceof Control){
                         return true;
                     }
                 }
                 return false;
             }

            我們遍歷這個字段的所有annotation,來檢查有沒有Control,如果有就執(zhí)行g(shù)etControlInstance,來實(shí)例化這個Control。getControlInstance會檢查我們在類上作的ControlType注釋,來選擇包裝器,進(jìn)行bean的包裝。   

          4)在實(shí)例化完成后,根據(jù)類的ControlType Annotation我們可以對類進(jìn)行包裝,每一個包裝類實(shí)現(xiàn)如下接口:

          public interface ControlWrapper {
             public Object wrapBean(Object bean);
          }

            這樣根據(jù)指定的ControlType的不同,我們還可以另外使用我們自定義的Control包裝器,進(jìn)行特殊的操作,我們首先實(shí)現(xiàn)了一個默認(rèn)的DefaultControlType,不作任何操作。
            到目前,我們的JavaControl最基本框架已經(jīng)完成了,我們可以測試結(jié)果,可以看到,我們通過注釋說明,就可以實(shí)現(xiàn)對成員變量的自動初始化,我們通過外部文件的配置就可以在以后方便的修改實(shí)現(xiàn)類。
            可以察看源代碼中ControlFactoryTest的testGetControl的運(yùn)行結(jié)果。

          使用動態(tài)代理來增加橫切(目錄)
            
          對于每一個方法被執(zhí)行的時(shí)候,我們希望可以進(jìn)行一種橫切,如每一個方法執(zhí)行前我們要記一個Log,這就需要添加一個動態(tài)代理,當(dāng)然我們也可以使用其他種方式,但是動態(tài)代理是最優(yōu)雅的。
            我們增加DefaultControlProxy類來處理橫切的動作,DefaultControlProxy是InvocationHandler的子類,實(shí)現(xiàn)了ivoke方法,代碼如下:

              public Object invoke(Object proxy, Method method, Object[] args) throws
                     Throwable {
                 log.info("method "+method.getName()+" invoked");
                 return method.invoke(getDelegate(),args);
             }  

            我們不作額外的操作,只是log被執(zhí)行的方法,這樣,所有的Control方法在執(zhí)行的時(shí)候都會紀(jì)錄log,這里的delegate是我們原來的對象,我們保留這個還有很多用處?! 榱送瓿蛇@些橫切,我們必須修改DefaultControlWrapper,加入代碼如下

          public Object wrapBean(Object bean) {
                 DefaultControlProxy proxy = new DefaultControlProxy();
                 proxy.setDelegate(bean);
                 System.out.println("wrap................"+bean.getClass());
                 return Proxy.newProxyInstance(this.getClass().getClassLoader(),bean.getClass().getInterfaces(),proxy);
          }

            重新運(yùn)行我們的測試,結(jié)果并不影響。只是在每個Control的方法執(zhí)行以前,打印了句Log信息。

          Control方法的權(quán)限檢查(目錄)
            
          workshop可以指定一個方法的角色,我們已經(jīng)有了橫切,所以這步工作也并不困難。首先定義一個Annotation,名字是Role,代碼如下:

          @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
          @Target( {java.lang.annotation.ElementType.METHOD})
          public @interface Role {
             public String name();
          }

            沒有好說的,與前面及唯一的區(qū)別就是對象變成了方法,我們可以這樣聲明來表示這個方法必需要角色admin來參與:

              @Role(name="admin")
             public String doTaskWithRole(){
                 return "admin";
              }

            對于每一個Control必須在建立的時(shí)候,告訴他所擁有的角色,所以修改所有JavaControl的基類BaseControl如下:

              private String role;
             public BaseControl() {
             }

              public void setRole(String role) {
                 this.role = role;
             }

              public String getRole() {
                 return role;
             }

            有了這些修改,我們可以在我們的橫切代碼那里增加檢查權(quán)限的功能

                  Method m = delegate.getClass().getMethod(method.getName(),classes);
                 Annotation[] annotation = m.getAnnotations();
                 for (int i = 0; i < annotation.length; i++) {
                     if(annotation[i] instanceof Role){
                         log.info("Method "+method.getName()+" should check role");
                         if(((BaseControl)getDelegate()).getRole().equals(((Role)annotation[i]).name())){
                         }else{
                             throw  new IllegalAccessException("Method "+method.getName()+" requier Role "+((Role)annotation[i]).name());
                         }
                     }
                 }

            以上代碼就是增加的檢查,我們使用Method m = delegate.getClass().getMethod(method.getName())得到新的Method,因?yàn)閭鬟f給我們的Method對象是代理過的,所以我們必須使用原始的類的定義,當(dāng)出現(xiàn)權(quán)限不足時(shí),一個例外就會拋出。對于我們的測試用例,我們增加testHasRoleCheckFailed()>和testHasRoleCheckSuccess()>用例,分別測試在沒有和有權(quán)限的時(shí)候會不會有例外發(fā)生。

          一個DatabaseControl的特例 (目錄)
            我們可以擴(kuò)展我們的JavaControl了,我們定義一個新的Annotation----DatabaseMethod

          @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
          @Target({java.lang.annotation.ElementType.METHOD})
          public @interface DatabaseMethod {
             public String sql();
             public String dataSource() default "dataSource";
          }

            這個Annotation有兩個成員,其中dataSource()有缺省值,也就是程序里注釋的時(shí)候,可以不輸入dataSource參數(shù)。

          @ControlType(name="DatabaseControl")
          public class DatabaseControl1Impl extends BaseControl implements DatabaseControl1{
             @DatabaseMethod(sql="insert into user values(:0,:1)")
             public void insertUser(int id , String name) {
             }
          }

            這就是一個使用DatabaseControl的例子,首先說明這個Control是一個DatabaseControl,需要使用net.rocksun.tiffany.wrapper.DatabaseControlWrapper來進(jìn)行包裝。再就是對應(yīng)的我們要修改DatabaseControlWrapper類,同時(shí)參照DefaultControl,我們要對DatabaseControlProxy進(jìn)行修改,使之可以處理sql:

          public Object invoke(Object proxy, Method method, Object[] args) throws
                     Throwable {
                 log.info(delegate.getClass()+ "’s method "+method.toString()+" invoked");
                 Class[] classes = null;
                 if(args!=null){
                     classes = new Class[args.length];
                     for (int i = 0; i < args.length; i++) {
                         classes[i] = args[i].getClass();
                         if(args[i] instanceof Integer){
                             classes[i] = Integer.TYPE;
                        }
                     }
                 }


                 Method m = delegate.getClass().getMethod(method.getName(),classes);
                 Annotation[] annotation = m.getAnnotations();
                 for (int i = 0; i < annotation.length; i++) {
                     if(annotation[i] instanceof DatabaseMethod){
                         String sql = ((DatabaseMethod)annotation[i]).sql();
                         String dataSource = ((DatabaseMethod)annotation[i]).dataSource();
                         for (int j = 0; j < args.length; j++) {
                             if(args[j] instanceof Integer){
                                 sql = sql.replace(":"+j,args[j].toString());
                             }else{
                                 sql = sql.replace(":"+j,"’"+args[j].toString()+"’");
                             } 
                        }
                         log.info(" will execute sql ’"+ sql +"’ for dataSource ’"+dataSource+"’");
                     }
                 }
                 return method.invoke(getDelegate(),args);
             }

            這是整個方法的定義,并沒有考慮到所有的類型,也沒有實(shí)際執(zhí)行sql,只是告訴大家我們這個時(shí)候已經(jīng)有了操作數(shù)據(jù)庫的能力了。運(yùn)行測試用例的testInsertUser,我們就可以看到我們把sql處理成可以執(zhí)行的狀態(tài)。

          關(guān)于Demo程序
            
          所有的Demo Control都在net.rocksun.tiffany.demo下,里面有FirstControl,SecondControl,DatabaseControl1三個Control和它們的實(shí)現(xiàn),所有的Control實(shí)現(xiàn)都是BaseControl的子類,BaseControl幫助它的子類保存角色信息。
            FirstControl中有SecondControl和DatabaseControl1的一個實(shí)例,F(xiàn)irstControl的三個方法,分別為doTask,doTaskWithRole,insertUser,其中第一個演示了成員變量的自動注入,第二個包括了權(quán)限檢查,第三個是調(diào)用DatabaseControl控件。
            有了這些,我們就可以使用ControlFactoryTest進(jìn)行測試

          框架如何的加強(qiáng)(目錄)
            作為框架程序,還有許多事情要做。如數(shù)據(jù)庫控件,我們可以添加一種事物控制標(biāo)示,在需要啟動事物的方法前添加一種annotation,方法里所有DatabaseControl使用相同的數(shù)據(jù)庫連接,并且根據(jù)拋出的Exception來選擇提交和會滾。有時(shí)候建立一種通用的控件框架是比較麻煩的,但如果在一個自己寫的框架下,在方法前增加注釋來說明一些額外的操作也是很方便的選擇。
            由于沒有工具,所以使用我們自己的Control框架并不容易,需要自己寫接口,然后是實(shí)現(xiàn),然后是配置信息,而使用workshop則可以只關(guān)心寫實(shí)現(xiàn),接口和配置都交給workshop自動的完成。使用Control最方便的地方就是可以很好的與編程工具結(jié)合,并且可以使代碼更規(guī)范,但如果沒有工具,就比較難說了。
            目前這個框架還有很多問題,注冊新控件太麻煩,包裝程序重復(fù)工作太多,還沒有很好的復(fù)用。再就是BaseControl應(yīng)該更好的包裝,空間聲明應(yīng)該使用新Annotation類型,而不應(yīng)該使用名稱作為參數(shù)的方式,因?yàn)槭褂眯骂愋?,可以在編譯時(shí)檢查,減少錯誤發(fā)生的可能。
            對于頁面流等技術(shù)我們已可以自己實(shí)現(xiàn),但核心思想是一樣的。

          感受
            
          記得看《XDoclet in Action》的時(shí)候,說XDoclet生成配置文件的時(shí)候,有兩種目的,一種是直接可以用,另一種是作為模版,第一種方式更好一些,第二種通常是沒有辦法時(shí)的選擇。Annotation和Spring其實(shí)就是XDoclet的兩種狀態(tài),當(dāng)Annotation是代碼級的時(shí)候,我們可以用工具得到對應(yīng)的Spring配置文件,但是這就意味著,我們可能最好不要直接修改Spring的配置文件,因?yàn)橄乱淮紊删桶堰@些修改覆蓋了,但這樣就失去了通過配置來靈活改變一些設(shè)置的可能性。同樣,如果我們完全依賴自己配置文件,可能會很麻煩。
            但我覺得使用配置雖然麻煩一些,但是確實(shí)是更清楚一些,如果有可能的話應(yīng)該結(jié)合使用,實(shí)現(xiàn)靈活性與簡單性的結(jié)合。
            Annotation,DIIOC)等等,這些東西出現(xiàn)的目的都差不多,都是為了減少這些橫切代碼的重復(fù)工作。我也越來越感覺萬變不離其宗了。JavaControl就是想提供這樣一個環(huán)境,在這個環(huán)境里可以自動的讀取Annotation來完成橫切的工作,這比使用spring確實(shí)方便一些,因?yàn)闆]有這樣一個環(huán)境,Spring只能通過配置實(shí)現(xiàn),這樣的重復(fù)工作也太多。

            本文代碼下載 (請選擇目標(biāo)另存為 tiffany.rar)

          參考文檔
            
          這一次幾乎沒參考什么,只是看了些Annotation的參考,但是忘記了地址。
            如果有就是這個了:oreilly_.java.1.5.tiger.a.developers.notebook.(2004)

          關(guān)于作者:
            
          sdj21,rocksun(dev2dev ID),軟件工程師,
            郵件地址:sdj21@sina.com , daijun@gmail.com ,sundaijun@126.com

          posted on 2005-05-07 14:30 笨笨 閱讀(853) 評論(0)  編輯  收藏 所屬分類: J2EE 、ALL 、Weblogic Portal
          主站蜘蛛池模板: 通河县| 万宁市| 阳信县| 金山区| 宜良县| 河间市| 阿巴嘎旗| 怀仁县| 双鸭山市| 胶州市| 大城县| 中江县| 姜堰市| 静安区| 台湾省| 麻江县| 永春县| 息烽县| 永新县| 沙湾县| 枞阳县| 玉门市| 调兵山市| 汤阴县| 镇宁| 呼和浩特市| 武汉市| 临沧市| 长武县| 班玛县| 西和县| 蓝田县| 衡阳县| 孝感市| 崇义县| 青铜峡市| 双柏县| 佛冈县| 夏津县| 朝阳县| 蓬溪县|