應用程序級方面會影響軟件相當多的區域,他們通常是軟件的特征,將會影響應用程序中的許多類。本節介紹系統級的橫切關注點集合,其中可以使用AspectJ更好地實現它們的特征。本節可以分成兩類:被動方面和主動方面。
被動方面是截獲器或觀察期器,或者是應用程序的邏輯,并且不會以明顯的方式影響或反饋進這個邏輯中。被動方面的關鍵特征之一是:它通常只包含before()和after()通知,如果使用around()通知,它將總是調用process()方法。
主動方面會以各種方式影響所應用的應用程序,如更改通向軟件的邏輯路徑。主動方面通常會包含around()通知,它不能調用process()方法,但是可以重寫作為原始業務邏輯一部分的觸發連接點。
一.應用面向方面的跟蹤
package com.aspectj;

import org.aspectj.lang.JoinPoint;

public abstract aspect TracingAspect


{
public abstract pointcut pointsToBeTraced();
public abstract pointcut pointsToBeExcluded();
public pointcut filteredPointsToBeTraced(Object caller) :
pointsToBeTraced() &&
!pointsToBeExcluded() &&
!within(com.oreilly.aspectjcookbook.tracing.TracingAspect+) &&
this(caller);
public pointcut catchStaticCallers() :
pointsToBeTraced() &&
!pointsToBeExcluded() &&
!within(com.oreilly.aspectjcookbook.tracing.TracingAspect+) &&
!filteredPointsToBeTraced(Object);
before(Object caller) : filteredPointsToBeTraced(caller)

{
traceBefore(thisJoinPoint, caller);
}
before() : catchStaticCallers()

{
traceStaticBefore(thisJoinPoint);
}
after(Object caller) : filteredPointsToBeTraced(caller)

{
traceAfter(thisJoinPoint, caller);
}
after() : catchStaticCallers()

{
traceStaticAfter(thisJoinPoint);
}
protected void traceBefore(JoinPoint joinPoint, Object caller)

{
System.out.println(caller + " calling " +
joinPoint.getSignature() + " @ " +
joinPoint.getSourceLocation());
}
protected void traceStaticBefore(JoinPoint joinPoint)

{
System.out.println("Static code calling " +
joinPoint.getSignature() + " @ " +
joinPoint.getSourceLocation());
}
protected void traceAfter(JoinPoint joinPoint, Object caller)

{
System.out.println("Returning from call to" +
joinPoint.getSignature() + " @ " +
joinPoint.getSourceLocation());
}
protected void traceStaticAfter(JoinPoint joinPoint)

{
System.out.println("Returning from static call to " +
joinPoint.getSignature() + " @ " +
joinPoint.getSourceLocation());
}
private static aspect FormatCallDepthAspect

{
private static int callDepth;
private pointcut captureTraceBefore() : call(protected void TracingAspect.trace*Before(..));
private pointcut captureTraceAfter() : call(protected void TracingAspect.trace*After(..));
after() : captureTraceBefore()

{
callDepth++;
}
before() : captureTraceAfter()

{
callDepth--;
}
private pointcut captureMessageOutput(String message) :
call(* *.println(String)) &&
args(message) &&
within(TracingAspect) &&
!within(FormatCallDepthAspect);
Object around(String originalMessage) : captureMessageOutput(originalMessage)

{
StringBuffer buffer = new StringBuffer();
for (int x = 0; x < callDepth; x++)

{
buffer.append(" ");
}
buffer.append(originalMessage);
return proceed(buffer.toString());
}
}
}

