Hot questions for Using AspectJ in reflection

Question:

When I run the application with tomcat it says that the advices has not been aplied, so my aspects won't work. Do I have to configure that anywhere? I haven't done anything about it, so I don't know what code might be useful.

Thank you!

EDIT

I've just found out how to fix that, and even when it says that the aspect has not been applied, when I call the setter it works, but I have a problem when using reflection for that.

I have an aspect which intercept a setter to a field which works well, but when the value is assigned from Gson library por example, it doesn't work.

This is the aspect:

public aspect AnnotationAspect {

    pointcut hasAnnotation(Annotation annotation) : @annotation(annotation);

    Object around(Annotation annotation, String word) : hasAnnotation(annotation) && set(String *) && args(word) {
        Object result = null;
        try {
            result = proceed(annotation, "intercepted");
        } catch (RuntimeException ex) {
            throw ex;
        }
        return result;
    }
}

And I have this class:

public class JavaEntity {

    @Annotation
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

When I do something like this it does not intercept the assignment:

JavaEntity entity = new Gson().fromJson("{\"name\":\"name\"}", JavaEntity.class);

Is there anyway to intercept that? Thank you!


Answer:

Reflective field access cannot be directly intercepted by AspectJ. You could only work around it by fighting fire with fire, i.e. also using reflection:

  • Make sure Gson is on the inpath for the AspectJ compiler (i.e. a weave dependency for AspectJ Maven plugin) so as to be able to weave into its code.
  • Intercept calls to Field.set(..).
  • In the intercepting advice check which field is to be set, look up its annotations via reflection in order to emulate @annotation(blah), decide whether to modify its value or not.

This is not exactly the usual AspectJ use case, but possible.

BTW, you do not have this kind of problem with execution() joinpoints because they are also triggered when a method is called reflectively. You would have the problem with call(), though. But this is just for your information, not directly related to this specific case.


Update: I mean something like this:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <configuration>
        <weaveDependencies>
            <weaveDependency>
                <groupId>Group</groupId>
                <artifactId>model</artifactId>
            </weaveDependency>
            <weaveDependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
            </weaveDependency>
        </weaveDependencies>
    </configuration>
</plugin>
@Test
public void testingInterceptWithJavaGson(){
    javaEntity = new Gson().fromJson("{\"name\":\"newName\"}", JavaEntity.class);
    Assert.assertEquals("intercepted", javaEntity.getName());
}
Object around(Field field, Object obj, Object value) :
    within(com.google.gson..*) &&
    call(public void Field.set(Object, Object)) &&
    target(field) &&
    args(obj, value)
{
    Object result = null;
    System.out.println(thisJoinPoint);
    System.out.println("  " + field + " -> " + field.getAnnotation(Annotation.class));
    System.out.println("  " + obj);
    System.out.println("  " + value);
    try {
        if (field.getAnnotation(Annotation.class) != null && field.getType() == String.class)
            result = proceed(field, obj, "intercepted");
        else
            result = proceed(field, obj, value);
    } catch (RuntimeException ex) {
        throw ex;
    }
    return result;
}

Actually you do not need to bind the first parameter obj via args(), I just use it for logging so as to show you what happens there. So this slightly simpler form will also do:

Object around(Field field, Object value) :
    within(com.google.gson..*) &&
    call(public void Field.set(Object, Object)) &&
    target(field) &&
    args(*, value)
{
    Object result = null;
    System.out.println(thisJoinPoint);
    try {
        if (field.getAnnotation(Annotation.class) != null && field.getType() == String.class)
            result = proceed(field, "intercepted");
        else
            result = proceed(field, value);
    } catch (RuntimeException ex) {
        throw ex;
    }
    return result;
}

The changes to your existing GitHub repo are contained in yet another pull request from my fork.

Question:

I am trying to instanciate an object of a non public class in AspectJ.

I have this class:

package ca1.business;

public class Bill {

    int id;
    String idOperator;
    String idClient;

