Advanced AspectJ Part II : Inter-type declaration

In this article, we discuss about another ApectJ advanced feature: inter-type declaration (ITD), or more commonly called member introduction.

In essence, this feature lets you “inject” new fields or methods into an existing class using an aspect.

With ITD you can for example achieve virtual multiple inheritance or mixin. It is extremely powerfull but also very bug-prone because it somehow breaks basic Java encapsulation rules.

I Basic rules

It is possible to introduce into a Java class:

  • field (final or not)
  • method
  • interface
  • constructor (as well!)

It is possible to introduce into a Java interface:

  • method default implementation (yes, it’s very counter-intuitive with respect to the Java standard rules)
  • non final fields

While introducing field, method or constructor, the following access modifiers are allowed:

  • private: the introduced member belongs to the target class but it it “private” with respect to the introducing aspect. In other words this member is only visible to the aspect, not to the target class!
  • public: the introduced member is visible to any class and aspect
  • default package protected: the introduced member is visible to any class and aspect in the same package of the declaring aspect (and not the package of the target class)

If their is one thing to remember from access modifier, it’s that their semantic applies with respect to the declaring aspect, and not to the target. A private introduced member is private to the aspect.

The syntax to introduce a member is:

  • for a field : [access modifier] TargetClass.introducedFieldName
  • for a method/constructor : [access modifier] TargetClass.introducedMethodName(..) { //method body }

where [access modifier] can be: private, public or nothing (aspect package-protected)
 

II Naming conflicts

By injecting members into a class, we are likely to run into conflicts if the target class already declares a member with the same name and same type.

If we introduce a private field into the target class which already contaisn the same field declared as private, there is indeed no conflict. The “introduced” field is only visible from the declaring aspect and the original private field is only visible within the target class.

Exemple:

package com.test;

public class TestITDClass
{
	private String myField;
}

public aspect ITDAspect
{
	private String TestITDClass.myField;
}

In the above example, the base class TestITDClass has a private field myField. The ITDAspect introduces another myField field with aspect-related private visibility.

The decompiled class file generated after compile-time weaving shows:

public class TestITDClass
{
  private String myField;
  public String ajc$interField$com_test_aspect_ITDAspect$myField;

  public TestITDClass()
  {   
    ITDAspect.ajc$interFieldInit$com_test_ITDAspect$com_test_TestITDClass$myField(this);
  }
}

Indeed, the original myField field of the target class is untouched. What AspectJ did is to inject a new public String field ajc$interField$com_test_aspect_ITDAspect$myField instead.

Please notice that the injected field name is built using the canonical aspect name and target class name, all the dots (.) being replaced by underscores (_).

Now, if the target class declares a public myField field and in the aspect code, we access targetInstance.myField, there will be an ambiguity. Which field should be returned ? The original myField field or the injected ajc$interField$com_test_aspect_ITDAspect$myField?

In such case, it is simply forbidden since there is no way for the AspectJ compiler to distinguish between both fields. It is similar if you try to introduce a method with the same name and same signature. Introducing a method with the same name but different signature(parameters or returned type) is fine; it is as if you overload a method with different parameters or with covariant returned type.

It is not possible to inject a field (public or private whatever) into a target class if the latter already declared a public field with the same name (event with a different type)

It is not possible to inject a method(public or private whatever) into a target class if the latter already declared a public method with the same name, same parameters signature and returned type

III Introducing members into interfaces

Similar to classes, it is also possible to inject members to an interface, with some differences:

  • vanilla Java interfaces only allow final fields whereas with AspectJ you can inject non-final fields to interfaces!
  • vanilla Java interfaces only allow method signature declaration wheraeas with AspectJ you can inject default implementation for a method
  • !

Let’s define an interface and inject a field and method implementation using AspectJ:

package com.test;

public interface TestITDInterface
{
	public String getInjectedMethod();
	
	 static aspect TestITDInterfaceImpl
	 {
		 public String myInjectedField;
		 public String TestITDInterface.getInjectedMethod()
		 {
			 return "Injected method";
		 }
	 }
}

public class TestITDClass implements TestITDInterface
{

	public static void main(String[] args)
	{
		TestITDClass test = new TestITDClass();
		System.out.println("getInjectedMethod = " + test.getInjectedMethod());
	}
}

We define and TestITDInterface interface declaring a getInjectedMethod() method. Inside the same interface file, we define a nested aspect TestITDInterfaceImpl. This nested aspect must be static as per convention.

Inside the TestITDInterfaceImpl aspect, we inject a field myInjectedField and we provide a concrete implementation for the getInjectedMethod() method.

When running the class TestITDClass, we get as expected:

getInjectedMethod = Injected method
 

IV Logger with ITD

In this chapter, we’ll inject a logger into any class using AspectJ ITD.

First we define the Loggable interface

public interface Loggable {}

Any class implementing this interface would have an injected logger.

Now let’s create an aspect for the job

public aspect LoggerInjectionITDAspect
{
	private Logger Loggable.logger;

	public Logger Loggable.getLogger()
	{
		return logger;
	}

	public Logger Loggable.getLogger(String customLogger)
	{
		return Logger.getLogger(customLogger);
	}
	
	pointcut objectConstruction(TestInjectedLoggerITD object) : 
		initialization(Loggable+.new(..)) && this(object);
	
	after(TestInjectedLoggerITD object) : objectConstruction(object)
	{
		object.logger = Logger.getLogger(object.getClass().getCanonicalName());
	}
	
}

The aspect injects:

  • a logger field (line 3)
  • a public method getLogger() with no argument (line 5)
  • a public method getLogger(String customLogger) (line 10)

More interesting is the pointcut objectConstruction() definition. This pointcut intercepts the instanciation (call to new(..) constructor) of any class implementing the Loggable interface (Loggable+ means any subtype of Loggable) and retrieves the target instance in the pointcut context (lines 15 & 16).

After the initialization of the object, the aspect will create the default logger using the canonical class name as logger name and save it to the injected logger field (line 20).

A call to getLogger() will return the injected logger field.
A call to getLogger(String customLogger) will build a new Logger instance based on the passed customLogger string and return this instance.

Below is how the injected logger is called in a POJO:

public class TestInjectedLoggerITD implements Loggable
{
	public void instanceLoggerITD()
	{
		this.getLogger().info("Test Logger ITD instance " + this.getLogger());

		this.getLogger("testLogger").info("Test customLogger ITD instance " + this.getLogger("testLogger"));
	}

	public static void main(String[] args)
	{
		TestInjectedLoggerITD testInjectedLoggerITD1 = new TestInjectedLoggerITD();
		TestInjectedLoggerITD testInjectedLoggerITD2 = new TestInjectedLoggerITD();

		testInjectedLoggerITD1.instanceLoggerITD();
		testInjectedLoggerITD2.instanceLoggerITD();
	}
}

The method instanceLoggerITD() will test the usage of default injected logger and custom name logger (lines 5 & 7).

Please notice how the TestInjectedLoggerITD class makes use of the getLogger() and getLogger(String customLogger) although they have never been declared in the Loggable interface. It’s the magic from AspectJ ITD.

The execution output gives:

INFO [01:42:18,484] com.test.TestInjectedLoggerITD@instanceLoggerITD: Test Logger ITD instance org.apache.log4j.Logger@1ab28fe
INFO [01:42:18,486] testLogger@instanceLoggerITD: Test customLogger ITD instance org.apache.log4j.Logger@1e8a1f6

INFO [01:42:18,486] com.test.TestInjectedLoggerITD@instanceLoggerITD: Test Logger ITD instance org.apache.log4j.Logger@1ab28fe
INFO [01:42:18,486] testLogger@instanceLoggerITD: Test customLogger ITD instance org.apache.log4j.Logger@1e8a1f6

1 Comment

  1. Pingback: mindcraft games

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.