《AspectJ Cookbook》讀書筆記二十: 應用類和組件級方面
一.驗證傳遞給方法的參數
創建一個模塊化參數檢查邏輯的方面。聲明一個切入點,用于捕獲其中將檢查參數的方法的執行。切入點應該把參數展示給相應的通知使得它可以執行檢查。
依賴于參數檢查的結果,通知將繼續執行方法,或者每當參數不合適時都重寫方法。
傳統的參數驗證和檢查代碼:
package com.aspectj;

import java.net.URL;
import java.net.MalformedURLException;

public class TraditionalMainApplication
{
private static final String COMMAND_LINE_USAGE = "MyAppliction usage :\n\n"
+ "\tjava MainApplication <url>";

public static void main(String[] args)
{
if (args.length == 1)
{
try
{
// Assuming that the first argument supplied is a url
// Concentrating on the business logic, not validation which
// is handled by the aspect.
URL url = new URL(args[0]);

System.out.println("Application Started, doing stuff with " + url);
}
catch (MalformedURLException mue)
{
System.err.println(COMMAND_LINE_USAGE);
System.err.println("Please enter a valid URL for <url>");
}
}
else
{
System.err.println(COMMAND_LINE_USAGE);
}
}
}
通過方面的應用,可以改成如下:
package com.aspectj;

import java.net.URL;
import java.net.MalformedURLException;

public aspect VerifyMethodArgsAspect
{
private static final String COMMAND_LINE_USAGE = "MyAppliction usage :\n\n" +
"\tjava MainApplication <url>";
public pointcut captureMain(String[] arguments) :
execution(void MainApplication.main(String[])) && args(arguments);

public pointcut createURLCalledinMainMethod() : call(java.net.URL.new(..)) && withincode(public void MainApplication.main(String[]));
void around(String[] arguments) : captureMain(arguments)
{
if (arguments.length == 1)
{
// Test that the host and port are valid
try
{
URL url = new URL(arguments[0]);
proceed(arguments);
}
catch(MalformedURLException mfe)
{
System.err.println(COMMAND_LINE_USAGE);
System.err.println("Please enter a valid URL for <url>");
}
}
else
{
System.err.println(COMMAND_LINE_USAGE);
}
}
// If necessary soften the exception that would normally have been raised
// if the url parameter was badly formed, but only in the validated main method
declare soft : MalformedURLException : createURLCalledinMainMethod();

}
package com.aspectj;

import java.net.URL;

public class MainApplication
{

public static void main(String[] args)
{
// Assuming that the first argument supplied is a url
// Concentrating on the business logic, not validation which
// is handled by the aspect.
URL url = new URL(args[0]);
System.out.println("Application Started, doing stuff with " + url);
}
}
二. 重寫在構造函數調用上實例化的類
public static void traditionalObjectOrentedImplementationSelection() {
MyInterface myObject = new MyClass() //Specifies the MyClass implementation of the MyInterface interface
System.out.println(myObject);
myObject.foo();
}
以上代碼為了改變MyInterface的實現,必須把代碼改成:
MyIntrface myObject = new AnotherClass();
通過使用AspectJ的call(Signature)切入點和around()通知,可以使得事情更簡單,更整潔。如:
package com.aspectj;

public aspect ControlClassSelectionAspect
{
public pointcut myClassConstructor() : call(MyClass.new());
Object around() : myClassConstructor()
{
return new AnotherClass();
}
// Runtime selection variation
/*
Object around() : myClassConstructor() &&
if (System.getProperty("select_class").equals("AnotherClass"))
{
return new AnotherClass();
}
*/
}
ControlClassSelectionAspect聲明了myClassConstructor()切入點,它會截獲調用,以實例化MyClass對象。相應的around()通知然后會返回重寫AnotherClass類的一個新實例。
進一步,可以檢查用戶定義的運行時參數:
Object aound():myClassConstructor()&&if (System.getProperty("select_class")).equals("AnotherClass") {
return new AnotherClass();
}
三.添加持久性到類中
可以把這個抽象方面擴展成一些特殊化的子方面,他們將會應用程序內的每個持久對象集合實現一個合適的持久性機制。
package com.aspectj;