TracingAspect具有兩個抽象切入點,他們允許特殊化的子方面指定要跟蹤的目標應用程序區域。
然后,filteredPointsToBeTraced(Object)切入點把pointToBeTraced()和pointsToBeExcluded()切入點與邏輯組合起來,用于排除TracingAspect本身,以及展示觸發跟蹤的調用對象。組合的切入點會捕獲具有調用對象的目標應用程序內要跟蹤的所有連接點。不幸的是,由于使用了this(TypePattern | Identifier)切入點,將從靜態代碼塊中出現的那些連接點中排除filteredPointsToBeTraced(Object)切入點。
catchStaticCallers()切入點通過捕獲要包括在跟蹤中但不會被filteredPointsToBeTraced(Object)切入點捕獲的所有連接點,來解決這個人問題。
兩組before()和after()通知用于把跟蹤消息輸出到System.out。其中一組用于在調用對象可用時執行消息輸出;另外一組則用于在調用對象不可用時做同樣的事情。
如果遺漏了TracingAspect,跟蹤消息將稍微有點難以閱讀。通過依據當前調用深度縮進每條消息,適當地格式化跟蹤消息將是有用的。這被證明是一個方面級橫切關注點,因為需要用合適的格式化邏輯來影響方面中的所有通知塊。
FormatCallDepth內部方面可以滿足跨TracingAspect方面的所有跟蹤消息的格式化要求。
package com.aspectj;

public aspect ApplicationSpecificTracingAspect extends TracingAspect


{
public pointcut pointsToBeTraced() : call(* *.*(..));
public pointcut pointsToBeExcluded() : call(void java.io.PrintStream.*(..));
}
二.應用面向方面的日志記錄
package com.aspectj;

import org.aspectj.lang.JoinPoint;

public abstract aspect LoggingAspect extends TracingAspect


{
protected abstract pointcut exceptionsToBeLogged();

private pointcut filteredExceptionCapture() :
exceptionsToBeLogged() &&
!pointsToBeExcluded();

before() : filteredExceptionCapture()

{
logException(thisJoinPoint);
}
protected abstract void logException(JoinPoint joinPoint);
}
LoggingAspect方面繼承了TracingAspect的所有行為,并添加了一些特定于日志記錄的新功能。提供了exceptionToBeLogged()抽象切入點,使得特殊化的子方面可以指定要在其中記錄了異常信息的連接點。logException(JoinPoint)抽象方法允許子方面實現在記錄異常時將發生的準確行為。
在實現exceptionsToBeLogged()抽象切入點時,handler(TypePattern)切入點是要在特殊化的子方面中使用的最合適的切入點聲明。不過,無法把exceptionToBeLogged()切入點限制于只用于handler(TypePattern)切入點定義。
然后,filteredExceptionCapture()切入點可以吧exceptionsToBeLogged()切入點與繼承自TracingAspect的pointcutsToBeExcluded()切入點組合起來,使得從日志記錄中繼續排除被聲明為要排除的任何連接點。
package com.aspectj;

import org.aspectj.lang.JoinPoint;

public aspect ApplicationLoggingAspect extends LoggingAspect


{
public pointcut pointsToBeTraced() : call(* *.*(..));
public pointcut pointsToBeExcluded() : call(* java.io.*.*(..));
public pointcut exceptionsToBeLogged() : handler(com.oreilly.aspectjcookbook.PackageA.BusinessException);
protected void traceBefore(JoinPoint joinPoint, Object caller)

{
System.out.println("Log Message: Called " + joinPoint.getSignature());
}
protected void traceStaticBefore(JoinPoint joinPoint)

{
System.out.println("Log Message: Statically Called " + joinPoint.getSignature());
}
protected void traceAfter(JoinPoint joinPoint, Object object)

{
System.out.println("Log Message: Returned from " + joinPoint.getSignature());
}
protected void traceStaticAfter(JoinPoint joinPoint)

{
System.out.println("Log Message: Returned from static call to " + joinPoint.getSignature());
}
protected void logException(JoinPoint joinPoint)

{
System.out.println("Log Message: " + joinPoint.getArgs()[0] + " exception thrown");
}
private static aspect FormatCallDepthAspect

{
private static int callDepth;
private pointcut captureTraceBefore() : call(protected void TracingAspect.trace*Before(..));
private pointcut captureTraceAfter() : call(protected void TracingAspect.trace*After(..));
after() : captureTraceBefore()

{
callDepth++;
}
before() : captureTraceAfter()

{
callDepth--;
}
private pointcut captureMessageOutput(String message) :
call(* *.println(String)) &&
args(message) &&
within(ApplicationLoggingAspect) &&
!within(FormatCallDepthAspect);
Object around(String originalMessage) : captureMessageOutput(originalMessage)

{
StringBuffer buffer = new StringBuffer();
for (int x = 0; x < callDepth; x++)

{
buffer.append(" ");
}
buffer.append(originalMessage);
return proceed(buffer.toString());
}
}
}

