应用程序级方面会影响软件相当多的区域,他们通常是软件的特征,将会影响应用程序中的许多类。本节介绍系统级的横切关注点集合,其中可以使用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();
}