© Copyright 2002 Palo Alto Research Center, Incorporated. All rights reserved.
Version 1.1alpha1
This is the first release of AspectJ 1.1. It includes a small number of new language features as well as major improvements to the functionality of the tools.
This document is intended to completely describe the 1.1 language and tools in relation to AspectJ 1.0.6. With that in mind, many features are documented sketchily with the flag "not working in 1.1alpha1", but we believe it to include some description of all the features of 1.1 that are different from those in 1.0.6.
The full documentation package for the 1.1 release is not yet ready, so this document goes into a bit more detail than usual changes documents. All descriptions here should be read as changes to the The AspectJ 1.0.6 documentation.
This document begins with short descriptions of changes in
followed by details of many of the changes.
AspectJ 1.1 is a slightly different language than AspectJ 1.0. In all but a few cases, programs written in AspectJ 1.0 should compile correctly in AspectJ 1.1. In many cases, there are new or preferred forms in AspectJ 1.1. However, some AspectJ 1.0 features are not yet implemented in 1.1, so many 1.0 programs will not compile in this release.
Most changes to the language are additions to expressibility requested by our users:
Some are have different behavior in edge cases but offer improved power and clarity:
But in order to support weaving into bytecode effectively, several incompatible changes had to be made to the language:
Finally, there are several AspectJ-1.0 language features that are unimplemented in 1.1alpha1 because we didn't have the time. These features should all be available in 1.1beta1.
The compiler for AspectJ 1.1 is different than the compiler for AspectJ 1.0. While this document describes the differences in the compiler, it's worthwhile noting that much effort has been made to make sure that the interface to ajc 1.1 is, as much as possible, the same as the interface to ajc 1.0. There are two important changes under the hood, however.
First, The 1.1 compiler is implemented on top of IBM's open-source Eclipse compiler. This has two benefits: It allows us to concentrate on the AspectJ extensions to Java and let the Eclipse team worry about making sure the Java edge cases work, and it allows us to piggyback on Eclipse's already mature incremental compilation facilities.
Second, ajc now cleanly deliniates compilation of source code from assembly (or "weaving") of bytecode. The compiler still accepts source code, but internally it transforms it into bytecode format before weaving.
This new architecture, and other changes to the compiler, allows us to implement some features that were defined in the AspectJ 1.0 language but not implementable in the 1.1 compiler (after returning advice on handler join points will probably be one of those features, when handler join points are finished). It also makes some new features available:
Some other features we plan to support for 1.1, but did not make it into this alpha release:
But some features of the 1.0 compiler are not supported in the 1.1 compiler:
A short description of the options ajc accepts is available with "ajc -help".
This release includes two new experimental Ant variants, a CompilerAdapter to support running ajc with the Javac task by setting the build.compiler property and a task that supports incremental mode by watching a file.
This release does not include ajdoc, the documentation tool for AspectJ sources.
AspectJ 1.1 will not include ajdb, the AspectJ stand-alone debugger. It is no longer necessary because more third-party debuggers are starting to work according to JSR 45, "Debugging support for other languages", which we plan to support in AspectJ 1.1.
This release has minor additions to the runtime library classes. As with any release, you should compile and run with the runtime library that came with your compiler, and you may run with a later version of the library without recompiling your code.
In one instance, however, they behave differently this release. Because the
AspectJ 1.1 compiler does its weaving through bytecode, column numbers of source
locations are not available. Therefore,
thisJoinPoint.getSourceLocation().getColumn()
is deprecated and
will always return 0.
AJDE for JBuilder, AJDE for Netbeans, and AJDE for Emacs and the AJBrowser have not changed much. They use the batch-build mode of the new compiler. The alpha compiler does not yet produce crosscutting structure for the model so there are no inline annotations in JBuilder or Emacs support and you only see a declaration tree in the structure views. Incremental building and bytecode weaving are not available in the AJDE tools in the alpha release, but will be in a future release.
Strangely enough, the alpha release of AspectJ 1.1 does not provide support for AJDE for Eclipse: Though we are using eclipse compiler technology in ajc, and we expect tight integration with the Eclipse, we just haven't done it yet.
The AspectJ tools sources come in two downloads. The first part is covered under the Mozilla Public Licence, and consists of most of the code for AspectJ 1.1. The second part is covered under the Common Public Licence, and consists of patch code into the Eclipse compiler.
More information about the sources can be found in their respective README files.
Type patterns may now be used to pick out methods and constructors based on their throws clauses. This allows the following two kinds of extremely wildcarded pointcuts:
pointcut throwsMathlike(): // each call to a method with a throws clause containing at least // one exception exception with "Math" in its name. call(* *(..) throws *..*Math*); pointcut doesNotThrowMathlike(): // each call to a method with a throws clause containing no // exceptions with "Math" in its name. call(* *(..) throws !*..*Math*);
The longwinded rules are that a method or constructor pattern can have a "throws clause pattern". Throws clause patterns look like:
ThrowsClausePattern: ThrowsClausePatternItem ("," ThrowsClausePatternItem)* ThrowsClausePatternItem: ["!"] TypeNamePattern
A ThrowsClausePattern matches the ThrowsClause of any code member signature. To match, each ThrowsClausePatternItem must match the throws clause of the member in question. If any item doesn't match, then the whole pattern doesn't match. This rule is unchanged from AspectJ 1.0.
If a ThrowsClausePatternItem begins with "!", then it matches a particular throws clause if and only if none of the types named in the throws clause is matched by the TypeNamePattern.
If a ThrowsClausePatternItem does not begin with "!", then it matches a throws clause if and only if any of the types named in the throws clause is matched by the TypeNamePattern.
These rules are completely backwards compatible with AspectJ 1.0. The rule for "!" matching has one potentially surprising property, in that the two PCDs shown below will have different matching rules.
[1] call(* *(..) throws !IOException) [2] call(* *(..) throws (!IOException)) void m() throws RuntimeException, IOException {}
[1] will NOT match the method m(), because method m's throws clause declares that it throws IOException. [2] WILL match the method m(), because method m's throws clause declares the it throws some exception which does not match IOException, i.e. RuntimeException.
AspectJ 1.0 does not provide kinded pointcut designators for two (rarely used) join points: preinitialization (the code that runs before a super constructor call is made) and advice execution. AspectJ 1.1 does not change the meaning of the join points, but will provide two new pointcut designators to pick out these join points, thus making join points and pointcut designators more parallel.
These new designators are not yet available in AspectJ 1.1alpha1.
We're strongly considering adding a pertype aspect kind to 1.1. This is somewhat motivated by the new restrictions on inter-type declarations. This is also motivated by many previous request to support a common logging idiom. Here's what pertype would look like:
/** One instance of this aspect will be created for each class, * interface or aspect in the com.bigboxco packages. */ aspect Logger pertype(com.bigboxco..*) { /* This field holds a logger for the class. */ Log log; /* This advice will run for every public execution defined by * a type for which a Logger aspect has been created, i.e. * any type in com.bigboxco..* */ before(): execution(public * *(..)) { log.enterMethod(thisJoinPoint.getSignature().getName()); } /* We can use a special constructor to initialize the log field */ public Logger(Class myType) { this.log = new Log(myType); } } /** External code could use aspectOf to get at the log, i.e. */ Log l = Logger.aspectOf(com.bigboxco.Foo.class).log;
The one open question that I see is how this should interact with inner types. If a pertype aspect is created for an outer type should advice in that aspect run for join points in inner types? That is the behavior of the most common uses of this idiom.
Much effort was made in AspectJ 1.0 to be able to advise as many call join points as possible. When the code for the call was passed to the compiler, ajc would allow call pointcuts could pick out the corresponding call join point. When only the code for the called method or constructor was passed to the compiler, however, ajc would allow only certain call pointcuts to pick out the corresponding join point, using an implementation technique called "callee-side call join points".
Because this was dependent on an implementation technique, it was highlighted in the implementation limitations section of the AspectJ documentation. The AspectJ 1.1 must make different implementation decisions, and so will pick out different callee-side call join points.
For this release, however, the question is moot: No handling of callee-side call join points are implemented for AspectJ 1.1alpha1.
Intertype declarations (once called "introductions") in AspectJ 1.1 can only have one target type. So the following code intended to declare that there is a void doStuff() method on all subtypes of Target is not legal AspectJ 1.1 code.
aspect A { public void Target+.doStuff() { ... } }
The functionality of "multi-intertype declarations" can be recovered by using a helper interface.
aspect A { private interface MyTarget {} declare parents: Target+ implements MyTarget; public void MyTarget.doStuff() { ... } }
We believe this is better style in AspectJ 1.0 as well, as it makes clear the static type of "this" inside the method body.
The one piece of functionality that can not be easily recovered is the ability to add static fields to many classes. We believe that the pertype proposal provides this functionality in a much more usable form.
AspectJ 1.1 does not consider initializer execution as a principled join point. The collection of initializer code (the code that sets fields with initializers and the code in non-static initializer blocks) is something that makes sense only in Java source code, not in Java bytecode.
The code generated by the initializers in Java source code now runs inside of constructor execution join points. This changes how before advice runs on constructor execution join points. Consider:
class C { C() { } String id = "identifier"; // this assignment // has to happen sometime } aspect A { before(C c) this(c) && execution(C.new()) { System.out.println(c.id.length()); } }
In AspectJ 1.0, this will print "10", since id is assigned its initial value prior to the before advice's execution. However, in AspectJ 1.1, this will throw a NullPointerExcception, since "id" does not have a value prior to the before advice's execution.
Note that the various flavors of after returning advice are unchanged in this respect in AspectJ 1.1. Also note that this only matters for the execution of constructors that call a super-constructor. Execution of constructors that call a this-constructor are the same in AspectJ 1.1 as in AspectJ 1.0.
We believe this difference should be minimal to real programs, since programmers using before advice on constructor execution must always assume incomplete object initialization, since the constructor has not yet run.
Becuase of the guarantees made (and not made) by the Java classfile format, there are cases where AspectJ 1.1 cannot guarantee that the within pointcut designator will pick out all code that was originally within the source code of a certain type.
The non-guarantee applies to code inside of anonymous and local types inside member types. While the within pointcut designator behaves exactly as it did in AspectJ 1.0 when given a package-level type (like C, below), if given a member-type (like C.InsideC, below), it is not guaranteed to capture code in contained local and anonymous types. For example:
class C { Thread t; class InsideC { void setupOuterThread() { t = new Thread( new Runnable() { public void run() { // join points with code here // might not be captured by // within(C.InsideC), but are // captured by within(C) System.out.println("hi"); } }); } } }
We believe the non-guarantee is small, and we haven't verified that it is a problem in practice.
The withincode pointcut has similar issues to those described above for within. We still need to do some hard thinking about how anonymous and local classes can be handled by this pointcut. alpha: This pointcut is unimplementd in the alpha1 release.
The pointcut designators this, target and args specify a dynamic test on their argument. These tests can not be performed on type patterns with wildcards in them. The following code that compiled under 1.0 will be an error in AspectJ-1.1:
pointcut oneOfMine(): this(com.bigboxco..*);
The only way to implement this kind of matching in a modular way would be to use the reflection API at runtime on the Class of the object. This would have a very high performance cost and possible security issues. There are two good work-arounds. If you control the source or bytecode to the type you want to match then you can use declare parents, i.e.:
private interface OneOfMine {} declare parents: com.bigboxco..* implements OneOfMine; pointcut oneOfMine(): this(OneOfMine);
If you want the more dynamic matching and are willing to pay for the performance, then you should use the Java reflection API combinded with if. That would look something like:
pointcut oneOfMine(): this(Object) && if(classMatches("com.bigboxco..*", thisJoinPoint.getTarget().getClass())); static boolean classMatches(String pattern, Class _class) { if (patternMatches(pattern, _class.getName())) return true; ... }
Note: wildcard type matching still works in all other pcds that match based on static types. So, you can use 'within(com.bigboxco..*+)' to match any code lexically within one of your classes or a subtype thereof. This is often a good choice.
The Java .class file format contains information about the source file and line numbers of its contents; however, it has not information about source columns. As a result, we can not effectively support the accesss of column information in the reflection API. So, any calls to thisJoinPoint.getSourceLocation().getColumn() will be marked as deprecated by the compiler, and will always return 0.
AspectJ 1.1 has a new declare form:
declare dominates ":" TypePatternList ";"
This is used to declare advice ordering constraints on join points. For example, the constraints that (1) aspects that have Security as part of their name should dominate all other aspects, and (2) the Logging aspect (and any aspect that extends it) should dominate all non-security aspects, can be expressed by:
declare dominates: *..*Security*, Logging+, *;
In the TypePatternList, the wildcard * means "any type not matched by another type in the declare dominates".
It is an error for any aspect to be matched by more than one TypePattern in a single decare dominates, so:
declare dominates: A, B, A ; // error
However, multiple declare dominates forms may legally have this kind of circularity. For example, each of these declare dominates is perfectly legal:
declare dominates: B, A; declare dominates: A, B;
And a system in which both constraints are active may also be legal, so long as advice from A and B don't share a join point. So this is an idiom that can be used to enforce that A and B are strongly independent.
Consider the following library aspects:
abstract aspect Logging { abstract pointcut logged(); before(): logged() { System.err.println("thisJoinPoint: " + thisJoinPoint); } } aspect aspect MyProfiling { abstract pointcut profiled(); Object around(): profiled() { long beforeTime = System.currentTimeMillis(); try { return proceed(); } finally { long afterTime = System.currentTimeMillis(); addToProfile(thisJoinPointStaticPart, afterTime - beforeTime); } } abstract void addToProfile( org.aspectj.JoinPoint.StaticPart jp, long elapsed); }
In order to use either aspect, they must be extended with concrete aspects, say, MyLogging and MyProfiling. In AspectJ 1.0, it was not possible to express that Logging's advice (when concerned with the concrete aspect MyLogging) dominated Profiling's advice (when concerned with the concrete aspect MyProfiling) without adding a dominates clause to Logging itself. In AspectJ 1.1, we can express that constraint with a simple:
declare dominates: MyLogging, MyProfiling;
By default, advice in a sub-aspect has more precedence than advice in a super-aspect. One use of the AspectJ 1.0 dominates form was to change this precedence:
abstract aspect SuperA dominates SubA { pointcut foo(): ... ; before(): foo() { // in AspectJ 1.0, runs before the advice in SubA // because of the dominates clause } } aspect SubA extends SuperA { before(): foo() { // in AspectJ 1.0, runs after the advice in SuperA // because of the dominates clause } }
This no longer works in AspectJ 1.1, since declare dominates only matters for concrete aspects. Thus, if you want to regain this kind of precedence change, you will need to refactor your aspects.
All of these language features should be implemented in 1.1beta1.
The AspectJ 1.1 compiler now accepts a -sourceroots option used to pass all .java files in particular directories to the compiler. It takes either a single directory name, or a list of directory names separated with the CLASSPATH separator character (":" for various Unices, ";" for various Windows).
So, if you have your project separated into a gui module and a base module, each of which is stored in a directory tree, you might use one of
ajc -sourceroots /myProject/gui:/myProject/base ajc -sourceroots d:\myProject\gui;d:\myProject\base
This option may be used in conjunction with lst files, listing .java files on the command line, and the -injars option.
ALPHA: In the alpha release of the compiler, only one directory can be passed to the -sourceroots option.
The AspectJ 1.1 compiler now accepts an -injars option used to pass all .class files in a particular jar file to the compiler. It takes either a single directory name, or a list of directory names separated with the CLASSPATH separator character (":" for various Unices, ";" for various Windows).
So, if MyTracing.java defines a trace aspect that you want to apply to all the classes in myBase.jar and myGui.jar, you would use one of:
ajc -injars /bin/myBase.jar:/bin/myGui.jar MyTracing.java ajc -injars d:\bin\myBase.jar;d:\bin\myGui.jar MyTracing.java
The class files in the input jars must not have had advice woven into them, since AspectJ enforces the requirement that advice is woven into a particular classfile only once. So if the classfiles in the jar file are to be created with the ajc compiler (as opposed to a pure Java compiler), they should not be compiled with any non-abstract aspects.
This option may be used in conjunction with lst files, listing .java files on the command line, and the -sourceroots option.
The -outjar option takes the name of a jar file into which the results of the compilation should be put. For example:
ajc -injars myBase.jar MyTracing.java -outjar myTracedBase.jar
No meta information is placed in the output jar file
The AspectJ 1.1 compiler now supports incremental compilation. For the final release, this will work from the various IDE plugins we ship, but for the Alpha release, it is only supported on the command-line compiler.
When ajc is called with the -incremental option, it must also be passed a -sourceroots option specifying a directory to incrementally compile. Once the initial compile is done, ajc waits for console input. Every time it reads a new line, (i.e., every time the user hits return) ajc recompiles those input files that need recompiling.
some changes to classes should force re-weaving, but are not noticed
inter-type declarations, declare parents are not tracked correctly
The final version of the AspectJ 1.1 compiler will have an option to suppress weaving, but to generate classfiles and that can be passed to ajc again (through the -injars option) to generate final, woven classfiles. This option didn't make it into the alpha release, however.
When aspects are compiled into classfiles, they include all information necessary for the ajc compiler to weave their advice and deal with their inter-type declarations. The 1.1alpha1 implementation does not properly deal with some necessary parts of these "library aspects" (inter-type declarations, in particular), but the final version of the AspectJ 1.1 compiler will have the ability to selectively apply library aspects.
In the alpha release of the compiler, around advice was implemented in the safest way for all uses of around advice. However, many (if not most) case of around advice do not involve, for example, inner classes capturing proceed, and can (and will) be implemented in more efficient styles.
The 1.0 implementation of AspectJ, when given:
class MyRunnable implements Runnable { public void run() { ... } } aspect A { call(): (void run()) && target(MyRunnable) { // do something here } }
would cause A's advice to execute even when, say, java.lang.Thread called run() on a MyRunnable instance. The alpha release of AspectJ 1.1, however, does not expose any call join point unless it is given the calling code. And the final release of AspectJ 1.1 may have different callee-side call behavior than the 1.0.6 release.
The AspectJ 1.0 compiler supported a number of options that started with X, for "experimantal". Some of them will not be supported in 1.1, either because the "experiment" succeeded (in which case it's part of the normal functionality) or failed. Others will be supported as is (or nearly so) in 1.1:
Building on the eclipse compiler has given us access to a very sophisticated problem reporting system as well as highly optimized error messages for pure Java code. Often this leads to noticably better error messages than from ajc-1.0.6. However, when we don't handle errors correctly this can sometimes lead to cascading error messages where a single small syntax error will produce dozens of other messages.
Many error condition in alpha1 are signalled by exception. You shouldn't be surprised to see RuntimeException("unimplemented") produced from perfectly reasonable programs that use features we just haven't implemented yet.
Advice that has an explicit throws clause needs to have that throws clause checked during weaving for each join point that is matched. This checking is not implemented in 1.1alpha1 which can lead to checked exceptions being thrown from places they are not allowed by the Java language.
before() throws IOException : execution (void m()) { InputStream s = new FileInputStream("m.out"); ... } ... public void m() { ... }
This code should result in a link-time weaving error that the throws clause in the advice is incompatible with the checked exceptions which can be legally thrown from the matched join point. In alpha1 this will just silently weave in the advice and it will be possible for an IOException to be thrown from m().
Some version of this checking will be in 1.1beta1. It will probably be more configuarable than the support in 1.0.6; using the same kind of configuration (though perhaps not the same mechanism) for compiler warnings that the Eclipse compiler currently has.
Because the configurability allows users to turn off warnings, we will also be able to warn about more potentially dangerous situations, such as the potentially unsafe casts used by very polymorphic uses of proceed in around advice.
Because AspectJ 1.1alpha1 does not generate source code after weaving, the source-code-specific options -source, -usejavac, -nocomment and -workingdir options are meaningless and so not supported.
Because AspectJ 1.1 uses the Eclipse compiler, which has its own mechanism for changing strictness, we no longer support the -strict and -lenient options.
AspectJ 1.1 does not have a -porting option. We believe that many useful porting information will be presented by the -Xlint option when it is enabled.
Because we build on Eclipse, the compiler will no longer run under J2SE 1.2. You must run the compiler (and all tools based on the compiler) using J2SE 1.3 or later.