ApplicationLoggingAspect方面提供了pointsToBeTraced()和pointsToBeExcluded()切入點的實現,用于指定要記錄的目標應用程序的區域,以及要從記錄中排除的區域。新的異常日志記錄切入點exceptionsToBeLogged()被實現用于滿足LoggingAspect的要求。
出于方便性考慮,在ApplicationLoggingAspect中包括了FormatCallDepth內部方面,使得在通過System.out輸出日志記錄消息時,他們易于閱讀。
LoggingAspect抽象方面支持多個子方面,他們同時以不同方式潛在地記錄應用程序的各個部分。如:
package com.aspectj;

import org.aspectj.lang.JoinPoint;

public aspect PackageSpecificLoggingAspect extends LoggingAspect


{
// Ensures that the System level logging is applied first, then the more specific package level
// Reference previous chapters recipes
declare precedence : ApplicationLoggingAspect, PackageSpecificLoggingAspect;
// Selects calls to all methods in the application
public pointcut pointsToBeTraced() : call(* com.oreilly.aspectjcookbook.PackageA.*.*(..));
// Protects against calls to System.out
public pointcut pointsToBeExcluded() : call(void java.io.PrintStream.*(..));
// Selects calls to all methods in the application
public pointcut exceptionsToBeLogged() : handler(PackageA.*);
protected void traceBefore(JoinPoint joinPoint, Object object)

{
System.out.println("<before>" + joinPoint.getSignature() + "</before>");
}
protected void traceStaticBefore(JoinPoint joinPoint)

{
System.out.println("<before type=\"static\">" + joinPoint.getSignature() + "</before>");
}
protected void traceAfter(JoinPoint joinPoint, Object object)

{
System.out.println("<after>" + joinPoint.getSignature() + "</after>");
}
protected void traceStaticAfter(JoinPoint joinPoint)

{
System.out.println("<after type=\"static\">" + joinPoint.getSignature() + "</after>");
}
protected void logException(JoinPoint joinPoint)

{
System.out.println("<exception>" + joinPoint.getSignature() + "</exception>");
}
private static aspect FormatCallDepthAspect

{
private static int callDepth;
private pointcut captureTraceBefore() : call(protected void TracingAspect.trace*Before(..));
private pointcut captureTraceAfter() : call(protected void TracingAspect.trace*After(..));
after() : captureTraceBefore()

{
callDepth++;
}
before() : captureTraceAfter()

{
callDepth--;
}
private pointcut captureMessageOutput(String message) :
call(* *.println(String)) &&
args(message) &&
within(PackageSpecificLoggingAspect) &&
!within(FormatCallDepthAspect);
Object around(String originalMessage) : captureMessageOutput(originalMessage)

{
StringBuffer buffer = new StringBuffer();
for (int x = 0; x < callDepth; x++)

{
buffer.append(" ");
}
buffer.append(originalMessage);
return proceed(buffer.toString());
}
}
}

三.應用延遲加載
package com.aspectj;

public abstract aspect LazyLoading extends DelegatingProxyPattern


{
public interface RealComponent extends Subject

{
}
public interface LazyProxy extends RealComponent

{
public RealComponent getRealComponent() throws LazyLoadingException;
}
public abstract LazyProxy initializeComponent(Object configuration);
}
LazyLoading方面反過來繼承自代理模式的實現,它重點關注的是代理模式委托特征,如:
package com.aspectj;

