AOP - accessing a protected/private attribute from the intercepted class

328 Views Asked by At

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.

1

There are 1 best solutions below

0
On BEST 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