Hot questions for Using AspectJ in ajdt

Question:

I am working on a project that is basically a lot of processes that run periodically. Each process is a different class that extends an abstract class RunnableProcess we created, which contains the abstract method run with the signature below:

public abstract void run(Map processContext) throws IOException;

To improve modularization on the project, I'm starting to use Aspect Oriented Programming (AOP) to intercept the run calls from every RunnableProcess. I am still learning AOP, and I have the following code until now:

import static org.slf4j.LoggerFactory.getLogger;

import org.slf4j.Logger;
import process.RunnableProcess;
import java.util.Map;

public aspect ProcessRunInterceptorProtocol {

    pointcut runProcess() : call(void RunnableProcess.run(Map));

    before(): runProcess() {
        logger = getLogger(getClass());
        logger.info("running process " + thisJoinPoint);
    }

    after(): runProcess() {
        logger = getLogger(getClass());
        logger.info("process run successfuly " + thisJoinPoint);
    }

    private Logger logger;
}

The problem I'm having is related to the logger (org.slf4j.Logger) initialization - I would like it to be linked with the process class (the one that extended RunnableProcess, and it is being intercepted by the aspect), which is not happening here (the getClass() retrieves the aspect class). How can I do that without changing the implementation of RunnableProcess and its childs?


Answer:

You want the target object on which the method is executed. Try this:

logger = getLogger(thisJoinPoint.getTarget().getClass());

Question:

I'm working on a Java 1.8 project with several modules and one huge cross-cutting concern - logs are present in almost every class, in every module. I recently read about Aspect Oriented Programming (AOP) and I though it would be nice to use AspectJ to make things more modular, just like the guy from this post did. I decided to give it a try...

Since I'm using Eclipse Oxygen and it isn't compatible with the latest AJDT, I've downloaded Eclipse Kepler and the latest AJDT. However, I've noticed that once AJDT was installed, all Java Compiller's settings were set to J2SE-1.4, and I couldn't put it back to 1.8, since this option was not available anymore on the IDE. This caused me a lot of compiler's errors, such as:

Build path specifies execution environment J2SE-1.4. There are no JREs installed in the workspace that are strictly compatible with this environment.

and

'<>' operator is not allowed for source level below 1.7

I have the feeling that AJDT is outdated and incompatible with the latest java versions. However, since this is the first time I'm trying to use AOP, I wonder if I'm not following the correct path...