import org.aspectj.lang.JoinPoint;

public abstract aspect DelegatingProxyPattern
extends ProxyPattern


{
protected boolean reject(
Object caller,
Subject subject,
JoinPoint joinPoint)

{
return false;
}

protected boolean delegate(
Object caller,
Subject subject,
JoinPoint joinPoint)

{
return true;
}

protected Object rejectRequest(
Object caller,
Subject subject,
JoinPoint joinPoint)

{
return null;
}
}

最后,創建LazyLoading方面的特殊化子方面,他們將為目標應用程序的特定組件實現延遲加載行為。
延遲加載涉及將類的加載和實例化延遲到剛好使用實例之前的那一刻。延遲加載的目標是:通過在需要某個對象的那一刻加載和實例化它,根據需要使用內存資源。
LazyLoading方面用于截獲對根據需要延遲加載的類的調用。
RealComponent接口是在LazyLoading方面中的聲明的,通過針對要延遲加載的目標應用程序中的任何類的特殊化子方面來應用它。需要一個代理對象來存儲信息,在需要時,可以利用這種信息實例化真實的類,以及通過LazyProxy接口提供這個角色。
LazyProxy接口提供了足夠的功能,用于處理真實的組件,而不必加載它們。LazyProxy定義了單個方法,在需要時可以調用它來加載真實的組件。
最后,LazyLoading抽象方面定義了initializeComponent(Object)抽象方法,通過子方面來實現它,用于實例化延遲代理來代替真實的組件。
package com.aspectj;

import org.aspectj.lang.JoinPoint;

public aspect LazyFeatureLoading extends LazyLoading


{
public LazyProxy initializeComponent(Object object)

{
LazyProxy proxy =
new LazyFeatureProxy((String) object);
return proxy;
}
protected pointcut requestTriggered() :
call(* com.oreilly.aspectjcookbook.features.Feature.* (..)) &&
!within(com.oreilly.aspectjcookbook.oopatterns.ProxyPattern+);
protected Object delegateRequest(
Object caller,
Subject subject,
JoinPoint joinPoint)

{
if (subject instanceof LazyFeatureProxy)

{
LazyFeatureProxy feature =
(LazyFeatureProxy) subject;
try

{
Feature implementedFeature =
(Feature) feature.getRealComponent();
implementedFeature.doSomething(
(String) joinPoint.getArgs()[0]);
}
catch (LazyLoadingException lle)

{
lle.printStackTrace();
lle.getOriginalException().printStackTrace();
System.out.println(
"Exception when attempting to "
+ "lazy load"
+ " a particular class,"
+ " aborting the call");
}
}
else

{
((Feature) subject).doSomething(
(String) joinPoint.getArgs()[0]);
}
return null;
}
declare parents : Feature implements RealComponent;
declare parents : LazyProxy implements Feature;
private class LazyFeatureProxy implements Feature, LazyProxy

{
private Object configuration;
private Feature delegate;

public LazyFeatureProxy(Object configuration)

{
this.configuration = configuration;
}
public synchronized RealComponent getRealComponent()
throws LazyLoadingException

{
if (this.configuration instanceof String)

{
try

{
if (this.delegate == null)

{
return this.delegate =
(Feature) Class
.forName((String) this.configuration)
.newInstance();
}
else

{
return this.delegate;
}
}
catch (Exception e)

{
throw new LazyLoadingException("Exception raised when loading real component", e);
}
}
else

{
throw new LazyLoadingException("Error in configuration");
}
}
public void doSomething(String message)

{
}
}
}