public abstract aspect PersistenceAspect
{
public interface ObjectStore
{
public void persist();
public void restore();
}
protected abstract pointcut restoreStorage(ObjectStore store);
after(ObjectStore store) : restoreStorage(store)
{
store.restore();
}
protected abstract pointcut persistStorage(ObjectStore store);
after(ObjectStore store) : persistStorage(store)
{
store.persist();
}
}
PersistenceAspect抽象方面把ObjfctStore角色定義為接口,可以將其應用與任何類,來管理對象集合的持久性。restoreStorage(ObjectStore)和persistStorage(ObjectStore)抽象切入點通過特殊化的子方面來實現,用于觸發對應的after()通知塊,它將恢復或保持指定的ObjectStore.
ObjectStore接口中指定的restore()和persist()方法是依據特地ingde持久性策略實現的,該策略用于應用程序內的對象集合,使得可以基于每個ObjectStore來改變持久性策略。
package com.aspectj;

import java.io.*;

public aspect EmployeePersistenceAspect extends PersistenceAspect
{
declare parents : EmployeeCollection implements ObjectStore;

protected pointcut restoreStorage(ObjectStore store) :
execution(EmployeeCollection.new(..)) &&
target(store);
protected pointcut persistStorage(ObjectStore store) :
call(* java.util.List.add(..)) &&
target(EmployeeCollection) &&
target(store);

declare parents : Employee extends Serializable;
private File EmployeeCollection.employeesFile = new File("employees.ser");
public void EmployeeCollection.persist()
{
try
{
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(this.employeesFile));
Object[] objectsToStore = this.toArray();
out.writeObject(objectsToStore);
out.flush();
out.close();
}
catch (Exception e)
{
System.err.println("Couldn't store employees to " + this.employeesFile);
}
}
public void EmployeeCollection.restore()
{
// Check that the serialized accounts file exists
if (this.employeesFile.exists() && this.employeesFile.canRead())
{
try
{
ObjectInput input = new ObjectInputStream(new FileInputStream(this.employeesFile));
Object[] objectsToRestore = (Object[]) input.readObject();
for(int x = 0; x < objectsToRestore.length; x++)
{
this.add(objectsToRestore[x]);
}
input.close();
}
catch (Exception e)
{
System.err.println("Couldn't restore employees due to a corrupt " + this.employeesFile + " file");
e.printStackTrace();
}
}
}
}
EmployeePersistenceAspect將ObjectStore接口應用于EmployeeCollection類。然后實現restoreStorage(ObjectStore)方面,以捕獲何時構造EmployeeCollection,并使用target(TypePattern || Identifier)切入點將EmployeeCollection展示為要存儲的ObjectStore.
實現persistStorage(ObjectStore)切入點,用以捕獲無論何時更改EmployeeCollection,并在此時保持ObjectStore的內容。EmployeeCollection是ArrayList的特殊化;為了避免直接織入到Java標準庫中,使用call(Signature)切入點來捕獲何時在List上調用add(...)方法。
不過,call(Signature)切入點定義本質上過于普通,因此必須將通過persistStorage(ObjectStore)切入點捕獲的連接點限制于只捕獲何時在EmployeeCollection上調用add(...)。為了應用這種限制,第一個target(TypePattern || Identifier)切入點使用TypePattern來指定你只對其目標是EmployeeCollection類的連接點感興趣。第二個target(TypePattern || Identifier)切入點使用一個標識符將當前ObjectStore傳遞給通知塊,作為persistStorage(ObjectStore)切入點的單一參數。
最后,將直觀的對象串行化持久性策略應用于EmployeeCollction。EmployeeCollection中的每個對象都是Employee類的一個實例,并且擴展這個類以實現Serializable接口,使得可對它應用標準的Java對象串行化技術。串行化的Employee對象將存儲在一個文件中;因此,將以employeesFile屬性的形式把這個文件信息添加到EmployeeCollection類中。
為了完成圖形,將把persist()和restore()方法實現添加到EmployeeCollection類中,使得它可以滿足ObjectStore接口所需的行為。這些方法會執行Employee對象的串行化和恢復,這是通過employeesFile屬性指定的文件來進行的。
以下顯示了抽象的PersistenceAspect替代實現的一部分,它只會在關閉應用程序關閉時保持其對應的ObjectStore.
package com.aspectj;

import java.io.*;

