Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can AspectJ replace "new X" with "new SubclassOfX" in third-party library code?

Tags:

aop

aspectj

I am looking at AspectJ to see if perhaps we can use it in our test suite.

We have a rather large third party Java communications library hardwired to use its own classes (which do not implement any interfaces) which in turn mean that we need a physical backend present and correctly configured to be able to run tests.

I am looking at our options for removing this restriction. A possibility would be to create a subclass of the troublesome classes and then ask AspectJ to simply replace "new X" with "new OurSubclassOfX" when loading the third party library, but I am new to AspectJ and from my brief skimming of the documentation this is not a typical use case.

Can AspectJ do this? What would the configuration snippet be?

like image 827
Thorbjørn Ravn Andersen Avatar asked May 03 '13 09:05

Thorbjørn Ravn Andersen


1 Answers

Yes, this is possible. Let us assume you have a hard-wired class, possibly fetching something from a database, and want to mock it via an aspect:

package de.scrum_master.aop.app;

public class HardWired {
    private int id;
    private String name;

    public HardWired(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public void doSomething() {
        System.out.println("Fetching values from database");
    }

    public int getSomething() {
        return 11;
    }

    @Override
    public String toString() {
        return "HardWired [id=" + id + ", name=" + name + "]";
    }
}

Then there is a little driver application using that very class (not an interface):

package de.scrum_master.aop.app;

public class Application {
    public static void main(String[] args) {
        HardWired hw = new HardWired(999, "My object");
        System.out.println(hw);
        hw.doSomething();
        System.out.println(hw.getSomething());
    }
}

The output is as follows:

HardWired [id=999, name=My object]
Fetching values from database
11

Now you define your derived mock class which should replace the original for testing purposes:

package de.scrum_master.aop.mock;

import de.scrum_master.aop.app.HardWired;

public class HardWiredMock extends HardWired {
    public HardWiredMock(int id, String name) {
        super(id, name);
    }

    @Override
    public void doSomething() {
        System.out.println("Mocking database values");
    }

    @Override
    public int getSomething() {
        return 22;
    }

    @Override
    public String toString() {
        return "Mocked: " + super.toString();
    }
}

And finally you define an aspect with a simple pointcut and advice to replace the original value during each constructor call:

package de.scrum_master.aop.aspect;

import de.scrum_master.aop.app.HardWired;
import de.scrum_master.aop.mock.HardWiredMock;


public aspect MockInjector {
    HardWired around(int p1, String p2) : call(HardWired.new(int, String)) && args(p1, p2) {
        return new HardWiredMock(p1, p2);
    }
}

The output changes as desired:

Mocked: HardWired [id=999, name=My object]
Mocking database values
22

You do that once per class and constructor and are fine. In order to generalise the approach you would need joinpoint properties and, depending on how far you want to go, maybe reflection, but this here is pretty straightforward. Enjoy!

like image 111
kriegaex Avatar answered Sep 28 '22 02:09

kriegaex