    Bill(int id, String idOperator, String idClient) { /* (...) */ }
    public String toString() { /* (...) */ }
    public boolean equals(Object o) { /* (...) */ }
    public int getId() { /* (...) */ }
    public String getOperator() { /* (...) */ }
    public String getClient() { /* (...) */ }
}

In the aspects class I wanted to be able to do:

Bill b = new Bill(currInvoice, idOperator, idClient);

The problem is that I get an error:

The constructor Bill(int, String, String) is not visible

So I investigated and tried to use reflection like it's explained in this post.

try {
    Constructor<Bill> cons = Bill.class.getDeclaredConstructor(null);
    cons.setAccessible(true);
    Bill invoice = cons.newInstance(null);
    invoice.id = 1;
    invoice.idOperator = "foo";
    invoice.idClient = "bar";
    // etc...
} catch (SecurityException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalArgumentException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

But I get an error in the lines:

invoice.id = 1;
invoice.idOperator = "foo";
invoice.idClient = "bar";

The error is:

The field Bill.X is not visible.

Does anyone know if there is any workaround?


Answer:

Why use reflection?

Let us assume the Bill class looks like this:

package ca1.business;

public class Bill {
  int id;
  String idOperator;
  String idClient;

  Bill(int id, String idOperator, String idClient) {
    this.id = id;
    this.idOperator = idOperator;
    this.idClient = idClient;
  }

  @Override
  public String toString() {
    return "Bill [id=" + id + ", idOperator=" + idOperator + ", idClient=" + idClient + "]";
  }

  public static Bill instance;

  public static void main(String[] args) {
    System.out.println(instance);
  }
}

We want our aspect to populate the static member before main is executed (silly example, just for demo):

Option A: privileged aspect

package de.scrum_master.aspect;

import ca1.business.Bill;

public privileged aspect MyAspect {
  before() : execution(public static void main(String[])) {
    Bill.instance = new Bill(11, "operator", "client");
  }
}

Option B: put aspect into package ca1.business

The constructor is package-private, i.e. other classes in the same package can access it.

package ca1.business;

public aspect MyAspect {
  before() : execution(public static void main(String[])) {
    Bill.instance = new Bill(11, "operator", "client");
  }
}

Option C: put factory class into protected package as a helper

package ca1.business;

public class BillFactory {
  public static Bill create(int id, String idOperator, String idClient) {
    return new Bill(id, idOperator, idClient);
  }
}
package de.scrum_master.aspect;

import ca1.business.Bill;
import ca1.business.BillFactory;;

public aspect MyAspect {
  before() : execution(public static void main(String[])) {
    Bill.instance = BillFactory.create(11, "operator", "client");
  }
}

Console log for each option A, B, C

Bill [id=11, idOperator=operator, idClient=client]

Question:

It's for the first time that Inside an AspectJ I may need to access a local private autowired field of a Repository in order to do some stuff on >exactly< that instance.

I created a pointcut that focuses on each method of every @Repository annotated class. When the pointcut fires, I get the current class instance from which I want to get the bean field.

This is the way:

@Repository
public class MyDao {

    @Autowired
    private MyBean bean;

    public List<Label> getSomething() {
        // does something...
    }
}


@Aspect
@Component
public class MyAspect {

    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    public void repositories() {
    }

    @Before("repositories()")
    public void setDatabase(JoinPoint joinPoint) {
        try {
            Field field = ReflectionUtils.findField(joinPoint.getThis().getClass(), "bean"); // OK since here - joinPoint.getThis().getClass() -> MyDao
            ReflectionUtils.makeAccessible(field); // Still OK
            Object fieldValue = ReflectionUtils.getField(field, joinPoint.getThis());
            System.out.println(fieldValue == null); // true

            // should do some stuff with the "fieldValue"
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

fieldValue is always null even if I create something like private | public | package String something = "blablabla"; instead.

I have ensured that "bean" is actually instantiated when the application starts (verified with the debugger).

I followed How to read the value of a private field from a different class in Java?

What I am missing? | Is it possible? | Are there any different ways?


Answer:

@springbootlearner suggested this approach access class variable in aspect class

All I had to do is to replace the joinPoint.getThis() with joinPoint.getTarget()

And the final solution is:

@Aspect
@Component
public class MyAspect {

    /**
     *
     */
    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    public void repositories() {
    }

    /**
     * @param joinPoint
     */
    @Before("repositories()")
    public void setDatabase(JoinPoint joinPoint) {
       Object target = joinPoint.getTarget();

       // find the "MyBean" field
       Field myBeanField = Arrays.stream(target.getClass().getDeclaredFields())
            .filter(predicate -> predicate.getType().equals(MyBean.class)).findFirst().orElseGet(null);

       if (myBeanField != null) {
           myBeanField.setAccessible(true);
           try {
              MyBean bean = (MyBean) myBeanField.get(target);// do stuff
           } catch (IllegalAccessException e) {
               e.printStackTrace();
           }
       }
    }

}