public privileged aspect AccountPersistenceAspect extends PersistenceAspect
{
declare parents : MainApplication implements ObjectStore, Runnable;

protected pointcut restoreStorage(ObjectStore store) :
execution(MainApplication.new(..)) &&
target(store);
// Selects all join points where it is necessary to persist the store
protected pointcut persistStorage(ObjectStore store) :
execution(public void MainApplication.run()) &&
this(store);
declare parents : Account extends Serializable;

private File MainApplication.accountsFile = new File("accounts.ser");
after(MainApplication mainApplication) :
restoreStorage(ObjectStore) &&
target(mainApplication)
{
// Register a shutdown hook
Thread shutdownThread = new Thread(mainApplication);
Runtime.getRuntime().addShutdownHook(shutdownThread);
}
public void MainApplication.run()
{
// Do nothing, merely provides the trigger that the shutdown hook has been
// executed so as to persist the store on shutdown.
}
public void MainApplication.persist()
{
try
{
ObjectOutput out = new ObjectOutputStream(
new FileOutputStream(this.accountsFile));
Object[] objectsToStore = this.accounts.toArray();
out.writeObject(objectsToStore);
out.flush();
out.close();
}
catch (Exception e)
{
System.err.println("Couldn't store accounts to " + this.accountsFile);
}
}
public void MainApplication.restore()
{
// Check that the serialized accounts file exists
if (this.accountsFile.exists() && this.accountsFile.canRead())
{
try
{
ObjectInput input = new ObjectInputStream(
new FileInputStream(this.accountsFile));
Object[] objectsToRestore = (Object[]) input.readObject();
for(int x = 0; x < objectsToRestore.length; x++)
{
this.accounts.add(objectsToRestore[x]);
}
input.close();
}
catch (Exception e)
{
System.err.println("Couldn't restore accounts due to a corrupt " + this.accountsFile + " file");
e.printStackTrace();
}
}
}
}
persistStore(ObjectStore)切入點修改成捕獲Runnable的執行,它在MyApplication類上強制執行public void run()方法。然后,AccountPersistenceAspect把必要的run()方法實現添加到MainApplication類中,以滿足Runnable接口的需要,但是,這只會提供一個標記,而不需要任何實現。
增加Runnable接口和MainApplication類上的run()存根方法意味著:可以利用JVM把MainApplication注冊為關閉掛鉤,使得在整個應用程序正常完成時將調用MainApplication.run()方法。在恢復ObjectStore(它是一個MainApplication類)時,通過執行around(MainApplication)通知塊,來完成把MainApplication類注冊為關閉掛鉤的任務。
通過把MainApplication用作關閉掛鉤,當觸發關閉掛鉤時,persistStoreage(ObjectStore)切入點將觸發MainApplication對象的保持操作。因此,一旦干凈利索地關閉應用程序,就會保持MainApplication中存儲的Account類的集合。
四. 應用模擬組件支持單元測試
package com.aspectj;

import com.thirdparty.ThirdPartyComponentFactory;
import com.thirdparty.ThirdPartyComponentInterface;

public class MyComponent implements MyComponentInterface
{
private ThirdPartyComponentInterface thirdPartyComponent;
public MyComponent()
{
this.thirdPartyComponent = ThirdPartyComponentFactory.getThirdPartyComponent();
System.out.println("Component found " + thirdPartyComponent);
}
public void foo()
{
System.out.println("Inside MyComponent.foo()");
this.thirdPartyComponent.bar();
}
}
package test.com.aspectj;

import com.aspectj.ThirdPartyComponentInterface;

public class MockThirdPartyComponent implements ThirdPartyComponentInterface {

/* (non-Javadoc)
* @see com.thirdparty.ThirdPartyComponentInterface#bar()
*/
public void bar()
{
System.out.println("Inside MockThirdPartyComponent.bar()");

}
}
package test.com.aspectj;

import com.aspectj.*;

public aspect MockThirdPartyComponentAspect
{
public pointcut catchThirdPartyConstructor() :
call(ThirdPartyComponentInterface ThirdPartyComponentFactory.
getThirdPartyComponent());
Object around() : catchThirdPartyConstructor()
{
return new MockThirdPartyComponent();
}
}
創建一個模塊化參數檢查邏輯的方面。聲明一個切入點,用于捕獲其中將檢查參數的方法的執行。切入點應該把參數展示給相應的通知使得它可以執行檢查。
依賴于參數檢查的結果,通知將繼續執行方法,或者每當參數不合適時都重寫方法。
傳統的參數驗證和檢查代碼:



































通過方面的應用,可以改成如下:




























































二. 重寫在構造函數調用上實例化的類





以上代碼為了改變MyInterface的實現,必須把代碼改成:
MyIntrface myObject = new AnotherClass();
通過使用AspectJ的call(Signature)切入點和around()通知,可以使得事情更簡單,更整潔。如:




















進一步,可以檢查用戶定義的運行時參數:
Object aound():myClassConstructor()&&if (System.getProperty("select_class")).equals("AnotherClass") {
return new AnotherClass();
}
三.添加持久性到類中
可以把這個抽象方面擴展成一些特殊化的子方面,他們將會應用程序內的每個持久對象集合實現一個合適的持久性機制。

























