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?
You want the target object on which the method is executed. Try this:
logger = getLogger(thisJoinPoint.getTarget().getClass());
Related
Suppose that I have this class
public class MyClass {
private Logger log = LogFactory.getLogger(MyClass.class);
public void doSomething() {
// doing something
}
}
Suppose that I want to write an aspect to log entry and exit:
public aspect TraceAspect {
pointcut method(): execution(* *(..));
before(): method(){
log.info("entering method");
}
after(): method(){
log.info("existing method");
}
}
The problem here is that I want to access the log object inside the class with aspect, but I don't know how. I don't want to create a new logger because I want to keep all the data associated with the class logger when logging. Is there a way or a pattern to access class data?
EDIT: wanted to state that this aspect should trace all classes that have log field. That is, I might have many classes: MyClass, MyClass1, YourClass2, RepositoryClass, etc.
What NĂ¡ndor said is technically correct, but my advice is: Please avoid accessing private members or methods whenever you can because they are private for a reason. For instance, they are subject to change even when the public interface of a class does not change. So you can never rely on their existence or their naming. Furthermore, a cross-cutting concern should also comply with design principles like encapsulation as much as possible.
This specific case is about Slf4J loggers, more precisely about your wish to avoid creating redundant logger objects. Well, Slf4J is not as stupid or careless as you might think concerning object creation. All classes implementing ILoggerFactory use an internal map in order to cache existing loggers for given (class) names, see e.g.
SimpleLoggerFactory
JDK14LoggerFactory
So why don't you just relax and access the corresponding loggers from your aspect using the target class names. This even works if a target class does not have its own static logger:
Application classes:
package de.scrum_master.app;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClassWithLogger {
private Logger log = LoggerFactory.getLogger(MyClassWithLogger.class);
public void doSomething() {}
}
package de.scrum_master.app;
public class MyClassWithoutLogger {
public void doSomething() {}
}
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new MyClassWithLogger().doSomething();
new MyClassWithoutLogger().doSomething();
}
}
Aspect:
package de.scrum_master.aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public aspect TraceAspect {
pointcut executedMethods(Object targetObject) :
execution(!static * *(..)) && target(targetObject);
before(Object targetObject) : executedMethods(targetObject) {
Logger log = LoggerFactory.getLogger(targetObject.getClass());
log.info("Entering " + thisJoinPoint);
}
after(Object targetObject) : executedMethods(targetObject) {
Logger log = LoggerFactory.getLogger(targetObject.getClass());
log.info("Exiting " + thisJoinPoint);
}
}
Console log with Slf4J configured to use simple logger:
[main] INFO de.scrum_master.app.MyClassWithLogger - Entering execution(void de.scrum_master.app.MyClassWithLogger.doSomething())
[main] INFO de.scrum_master.app.MyClassWithLogger - Exiting execution(void de.scrum_master.app.MyClassWithLogger.doSomething())
[main] INFO de.scrum_master.app.MyClassWithoutLogger - Entering execution(void de.scrum_master.app.MyClassWithoutLogger.doSomething())
[main] INFO de.scrum_master.app.MyClassWithoutLogger - Exiting execution(void de.scrum_master.app.MyClassWithoutLogger.doSomething())
I have the following advice:
#Before("execution(* com.myapp..*.*(..)) && !execution(* com.myapp.cms.workflow..*.*(..))")
public void logBefore(JoinPoint joinPoint) {
log.info("Calling " + joinPoint.getSignature().getName());
}
When I add the second condition to the pointcut:
&& !execution(* com.myapp.cms.workflow..*.*(..))
it logs every method call from every package.
I want the advice only to apply if its in the myapp package but not under the workflow package. Can anyone advise what I've done wrong?
AspectJ 1.6.8
it logs every method call from every package
No, it does not. It logs every method from package com.myapp and all its subpackages only, except for everything inside and below com.myapp.cms.workflow. If this is not what you want, maybe you should change your pointcut.
BTW, why are you using an outdated AspectJ version from 2009? It only supports Java 6 which is long out of support.
Update:
As you seem to not believe me, here is proof that your statement is wrong.
Java classes according to your example:
package com.myapp;
public class Foo {
public String convert(Integer number) {
return number.toString();
}
}
package com.myapp.cms.workflow;
public class Workflow {
public void doSomething() {}
}
package de.scrum_master.app;
import com.myapp.Foo;
import com.myapp.cms.workflow.Workflow;
public class Application {
// Should not be logged
public static void main(String[] args) {
// Should be logged
new Foo().convert(11);
// Should not be logged
new Workflow().doSomething();
}
}
Aspect according to your example:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class LogAspect {
#Before("execution(* com.myapp..*.*(..)) && !execution(* com.myapp.cms.workflow..*.*(..))")
public void logBefore(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
I compiled with Java 8 and AspectJ 1.8.13. I even tried from the console with AspectJ 1.6.8 and Java 1.6.0_45, the result is exactly the same.
Console log:
execution(String com.myapp.Foo.convert(Integer))
Ergo: Everything works as expected. Either you have not shown me your real pointcut or you forgot to remove another aspect logging everything from the classpath or whatever. AspectJ is not the problem. The problem sits in front of the computer, I would assume.
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.
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
I have an abstract class (database mapping) implementing an interface where default implementations are injected at runtime (this is part of another library and cannot be changed).
I want to override one of the default implementation via a proxy (as that seems like the way to override this).
public abstract class Table1 implements Storable<Table1>
{
#Sequence("ID_SEQUENCE")
#Alias("ID")
public abstract String getID();
public abstract void setID(String ID);
#Alias("NAME")
public abstract String getAvailabilityZone();
public abstract void setAvailabilityZone(String value);
}
public interface Storable<S extends Storable<S>> {
//a bunch of method definition.
boolean tryLoad() throws Exception;
}
Let's say I want to override tryLoad() method to do my own things instead of what the generated code provides. Given the nature of the library, it is not something I can achieve by simple #Override.
The simple way this is currently used is as following:
public void method() {
Table1 t = Repository.storageFor(Table1.class).prepare();
t.setName( "temp" );
if (!t.tryLoad())
t.tryInsert();
}
I want to proxy tryLoad() without making changes in all the methods across the whole codebase - that would be to get proxied instance instead of actual one and perform the operation on that.
Is there any recommended way to achieve this?
Thanks!
I woke up last night and felt bored, so despite your lack of feedback I created a little Carbonado showcase project and shared it on GitHub. I made three commits:
Initial commit with Maven project already prepared for AspectJ and a JUnit test for me to find out how Carbonado actually works, because I had never used it before.
Add failing unit test for behaviour of tryLoad() expected to be provided by aspect.
Add aspect to make unit test pass. Aspect hooks into tryLoad() and auto-creates non-existent record. I do not know if I guessed right what you actually wanted to achieve, but if it was a different thing, just change the aspect implementation.
Sample code
Carbonado storable:
package de.scrum_master.app;
import com.amazon.carbonado.Nullable;
import com.amazon.carbonado.PrimaryKey;
import com.amazon.carbonado.Storable;
#PrimaryKey("ID")
public interface StoredMessage extends Storable<StoredMessage> {
long getID();
void setID(long id);
#Nullable String getMessage();
void setMessage(String message);
}
Aspect:
package de.scrum_master.aspect;
import com.amazon.carbonado.Storable;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class CarbonadoAspect {
#Around("call(boolean tryLoad()) && target(storable)")
public boolean tryInsertIfNotFound(ProceedingJoinPoint thisJoinPoint, Storable storable) throws Throwable {
System.out.println(thisJoinPoint);
if ((boolean) thisJoinPoint.proceed())
return true;
System.out.println("Not found: " + storable + " -> inserting");
return storable.tryInsert();
}
}
JUnit test:
package de.scrum_master.app;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.Repository;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storage;
import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.repo.map.MapRepositoryBuilder;
import de.scrum_master.app.StoredMessage;
public class CarbonadoTest {
private Repository repo;
private Storage<StoredMessage> storage;
StoredMessage message;
#Before
public void setUp() throws Exception {
repo = MapRepositoryBuilder.newRepository();
storage = repo.storageFor(StoredMessage.class);
message = storage.prepare();
}
#After
public void tearDown() throws Exception {
repo.close();
repo = null;
storage = null;
message = null;
}
// (...)
#Test
public void aspectCreatesNonExistentRecord() throws SupportException, RepositoryException {
message.setID(1);
// Without the aspect this would be false
assertTrue(message.tryLoad());
assertEquals(message.getID(), 1);
assertEquals(message.getMessage(), null);
}
}
Enjoy!
I am learning Spring and I searched a lot about how to properly use #args() AspectJ designator but I am still not clear completely. What I know about it is that it limits joint-point matches to the execution of methods whose arguments are annoted with the given annotation types. This does not seem to work in my case.
So here goes my files:
Human.java
#Component
public class Human {
int sleepHours;
public int sleep(String sleepHours) {
this.sleepHours = Integer.parseInt(sleepHours);
System.out.println("Humans sleep for " + this.sleepHours + " hours.");
return this.sleepHours+1;
}
}
Sleepable.java - Sleepable annotation
package com.aspect;
public #interface Sleepable {
}
SleepingAspect.java - Aspect
#Component
#Aspect
public class SleepingAspect {
#Pointcut("#args(com.aspect.Sleepable)")
public void sleep(){};
#Before("sleep()")
public void beforeSleep() {
System.out.println("Closing eyes before sleeping");
}
#AfterReturning("sleep()")
public void afterSleep() {
System.out.println("Opening eyes after sleep");
}
}
MainApp.java
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Human human = (Human) context.getBean("human");
#Sleepable
String sleepHours = "8";
human.sleep(sleepHours);
}
}
Output
Humans sleep for 8 hours.
Expected Output
Closing eyes before sleeping
Humans sleep for 8 hours.
Opening eyes after sleep
You have several mistakes in your code:
Spring AOP can only intercept method calls upon Spring components. What you are trying to intercept is an annotation on a local variable. Not even the much more powerful AspectJ can intercept anything concerning local variables, only read/write access to class members. Thus, what you are trying to do is impossible. And by the way, it is bad application design. Why would anyone want to rely on method internals when trying to apply cross-cutting behaviour? Method internals are subject to frequent refactoring. Suggestion: Put your annotation on method public int sleep(String sleepHours).
Your annotation is invisible during runtime because your forgot to add a meta annotation like #Retention(RetentionPolicy.RUNTIME).
#args is the wrong pointcut type. It captures method arguments the types of which are annotated. You want to use #annotation(com.aspect.Sleepable) instead.
I think you should not try a copy & paste cold start with Spring AOP but read the Spring AOP manual first. Everything I explained here can be found there.
Update: So according to you comments you were just practicing and trying to make up an example for #args(). Here is one in plain AspectJ. You can easily use it in similar form in Spring AOP. The #Before advice shows you how to match on an argument with an annotation on its class, the #After advice also shows how to bind the corresponding annotation to an advice argument.
Annotation + class using it:
package com.company.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {}
package com.company.app;
#MyAnnotation
public class MyClass {}
Driver application:
package com.company.app;
public class Application {
public static void main(String[] args) {
new Application().doSomething(new MyClass(), 11);
}
public String doSomething(MyClass myClass, int i) {
return "blah";
}
}
As you can see, here we use the annotated class MyClass in a method argument. This can be matched with #args() in the following aspect.
Aspect:
package com.company.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import com.company.app.MyAnnotation;
#Aspect
public class MyAspect {
#Before("#args(com.company.app.MyAnnotation, ..)")
public void myBeforeAdvice(JoinPoint thisJoinPoint) {
System.out.println("Before " + thisJoinPoint);
}
#After("#args(myAnnotation, ..)")
public void myAfterAdvice(JoinPoint thisJoinPoint, MyAnnotation myAnnotation) {
System.out.println("After " + thisJoinPoint + " -> " + myAnnotation);
}
}
Console log:
Before call(String com.company.app.Application.doSomething(MyClass, int))
Before execution(String com.company.app.Application.doSomething(MyClass, int))
After execution(String com.company.app.Application.doSomething(MyClass, int)) -> #com.company.app.MyAnnotation()
After call(String com.company.app.Application.doSomething(MyClass, int)) -> #com.company.app.MyAnnotation()
Of course, call() joinpoints are unavailable in Spring AOP, so there you would only see two lines of log output.