So, to make it simple and direct, my question is - is it possible to use AJDT with Java 1.8 (maybe manually (directly on a text file) editting some workspace configuration instead of using the IDE's UI, or something like that)? Or, to use AJDT, I'll need to make my project compatible with older Java versions, by "fixing", for instance, the <> operators, among several other things adopted by the latest versions of Java?


Answer:

AJDT has a development build for Eclipse Oxygen (4.7), see https://eclipse.org/ajdt/downloads/

AJDT dev builds for Eclipse 4.7

Update Site URL:http://download.eclipse.org/tools/ajdt/47/dev/update

I just created a HelloWorld test project with aspects in Oxygen, and it runs under Java 8.

Question:

I am working on a project that is basically a lot of processes that run periodically. Each process is a different class that extends an abstract class RunnableProcess we created, which contains a private attribute Map<String, String> result and the abstract method run with the signature below:

public abstract void run(Map processContext) throws IOException;

To improve modularization on the project, I'm starting to use Aspect Oriented Programming (AOP) to intercept the run calls from every RunnableProcess. I am still learning AOP, and I have the following code until now:

import static org.slf4j.LoggerFactory.getLogger;

import org.slf4j.Logger;
import process.RunnableProcess;
import java.util.Map;

public aspect ProcessRunInterceptor {
    private Logger logger;
    pointcut runProcess() : call(void RunnableProcess.run(Map));

    after(): runProcess() {
        logger = getLogger(thisJoinPoint.getClass());
        logger.info("process run successfully");
    }
}

It is working, but I want to log more information than just "process run successfully". I have this information inside the intercepted class, in the result attribute mentioned above. Is it possible to access it in the advice without changing the implementation of RunnableProcess?

I can (prefer not to, but if it would be the only choice...) change the attribute from private to protected, but I wouldn't change it to public. I also would not like to create a get method for it.


Answer:

Building upon my answer to your other question, I will explain to you what you can do. A few hints:

  • Instead of before() and after() you could just use around().

  • You should not use a private member for the process instance in a singleton aspect because if you have asynchronous processes in multiple threads, the member could be overwritten. Thus, your approach is not thread-safe and you should use a local variable instead.

  • You should not print "process run successfully" in an after() advice because after() also runs after an exception. So you cannot safely assume that the process ran successfully, only that it ran at all. You should rather write "finished process" or similar. BTW, if you want to differentiate between successful processes and such ending with an exception, you might want to look into pointcut types after() returning and after() throwing().

  • It does not make sense to use an abstract base class and not define the member result there directly. Why add it as a private member in each subclass if you can have it as a protected member in the parent? We are still doing OOP here (beside AOP, of course), right? The advantage is that you can access the member directly from the aspect using the base class like you already do in your pointcut.

Here is an MCVE for you:

Process classes:

package de.scrum_master.app;

import java.io.IOException;
import java.util.Map;

public abstract class RunnableProcess {
  protected String result = "foo";

  public abstract void run(Map processContext) throws IOException;
}
package de.scrum_master.app;

import java.io.IOException;
import java.util.Map;

public class FirstRunnableProcess extends RunnableProcess {
  @Override
  public void run(Map processContext) throws IOException {
    System.out.println("I am #1");
    result = "first";
  }
}
package de.scrum_master.app;

import java.io.IOException;
import java.util.Map;

public class SecondRunnableProcess extends RunnableProcess {
  @Override
  public void run(Map processContext) throws IOException {
    System.out.println("I am #2");
    result = "second";
  }
}

Driver application:

package de.scrum_master.app;

import java.io.IOException;

public class Application {
  public static void main(String[] args) throws IOException {
    new FirstRunnableProcess().run(null);
    new SecondRunnableProcess().run(null);
  }
}

Aspect:

Here you just bind the target() object to a parameter in the pointcut and use it in both advices.

package de.scrum_master.aspect;

import static org.slf4j.LoggerFactory.getLogger;

import org.slf4j.Logger;
import de.scrum_master.app.RunnableProcess;
import java.util.Map;

public privileged aspect ProcessRunInterceptorProtocol {
  pointcut runProcess(RunnableProcess process) :
    call(void RunnableProcess.run(Map)) && target(process);

  before(RunnableProcess process): runProcess(process) {
    Logger logger = getLogger(process.getClass());
    logger.info("logger = " + logger);
    logger.info("running process = " + thisJoinPoint);
  }

  after(RunnableProcess process): runProcess(process) {
    Logger logger = getLogger(process.getClass());
    logger.info("finished process = " + thisJoinPoint);
    logger.info("result = " + process.result);
  }
}

Console log with JDK logging (some noise removed):

INFORMATION: logger = org.slf4j.impl.JDK14LoggerAdapter(de.scrum_master.app.FirstRunnableProcess)
INFORMATION: running process = call(void de.scrum_master.app.FirstRunnableProcess.run(Map))
I am #1
INFORMATION: finished process = call(void de.scrum_master.app.FirstRunnableProcess.run(Map))
INFORMATION: result = first
INFORMATION: logger = org.slf4j.impl.JDK14LoggerAdapter(de.scrum_master.app.SecondRunnableProcess)
INFORMATION: running process = call(void de.scrum_master.app.SecondRunnableProcess.run(Map))
I am #2
INFORMATION: finished process = call(void de.scrum_master.app.SecondRunnableProcess.run(Map))
INFORMATION: result = second

Question:

I'm trying to use @DeclareMixin for the first time, and either I am doing something incorrect, or there is a bug somewhere.

I've published my sample code to github: https://github.com/benze/AspectJError.git. I'm pasting the little bits here as well.

If I look at the compiled code of ApplyAspect.class using a decompiler, I can see that ajc has properly added in the implemented interface. However, the compiler complains in Test that ApplyAspect does not have the setCreated() or the getCreated() methods.

Additionally, if I try to compile the project from the command line, I get the same compilation errors as well.

I'm not sure what I am doing wrong, or if there is a bug somewhere else with @DeclareMixin directive.

Interface CreatedBean.java:

public interface CreatedBean {
    public Object getCreated();
    public void setCreated(final Object created);
}

Implementation CreatedBeanImpl.java:

public class CreatedBeanImpl implements CreatedBean{
        private Object created;

        public Object getCreated(){
            return this.created;
        }

        public void setCreated(final Object created ){
            this.created = created;
        }
}

Aspect definition:

@Aspect
public class DeclareMixinAspect {
    @DeclareMixin("com.benze.bo.ApplyAspect")
    public CreatedBean auditableBeanMixin(){
        return new CreatedBeanImpl();
    }
}

Class being advised (com.benze.bo pkg):

public class ApplyAspect {
    private String name = "test class";
}

Class trying to use ApplyAspect:

public class Test {

    public static void main(String[] args) {
        ApplyAspect aa = new ApplyAspect();
        aa.setCreated(new Date());
        System.out.println( aa.getCreated().toString());
        System.out.println(aa.toString());
        System.out.println("all done");
    }
}

The pom is very basic with only the aspectj plugin (and dependencies) added. I'm using AJ 1.8.2.


Answer:

I think you need casts in your Test class:

((CreatedBean)aa).setCreated(new Date());

System.out.println(((CreatedBean)aa).getCreated().toString());

IIRC the reason is that annotation style code is intended to be compilable with javac, which would not know about the affect of DeclareMixin.