PersistenceAspect抽象方面把ObjfctStore角色定義為接口,可以將其應用與任何類,來管理對象集合的持久性。restoreStorage(ObjectStore)和persistStorage(ObjectStore)抽象切入點通過特殊化的子方面來實現,用于觸發對應的after()通知塊,它將恢復或保持指定的ObjectStore.
ObjectStore接口中指定的restore()和persist()方法是依據特地ingde持久性策略實現的,該策略用于應用程序內的對象集合,使得可以基于每個ObjectStore來改變持久性策略。































































EmployeePersistenceAspect將ObjectStore接口應用于EmployeeCollection類。然后實現restoreStorage(ObjectStore)方面,以捕獲何時構造EmployeeCollection,并使用target(TypePattern || Identifier)切入點將EmployeeCollection展示為要存儲的ObjectStore.
實現persistStorage(ObjectStore)切入點,用以捕獲無論何時更改EmployeeCollection,并在此時保持ObjectStore的內容。EmployeeCollection是ArrayList的特殊化;為了避免直接織入到Java標準庫中,使用call(Signature)切入點來捕獲何時在List上調用add(...)方法。
不過,call(Signature)切入點定義本質上過于普通,因此必須將通過persistStorage(ObjectStore)切入點捕獲的連接點限制于只捕獲何時在EmployeeCollection上調用add(...)。為了應用這種限制,第一個target(TypePattern || Identifier)切入點使用TypePattern來指定你只對其目標是EmployeeCollection類的連接點感興趣。第二個target(TypePattern || Identifier)切入點使用一個標識符將當前ObjectStore傳遞給通知塊,作為persistStorage(ObjectStore)切入點的單一參數。
最后,將直觀的對象串行化持久性策略應用于EmployeeCollction。EmployeeCollection中的每個對象都是Employee類的一個實例,并且擴展這個類以實現Serializable接口,使得可對它應用標準的Java對象串行化技術。串行化的Employee對象將存儲在一個文件中;因此,將以employeesFile屬性的形式把這個文件信息添加到EmployeeCollection類中。
為了完成圖形,將把persist()和restore()方法實現添加到EmployeeCollection類中,使得它可以滿足ObjectStore接口所需的行為。這些方法會執行Employee對象的串行化和恢復,這是通過employeesFile屬性指定的文件來進行的。
以下顯示了抽象的PersistenceAspect替代實現的一部分,它只會在關閉應用程序關閉時保持其對應的ObjectStore.

















































































persistStore(ObjectStore)切入點修改成捕獲Runnable的執行,它在MyApplication類上強制執行public void run()方法。然后,AccountPersistenceAspect把必要的run()方法實現添加到MainApplication類中,以滿足Runnable接口的需要,但是,這只會提供一個標記,而不需要任何實現。
增加Runnable接口和MainApplication類上的run()存根方法意味著:可以利用JVM把MainApplication注冊為關閉掛鉤,使得在整個應用程序正常完成時將調用MainApplication.run()方法。在恢復ObjectStore(它是一個MainApplication類)時,通過執行around(MainApplication)通知塊,來完成把MainApplication類注冊為關閉掛鉤的任務。
通過把MainApplication用作關閉掛鉤,當觸發關閉掛鉤時,persistStoreage(ObjectStore)切入點將觸發MainApplication對象的保持操作。因此,一旦干凈利索地關閉應用程序,就會保持MainApplication中存儲的Account類的集合。
四. 應用模擬組件支持單元測試
創建組件所依賴的外部組件的模擬實現。創建一個方面,應用模擬組件實現來代替真實的組件。單元測試完成時,使用單獨的AspectJ構建配置文件裝換出測試文件,使得可以再次使用真實的實現。
以下為一種典型情況,MyComponent是要測試的組件,并且它具有與ThirdPartyComponentInterface的外部實現的依賴性。ThirdPartyComponentInterface的真實實現是通過調用工廠方法ThirdPartyFactory.getThirdPartyComponent()來獲得的。





















為了孤立地對MyComponent運行單元測試,將需要通過在測試中包括真實的外部組件,重寫ThirdPartyComponent實現,從而不會混淆測試結果。一種策略是:手動應用重寫真實組件實現的模擬組件。如:















以上方法如果用在組件較多的接口,可能難以管理。使用面向方面的替代方法,可以創建一個方面,用于截獲ThirdPartyComponent的創建,并用模擬對象實現重寫返回的對象。















可以通過創建兩個不同的AspectJ構建配置文件,來區分真實實現和模擬實現。
posted on 2008-08-29 11:18 Brian 閱讀(341) 評論(0) 編輯 收藏 所屬分類: 《AspectJ Cookbook》讀書筆記