【永恒的瞬間】
          ?Give me hapy ?
          面向切面(AOP)"與"面向?qū)ο?OOP)"
          (原來是按照原作者的格式分了4篇,現(xiàn)在整理了一下.合并為一篇.?--譯者注)

          首先你要明確的一點,AOP和OOP是兩種不同的認識事物的角度,并不是說有了AOP就不要用OOP.AOP所關(guān)注的是傳統(tǒng)OOP不能優(yōu)雅解決的問題.(程序員通常都是完美主義者,當(dāng)解決某個問題不優(yōu)雅的時候,那就意味著不完美.)下面將就一個簡單的例子來說明他們到底如何的不同.

          作為一個使用OOP多年的人來說,當(dāng)我聽說AOP可以解決一些OOP一直都不能優(yōu)雅地解決的問題時,我覺得應(yīng)該去探個究竟了.對兩種技術(shù)的比較最能給我們實際應(yīng)用提供見解.這里我設(shè)計了一個例子:一個OOP應(yīng)用,其中某些方面適合使用AOP.

          本文展示了一個簡單的例子.一開始介紹了問題域,然后分別給出OOP與AOP的解決方案.后者使用了?JDK5.0,JUnit,和AspectWerkz.最后說明如何編寫代碼.讀完本文后,我希望你能知道AOP到底是什么,解決什么樣的問題.(由于作者在后面AOP的例子中使用了Java5.0的批注(Annotation),建議讀者先有所了解.?--?譯者注).

          問題域描述
          一個軟件公司雇傭一個程序員,指定給他一個業(yè)務(wù)部門并要求他隨時向經(jīng)理報告.當(dāng)團隊成員完成他們的目標(biāo)時,經(jīng)理會給他們相應(yīng)的獎金.公司所需要的方案必須能夠增加一個新的雇員并給當(dāng)前的員工增加獎金.為了方便,我們用CSV文件存儲數(shù)據(jù).


          圖1?解決方案模型

          類Manager(經(jīng)理)繼承自類Employee,包含一個額外的屬性,Managing?Project.一個部門可能包含很多員工.多個部門構(gòu)成了公司.暫不考慮公司這樣的一個類,因為它在問題域之外.

          解決方案設(shè)計
          以下流程圖描述了解決方案設(shè)計.


          圖2?對象之間的交互(增加一個新的員工,指派給他一個部門和經(jīng)理)

          出于簡單的考慮,本文只關(guān)注必需的細節(jié).當(dāng)然你也可以深入代碼得到你想要的其他信息.
          [link]http://www.devx.com/assets/sourcecode/13172.zip[/link]
          EmployeeServiceTestCase,一個JUnit測試用例,模擬一個最終用戶,創(chuàng)建新員工記錄,指派部門和經(jīng)理.它獲取所有可用的部門和經(jīng)理數(shù)據(jù)并顯示在圖形界面上.為了實例化域?qū)ο驜usinessUnit和Manager,獲得的記錄將傳遞給工廠類.之后,通過給EmployeeService傳遞一個引用來創(chuàng)建一個Employee對象.這個服務(wù)類使用EmployeeFactory創(chuàng)建對象,并把這個對象傳給EmployeeRepository?來進行持久化操作.

          應(yīng)用程序中需要面向哪些"切面"
          到目前為止,對模型和設(shè)計的討論還限于一個較抽象的層面.現(xiàn)在,我轉(zhuǎn)向這個應(yīng)用的其他方面?-?這對理解AOP的價值至關(guān)重要.

          操作所需的資源
          1. public?static?Set?findAllBusinessUnits()?throws?RepositoryException?{
          2. Set?businessUnits?=?new?HashSet();
          3. try?{
          4. FileReader?businessUnitFile?=?null;
          5. BufferedReader?bufferedBusinessUnitFile?=?null;
          6. try?{
          7. businessUnitFile?=?new?FileReader(FILE_NAME);
          8. bufferedBusinessUnitFile?=?new?BufferedReader(businessUnitFile);
          9. String?businessUnitRecord;
          10. while((businessUnitRecord?=?bufferedBusinessUnitFile.readLine())?!=?null)?{
          11. BusinessUnit?businessUnit?=?BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
          12. businessUnits.add(businessUnit);
          13. }
          14. }?finally?{
          15. if(bufferedBusinessUnitFile?!=?null)?{
          16. bufferedBusinessUnitFile.close();
          17. }
          18. if(businessUnitFile?!=?null)?{
          19. businessUnitFile.close();
          20. }
          21. }
          22. }?catch(IOException?ioe)?{
          23. String?message?=?"IOError.?Unable?to?find?Business?Unit?records";
          24. logger.log(SEVERE,?message,?ioe);
          25. throw?new?RepositoryException(message,?ioe);
          26. }
          27. logger.log(INFO,?"Manager?Records?returned:"?+?businessUnits.size());
          28. return?businessUnits;
          29. }

          上面的代碼通過FileReader和BUfferedReader來讀取CSV文件中的業(yè)務(wù)數(shù)據(jù).
          應(yīng)用程序重復(fù)地從資源文件中取得數(shù)據(jù)然后在操作完成后釋放.我們會發(fā)現(xiàn):去掉程序的這兩個"切面"將提高代碼的可讀性并達到一個更好的設(shè)計,因為去掉這些"多余"的東西,剩下的代碼才是這個方法真正的精髓.這個方法的作用是讀取業(yè)務(wù)單位數(shù)據(jù).所以不應(yīng)該也不需要去知道"如何獲取和釋放資源以及這個過程中出現(xiàn)的異常"這個"切面".同樣地,使用AOP處理異常也變得不同.(后面將詳細介紹)

          持久層
          傳統(tǒng)的OOP使用倉庫類(repository?classes)來打理應(yīng)用程序的持久層.即:?
          1. public?class?EmployeeRepository?{
          2. public?static?void?createEmployee(Employee?employee)?throws?RepositoryException?{
          3. //使用print?writer把數(shù)據(jù)放入csv文件
          4. }
          5. public?static?String?findEmployeeRecordById(String?id)?throws?RepositoryException?{
          6. //使用file?reader來獲得指定id的員工數(shù)據(jù)
          7. }
          8. public?static?Employee?findEmployeeById(String?id)?throws?RepositoryException?{
          9. //使用該方法獲取員工數(shù)據(jù),Employee對象由工廠類創(chuàng)建
          10. }
          11. public?static?void?updateEmployee(Employee?employee)?{
          12. //更新員工數(shù)據(jù)
          13. }
          14. }

          類EmployeeService?使用一個倉庫類給應(yīng)用中相關(guān)雇員提供服務(wù),在一個企業(yè)應(yīng)用中,從域模型(domain?model)中去掉持久層代碼是一種設(shè)計上的改進.模型設(shè)計者和程序員就可以關(guān)注各自的業(yè)務(wù)邏輯和持久層處理.后面你將會看到如何通過AOP來達到這樣的效果.

          日志
          刪除用于調(diào)試的日志代碼將會極大地改進代碼的可讀性.考慮下面的代碼片斷:
          1. public?Employee?createEmployee(String?name,
          2. String?contactNumber,
          3. BusinessUnit?businessUnit,
          4. Manager?manager)
          5. throws?EmployeeServiceException?{
          6. String?id?=?createNewEmployeeId();
          7. Employee?employee?=
          8. EmployeeFactory.createEmployee(id,?name,?contactNumber,?businessUnit,?manager);
          9. try?{
          10. EmployeeRepository.createEmployee(employee);
          11. }?catch(RepositoryException?re)?{
          12. String?message?=?"Created?employee?successfully:"?+?employee;
          13. logger.log(SEVERE,?message);
          14. throw?new?EmployeeServiceException(message,?re);
          15. }
          16. logger.log(INFO,?"Created?employee?successfully:"?+?employee);
          17. return?employee;
          18. }

          上面的代碼里包含了一個致命錯誤和一個成功信息.輸出日志這一"切面"同樣可以移到業(yè)務(wù)模型外獨立實現(xiàn).

          異常處理
          異常處理的例子我這里不再贅述,但這節(jié)已經(jīng)通過上面的代碼討論了潛在的問題.當(dāng)你調(diào)用EmployeeRepository?對象的createEmployee?方法時,你可能會得到一個RepositoryException異常.傳統(tǒng)的解決方法是,在這個類中處理.另一種方法是,當(dāng)RepositoryException?異常被拋出時createEmployee?方法返回null,catch塊中的其他邏輯可以在類外處理這一錯誤.
          錯誤處理在不同的情況中也會不同.但是,通過AOP可以區(qū)分開每種情況.


          圖3

          圖3中描述了AOP方法的設(shè)計以及在一個更抽象的層次上類間的交互.你可以通過對比圖1和圖3來更好地理解AOP.
          程序的目的是通過BusinessUnit對象讀取CSV文件中的記錄然后?填入類BusinessUnitService?中的map.使用AOP來填充這個map有點類似后門(backdoor)方法?--?控制被委派給BusinessUnit?來讀取存儲介質(zhì)中的記錄.

          AOP就是定義一些切入點(pointcut)和處理方法(advice).一個"切入點"是源代碼中一個執(zhí)行點.前面的例子定義了一個"切入點"?--??類BusinessUnitService中的findBusinessUnits方法.一個"處理方法"顧名思義就是當(dāng)執(zhí)行到某個"切入點"時的一塊代碼.類BusinessUnitPersistentAspect?包括advice方法findAllBusinessUnits,該方法從存儲介質(zhì)中載入數(shù)據(jù),然后使用工廠類創(chuàng)建BusinessUnit?對象.然后這個對象被加入map,map對象的引用通過BusinessUnitService?對象獲得."切入點"和"處理方法"組成了所謂的"切面(Aspect)"

          為了讀取存儲介質(zhì)中的數(shù)據(jù),OOP方法通過一個DAO類來做.而AOP中,你只要定義一個"切入點"和相應(yīng)的"處理方法"來讀取數(shù)據(jù).AOP框架會以advice的形式注入代碼,既可以在執(zhí)行期也可以在編譯期.

          總而言之,當(dāng)類BusinessUnitService?中的findAllBusinessUnits?方法被調(diào)用時,AOP框架會在"切入點"處注入處理方法,通過BusinessUnit?對象預(yù)先讀取數(shù)據(jù)來填充map對象.這樣,持久層方面的代碼就可以移到業(yè)務(wù)代碼之外了.

          新方法里的"切面"

          本節(jié)討論如何用AOP為應(yīng)用程序的各個"切面"建模

          操作資源

          類BusinessUnitPersistenceAspect?的持久方法使用了一個buffered?reader.你甚至可以定義"切面"的"切面",但為了簡單,這里只關(guān)注類的查找方法.
          1. @Aspect("perJVM")
          2. public?class?BufferedFileReaderAspect?{
          3. @Expression("execution(*?org.javatechnocrats.aop.withaop.aspects.BusinessUnitPersistenceAspect.find*(..))")
          4. Pointcut?businessUnitPersistenceAspect;
          5. //?其他"切入點"定義
          6. @Expression("businessUnitPersistenceAspect?||
          7. employeePersistenceAspect?||
          8. managerPersistenceAspect")
          9. Pointcut?allPersistencePointcuts;
          10. private?Map<Class,?String>?fileNames;
          11. public?BufferedFileReaderAspect()?{
          12. System.out.println("BufferedFileReaderAspect?created");
          13. fileNames?=?new?HashMap<Class,?String>();
          14. fillFileNames();
          15. }
          16. @Before("allPersistencePointcuts")
          17. public?void?assignReader(JoinPoint?joinPoint)?throws?Throwable?{
          18. System.out.println("assignReader?advice?called");
          19. Object?callee?=?joinPoint.getCallee();
          20. IBufferedFileReaderConsumable?bufReaderConsumable?=?(IBufferedFileReaderConsumable)callee;
          21. Class?persistenceClass?=?callee.getClass();
          22. String?fileName?=?fileNames.get(persistenceClass);
          23. FileReader?fileReader?=?new?FileReader(fileName);
          24. BufferedReader?bufferedReader?=?new?BufferedReader(fileReader);
          25. bufReaderConsumable.setBufferedReader(bufferedReader);
          26. }
          27. @AfterFinally("allPersistencePointcuts")
          28. public?void?releaseReader(JoinPoint?joinPoint)?throws?Throwable?{
          29. //釋放buffered?reader等資源
          30. }
          31. //其他方法
          32. }


          上面的代碼試圖為每一個方法創(chuàng)建"切入點"--?所有以find開頭的方法.無論何時這些方法被調(diào)用,assignReader方法都會被提前執(zhí)行.這里它獲取被調(diào)用的類實例然后設(shè)置新建的buffered?reader.

          同樣地,在releaseReader?方法里,代碼會預(yù)先關(guān)閉buffered?reader集合.本節(jié)只解釋@before和@
          AfterFinally?這兩個"切入點".(以J2SE?5.0的標(biāo)記定義).另外,你也可以在方面定義的xml文件中聲明他們.你可以查看例程源代碼中的aop.xml文件.

          下載

          持久化

          前面提到,OOP方法使用BusinessUnit?來為應(yīng)用的持久層填充Map.在下面的高亮代碼中(@before一行,以及while循環(huán)代碼?-?譯者注),當(dāng)BusinessUnitService?中的方法findAllBusinessUnits?被調(diào)用時"處理方法"findAllBusinessUnits?也將被調(diào)用.
          1. @Aspect("perJVM")
          2. public?class?BusinessUnitPersistenceAspect?implements?IBufferedFileReaderConsumable?{
          3. private?BufferedReader?buffFileReader;
          4. @Before("execution(Collection?org.javatechnocrats.aop.withaop.BusinessUnitService.findAllBusinessUnits())")
          5. public?void?findAllBusinessUnits(JoinPoint?joinPoint)?throws?Throwable?{
          6. System.out.println("findAllBusinessUnits?advice?called");
          7. Map<String,?BusinessUnit>?businessUnits?=
          8. ((BusinessUnitService)joinPoint.getThis()).getBusinessUnits();
          9. String?businessUnitRecord;
          10. while((businessUnitRecord?=?buffFileReader.readLine())?!=?null)?{
          11. BusinessUnit?businessUnit?=?BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
          12. businessUnits.put(businessUnit.getId(),?businessUnit);
          13. }
          14. }
          15. public?void?setBufferedReader(BufferedReader?buffFileReader)?{
          16. System.out.println("BusinessUnitPersistenceAspect.setBufferedReader?called");
          17. this.buffFileReader?=?buffFileReader;
          18. }
          19. public?BufferedReader?getBufferedReader()?{
          20. System.out.println("BusinessUnitPersistenceAspect.getBufferedReader?called");
          21. return?this.buffFileReader;
          22. }
          23. }

          "處理方法"從數(shù)據(jù)存儲中讀取記錄,使用工廠類創(chuàng)建一個BusinessUnit實例.然后這個實例被加入到Map.該Map掌管程序的所有持久化"切面".

          日志
          本文中的例子沒有包含一個完整的日志AOP解決方案.但是,它為java.lang.Object類的toString方法定義了一個"切入點"來獲取類的調(diào)試信息.因此,域中的類不需要實現(xiàn)toString方法.通??赡苣憧赡苄枰獮槊恳粋€類都要實現(xiàn)這個方法.

          1. @Aspect("perJVM")
          2. public?class?LoggingAspect?{
          3. @Around("execution(String?org.javatechnocrats.aop.withaop..*.toString())")
          4. public?Object?toStringAdvice(JoinPoint?joinPoint)?throws?Throwable?{
          5. System.out.println("toStringAdvice?called");
          6. String?toString?=?(String)joinPoint.proceed();
          7. Object?target?=?joinPoint.getThis();
          8. Field?fields[]?=?target.getClass().getDeclaredFields();
          9. List?members?=?new?ArrayList(fields.length?+?1);
          10. members.add(toString);
          11. for(Field?field?:?fields)?{
          12. field.setAccessible(true);
          13. Object?member?=?field.get(target);
          14. members.add(field.getName()?+?"="?+?member);
          15. }
          16. return?members.toString();
          17. }

          你也可以用這個樣例代碼完成錯誤處理"切面".

          深入源代碼

          為了理解樣例需求的OOP設(shè)計,請參看源代碼并思考以下幾個問題:?下載

          *?首先分析oldway包中EmployeeServiceTestCase?類中的代碼
          *查看testEmployeeCredit?方法
          *搞懂業(yè)務(wù)類Employee和BusinessUnit
          *學(xué)習(xí)?service,repository和factory概念.這些是業(yè)務(wù)驅(qū)動設(shè)計的主要概念.
          *更深入地理解oldway包中的service,repository和factory類

          而AOP地理解則應(yīng)該是:
          *分析newway包中EmployeeServiceTestCase?類
          *查看service,repository和factory類,基本和前一種差不多.只是你要讓"處理方法"截獲程序的執(zhí)行流程.
          *研究aspect類學(xué)習(xí)"切入點"的定義

          要執(zhí)行程序,你需要做的工作:
          *?下載AspectWerkz?2.0?http://aspectwerkz.codehaus.org/
          *設(shè)置以下的環(huán)境變量:
          set?JAVA_HOME=c:\Program?Files\Java\jdk1.5.0
          set?ASPECTWERKZ_HOME=C:\aw_2_0_2
          set?PATH=%PATH%;%ASPECTWERKZ_HOME%\bin
          set?CLASSPATH=
          C:\aw_2_0_2\lib\aspectwerkz-2.0.RC2.jar;C:\aw_2_0_2\lib\aspectwerkz-jdk5-2.0.RC2.jar;?classes;C:\?junit\3.8.1\resources\lib\junit.jar
          *解壓縮源代碼和其他文件
          *編譯Java文件,但不要編譯測試用例否則你調(diào)試時會遇到一個錯誤.
          *進行離線調(diào)試.假設(shè)你把文件解壓縮到c:\aop?,類文件解壓到c:\aop\classes,在c:\aop目錄下執(zhí)行以下命令:
          %ASPECTWERKZ_HOME%\bin\aspectwerkz?-offline?etc/aop.xml?-cp?classes?classes
          *AOP框架會修改類來注入必要的字節(jié)碼
          *編譯測試用例,使用JUnit運行它.

          后記
          當(dāng)你完成了上面的這些工作,你應(yīng)該有以下的領(lǐng)悟:
          *程序中的交叉關(guān)聯(lián)
          *關(guān)于AOP中深入源代碼

          為了理解樣例需求的OOP設(shè)計,請參看源代碼并思考以下幾個問題:?下載

          *?首先分析oldway包中EmployeeServiceTestCase?類中的代碼
          *查看testEmployeeCredit?方法
          *搞懂業(yè)務(wù)類Employee和BusinessUnit
          *學(xué)習(xí)?service,repository和factory概念.這些是業(yè)務(wù)驅(qū)動設(shè)計的主要概念.
          *更深入地理解oldway包中的service,repository和factory類

          而AOP地理解則應(yīng)該是:
          *分析newway包中EmployeeServiceTestCase?類
          *查看service,repository和factory類,基本和前一種差不多.只是你要讓advice截取程序的流程.
          *研究aspect類學(xué)習(xí)point?cut的定義

          要執(zhí)行程序,你需要做的工作:
          *?下載AspectWerkz?2.0?http://aspectwerkz.codehaus.org/
          *設(shè)置以下的環(huán)境變量:
          set?JAVA_HOME=c:\Program?Files\Java\jdk1.5.0
          set?ASPECTWERKZ_HOME=C:\aw_2_0_2
          set?PATH=%PATH%;%ASPECTWERKZ_HOME%\bin
          set?CLASSPATH=
          C:\aw_2_0_2\lib\aspectwerkz-2.0.RC2.jar;C:\aw_2_0_2\lib\aspectwerkz-jdk5-2.0.RC2.jar;?classes;C:\?junit\3.8.1\resources\lib\junit.jar
          *解壓縮源代碼和其他文件
          *編譯Java文件,但不要編譯測試用例否則你調(diào)試時會遇到一個錯誤.
          *進行離線調(diào)試.假設(shè)你把文件解壓縮到c:\aop?,類文件解壓到c:\aop\classes,在c:\aop目錄下執(zhí)行以下命令:
          %ASPECTWERKZ_HOME%\bin\aspectwerkz?-offline?etc/aop.xml?-cp?classes?classes
          *AOP框架會修改類來注入必要的字節(jié)碼
          *編譯測試用例,使用JUnit運行它.

          后記
          當(dāng)你完成了上面的這些工作,你應(yīng)該有以下的領(lǐng)悟:
          *程序中的交叉關(guān)聯(lián)
          *關(guān)于AOP中"切面"的含義
          *如何用AOP來把程序業(yè)務(wù)層中的交叉關(guān)聯(lián)分離出來,使用"切入點"和"處理方法"
          *OOP和AOP時在程序控制流上的不同

          從本文你應(yīng)該也得到一種看待實際開發(fā)的新視角.你應(yīng)該有信心使用AOP來改進項目中的設(shè)計,建模,提高代碼的重用性.至少,你可以開始使用AOP來處理日志,錯誤和持久化.

          個人覺得,AOP的學(xué)習(xí)曲線相對較陡,尤其在理解定義"切入點"的句法時.理想的情況是,使用OOP來設(shè)計業(yè)務(wù)模型,使用AOP把業(yè)務(wù)模型中的交叉關(guān)聯(lián)移出,從而使代碼簡潔并提高可讀性.

          AOP的一個缺點是會使調(diào)試變得困難,因為不同于OOP,程序流變的復(fù)雜了,交互是由編譯期或執(zhí)行期決定.我準(zhǔn)備將來做一些自動化工具來解決這個問題.

          posted on 2007-02-02 19:51 ???MengChuChen 閱讀(186) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 临邑县| 茂名市| 泰来县| 铜川市| 永德县| 隆安县| 嵊泗县| 昭通市| 上犹县| 奎屯市| 濮阳县| 通许县| 信丰县| 闽侯县| 彭水| 巴青县| 贵州省| 南平市| 阆中市| 巨野县| 综艺| 无为县| 临猗县| 类乌齐县| 德江县| 邓州市| 顺平县| 仁化县| 山西省| 莲花县| 和田市| 大同市| 海阳市| 剑阁县| 宕昌县| 秦皇岛市| 萍乡市| 兴山县| 东阿县| 丰顺县| 汝阳县|