LazyFeatureLoading方面封裝了如何延遲加載實現Feature接口的類類型。兩個declare parent語句指定:實現Feature接口的任何類都滿足RealComponent角色,并且LazyProxy可以代替Feature來履行其為任何Feature對象提供一個代理的角色。
initializeComponent(Object)方法返回一個LazyProxy實例,它是用必要的配置建立的,用以加載真實的組件。無論何時目標應用程序決定延遲加載特定的實例,都會調用InitializeComponent(Object)方法。
抽象的DelegatingProxyPattern需要requestTriggered(..)切入點和delegateRequest(..)方法。requestTriggered(..)切入點用于捕獲對實現Feature接口的類上方法的所有調用。當方面調用在LazyProxy上調用的方法時,將提供額外的保護來阻止LazyFeatureLoading方面通知它自身。
delegateRequest(..)方法會檢查調用的主題,以查看它是否是LazyfeatureProxy類的一個實例。如果是,就會調用getRealComponent()方法來加載真實的組件。然后,將把方法調用轉發給真實的組件。
最后,LazyFeatureLoading方面定義了LazyFeatureProxy類。這個類包含調用一個方法來加載相應的真實Feature實現所需的所有信息。通過LazyProxy接口所需的getRealComponent()方法來執行真實Feature實現的加載。
package com.aspectj;


public class MainApplication


{
private Feature[] features;

public MainApplication()

{
features = new Feature[2];
features[0] =
LazyFeatureLoading
.aspectOf()
.initializeComponent(
"com.oreilly.aspectjcookbook.features.FeatureA");

features[1] =
LazyFeatureLoading
.aspectOf()
.initializeComponent(
"com.oreilly.aspectjcookbook.features.FeatureB");
features[0].doSomething("Hello there");
features[0].doSomething("Hello again");

features[1].doSomething("Hi to you too");
features[1].doSomething("Hi again");
}

public static void main(String[] args)

{
MainApplication mainApplication =
new MainApplication();
}
}

四.管理應用程序屬性
Java應用程序屬性傳統上是從單件類加載的,并由其管理。它的一個示例是System.getProperty()方法,它從命令行利用-D選項返回提供給應用程序的屬性。
不幸的是,單件傾向于是一種脆弱的解決方案,它導致應用程序的許多區域依賴于對單件的接口。如果單件的接口發生變化,則應用程序的許多相關區域不得不做改動,以納入新的接口。這是方面可以解決的一類橫切關注點。
在針對屬性管理的傳統方法中,單件屬性管理器是應用程序中的被動參與者,并且對來自應用程序多個部分的屬性信息請求作出響應。屬性管理器對將利用它所提供的屬性信息做什么或者它會達到哪里一無所知,并且在更新任何屬性時都依賴于通知。
利用針對系統屬性的面向方面的方法,就變換了看問題的視角。AspectJ提供的機制允許設計屬性管理器,使得它可以主動把屬性應用于那些需要它們的應用程序區域。關于將屬性部署在何處的所有信息都包含在方面內,因此,如果需要新的屬性,那么只要方面會發生變化。
屬性管理方面將應用程序的余下部分與關于如何加載、存儲和提供屬性的任何考慮事項隔離開。不再需要把過分簡單化的名、值形式的接口綁定到屬性上,因為接口不再存在。方面會加載屬性,在需要的地方應用它們,以及在應用程序關閉時把它們存儲起來。
使用方面管理屬性的最后一個優點是:由于屬性管理方面很可能是一個特權方面,可以設置應用程序中對應于它所管理屬性的變量,所以如果需要,它可以監視這些變量的任何變化,以將這些變化反映回它說管理的屬性中。這意味著方面可以加載、供應和存儲屬性,并且可以監視屬性的變化,因此在屬性發生變化時,根本不需要應用程序通知屬性管理器。

package com.aspectj;

import java.util.Properties;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public privileged aspect MyApplicationProperties


