Question

The following example is a reduction of the real problem in that it tries to simplify is as much as possible.

I have a java interface, and several objects that implement that interface, like:

public interface Shape{
    public void draw();
    public void erase();
    public boolean isDrawn();
}

public class Square implements Shape{
    @Override
    public void draw(){
        //TODO: method implementation
    }

    @Override
    public void erase(){
        //TODO: method implementation
    } 

    Override
    public boolean isDrawn(){
        //TODO: method implementation
        return false;
    }
}

public Triangle implements Shape{
    //same as above
}

public Circle implements Shape{
    //same as above
}

This is the structure of my program. By using AspectJ I want to have a map that holds each object that implements the interface. To do so I was trying to capture the constructors by using the following aspect:

public aspect ShapeHolderAspect{
    private Map<Integer, Shape> map = new HashMap<>();
    private int count = 0;    

    pointcut shapeInit(): call((Shape+).new(..));

    Object around(): shapeInit() {
        System.out.println("capturing new");

        Shape shapeType = (Shape)proceed();
        map.put(++count, shapeType);
        return shapeType;
    }
}

This code will work if I create a Shape using the following scenario:

public static void main(String[] args){
    Shape myShape = new Circle();
}

However, I am using java language reflection, and so technically I don't call the "new" constructor. Instead I locate the path of the package, and create the object passing a string with the name of the class:

public static void main(String[] args){
    String shapeClassName = args[0];
    Class<?> classType = Class.forName("myPackage.figures" + "." + shapeClassName);
    Shape myShape =(Shape)classType.getConstructor().newInstance();
}

By doing this way, AspectJ cannot detect that I am creating shapes. How do I fix this?

Was it helpful?

Solution

New, better version:

Well, while the old version below actually catches all constructor executions, an around advice on constructor execution returns null because the object in question has not been initialised yet. So you would end up with a map of null pointers in your aspect. In order to fix this you need to bind this() to a variable (sample code uses default package name):

public class Application {
    public static void main(String[] args) throws Exception {
        new Circle().draw();
        ((Shape) Class.forName("Triangle").getConstructor().newInstance()).isDrawn();
        ((Shape) Class.forName("Square").getConstructor().newInstance()).erase();
    }
}
import java.util.HashMap;
import java.util.Map;

public aspect ShapeHolderAspect {
    private Map<Integer, Shape> map = new HashMap<Integer, Shape>();
    private int count = 0;

    after(Shape shape): execution(Shape+.new(..)) && this(shape) {
        System.out.println(thisJoinPointStaticPart);
        map.put(++count, shape);
    }

    after() : execution(* Application.main(..)) {
        System.out.println("\nList of shapes:");
        for (int key : map.keySet())
            System.out.println("  " + key + " -> " + map.get(key));
    }
}

The output looks like this:

initialization(Circle())
initialization(Triangle())
initialization(Square())

List of shapes:
  1 -> Circle@1a2961b
  2 -> Triangle@12d03f9
  3 -> Square@5ffb18

BTW, if you absolutely need an around advice because you want to do other things before and after object creation, it would look like this:

void around(Shape shape): execution(Shape+.new(..)) && this(shape) {
    System.out.println(thisJoinPointStaticPart);
    proceed(shape);
    map.put(++count, shape);
}

Old, incomplete version:

Quite simply, just intercept constructor execution instead of call:

pointcut shapeInit(): execution(Shape+.new(..));

This way you weave into the called code (callee), not the calling code (caller). Consequently, it does not matter if the caller issues a reflective or normal call.

OTHER TIPS

Found that the following pointcut will do the job:

pointcut lockReflectInit(): call(public Object java.lang.reflect.Constructor.newInstance(..));  

This will however catch ALL calls of newInstance, and not just the ones that return Shape =(

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top