{
// Default property values
private static final String PROPERTY_FILE_SYSTEM_PROPERTY = "props";
private static final String DEFAULT_PROPERTIES_FILENAME = "myapplication.properties";
private static final String MYCLASS_PROPERTY_NAME = "com.oreilly.aspectjcookbook.MyClass.property";
private static final String MAINAPPLICATION_PROPERTY_NAME = "com.oreilly.aspectjcookbook.MainApplication.property";
private static final int DEFAULT_MAINAPPLICATION_PROPERTY = 1;
private static final String DEFAULT_MYCLASS_PROPERTY = "Property Initialized:";
// In this example the properties are stored simply as Java properties
// In a properties file
Properties applicationProperties = new Properties();
File propertiesFile;
// Load Properties
public MyApplicationProperties()

{
// Note. Aspects are initialized even before the MainApplication.
try

{
String propertyFilename = System.getProperty(PROPERTY_FILE_SYSTEM_PROPERTY);
if (propertyFilename != null)

{
propertiesFile = new File(propertyFilename);
}
else

{
propertiesFile = new File(DEFAULT_PROPERTIES_FILENAME);
}
FileInputStream inputStream = new FileInputStream(propertiesFile);
applicationProperties.load(inputStream);
inputStream.close();
}
catch (Exception e)

{
// Just using default properties instead.
System.err.println("Unable to load properties file, reverting to default values");
}
}
// Supply Properties
public pointcut mainApplicationInitialization() : staticinitialization(MainApplication);
after() : mainApplicationInitialization()

{
try

{
int mainApplicationProperty = new Integer(applicationProperties.getProperty(MAINAPPLICATION_PROPERTY_NAME)).intValue();
MainApplication.property = mainApplicationProperty;
}
catch (Exception e)

{
MainApplication.property = DEFAULT_MAINAPPLICATION_PROPERTY;
applicationProperties.setProperty(MAINAPPLICATION_PROPERTY_NAME, new Integer(DEFAULT_MAINAPPLICATION_PROPERTY).toString());
}
}
public pointcut myClassObjectCreation(MyClass myObject) : execution(public MyClass.new(..)) && this(myObject);
before(MyClass myObject) : myClassObjectCreation(myObject)

{
String myClassProperty = applicationProperties.getProperty(MYCLASS_PROPERTY_NAME);
if (myClassProperty != null)

{
myObject.property = myClassProperty;
}
else

{
myObject.property = DEFAULT_MYCLASS_PROPERTY;
applicationProperties.setProperty(MYCLASS_PROPERTY_NAME, DEFAULT_MYCLASS_PROPERTY);
}
}
// Monitoring properties
public pointcut monitorMainApplicationProperty(int newValue) : set(int MainApplication.property) && args(newValue);
after(int newValue) : monitorMainApplicationProperty(newValue) && !within(MyApplicationProperties)

{
System.out.println("MainApplication.property changed to: " + newValue);
applicationProperties.setProperty(MAINAPPLICATION_PROPERTY_NAME, new Integer(newValue).toString());
// Does not update other instances of the class, just works like a setProperty method invocation.
}
public pointcut monitorMyClassProperty(String newValue) : set(String MyClass.property) && args(newValue);
after(String newValue) : monitorMyClassProperty(newValue) && !within(MyApplicationProperties)

{
System.out.println("MyClass.property changed to: " + newValue);
applicationProperties.setProperty(MYCLASS_PROPERTY_NAME, newValue);
// Does not update other instances of the class, just works like a setProperty method invocation.
}
// Store properties
class ShutdownMonitor implements Runnable

{
public ShutdownMonitor()

{
// Register a shutdown hook
Thread shutdownThread = new Thread(this);
Runtime.getRuntime().addShutdownHook(shutdownThread);

}
public void run()

{
try

{
FileOutputStream outputStream = new FileOutputStream(propertiesFile);
applicationProperties.store(outputStream, "---Properties for the AO Property Manager Example---");
outputStream.close();
}
catch (Exception e)

{
System.err.println("Unable to save properties file, will use default on next run");
}
}
}
private ShutdownMonitor shutdownMonitor = new ShutdownMonitor();
}
