I am a newbie to aspectj...
I have written the following aspect which is intended to add logging to function calls of type public * doSomething*(..)
. If my main class is part of the same project the weaving of the aspect is performed without a glitch and the code executes. If I pack up the weaved code into a jar and call it from another eclipse project - the advice is not executed. Another scenario is packing up the aspect (.aj) only into a separate jar and adding that jar to the "Aspect Path" in eclipse, this enables eclipse to weave the aspect properly. Thing is I need to wrap this up into a jar and call the code from elsewhere. That doesn't work either (Not surprisingly I presume...) Why?
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.apache.log4j.Logger;
public aspect Logging {
pointcut allPublic(): !cflow(call(public void main(..))) && (call(public * doSomething*(..)));
private static final Logger log = Logger.getLogger("Logging.aspect");
@SuppressWarnings({"unchecked", "unused"})
private void printParameters(JoinPoint jp) {
CodeSignature methodSignature = (CodeSignature) jp.getSignature();
String methodName = methodSignature.getName();
Object[] paramNames = methodSignature.getParameterNames();
Class[] paramTypes = (Class[])methodSignature.getParameterTypes();
Object[] paramObjects = jp.getArgs();
StringBuffer infoMsg = new StringBuffer();
infoMsg.append("Entering function: " + methodName);
if (paramNames != null && paramNames.length > 0){
if (paramNames.length == 1){
infoMsg.append(" with input parameter: ["+ paramNames[1]+ "] = [" + paramObjects[1] + "]");
}
else {
infoMsg.append(" with input parameters: ");
}
for (int i = 1; i < paramNames.length; i++) {
infoMsg.append(" [" + paramTypes[i].getName() + " " + paramNames[i]+ "] = [" + paramObjects[i] + "]");
}
}
else {
infoMsg.append(" NONE");
}
log.info(infoMsg.toString());
}
@SuppressWarnings("unused")
private void printExit(JoinPoint jp) {
log.info("Exit function: " + jp.getSignature().toString());
}
before() : allPublic() {
printParameters (thisJoinPoint);
}
after() : allPublic() {
printExit(thisJoinPoint);
}
}
The class which is supposed to be advised:
public class Main {
private static final Logger log = Logger.getLogger("A.class");
public static void doSomethingAa(int number, String message, Map<String, String> map){
log.debug("A");
}
public static void doSomethingB(int id, String name){
log.debug("B");
}
public static void main(String[] args){
Map<String, String> map1 = new TreeMap<String, String>();
Map<String, String> map2 = new TreeMap<String, String>();
map1.put("FirstKey", "FirstValue");
map1.put("SecondKey", "SecondValue");
map2.put("Tal", "Guy");
map2.put("Happy", "Birthday");
A.doSomethingAa(17, "Tal", map1);
A.doSomethingAa(35, "Guy", map2);
A.doSomethingB(12, "TalG");
A.doSomethingB(40, "GuyG");
System.out.println("Finished running main");
}
}
Thanks all!
I've not tried using aspectj in plugin development, so there might be a few additional things. But here's a few things you need to do to ensure the target is woven correctly at compile time and can be run.
Update, I've been unable to reproduce your problem (i.e. it works fine on my box). To replicate the situation I created an AspectJ project with the single Logging.aj file in the source directory. I exported that as a jar file (called logging.jar) to another project's root (the other project also set up as an AspectJ project containing the "Main" class). I then modified the Aspect Path of the "main" project to include the logging.jar and the aspects and the advice was woven to each doSomethingAa() and doSomethingB() method call.
The only issue I found with your code was that your static method calls are for "A" rather than "Main".
Here is the entry from the main project's .classpath file:
<classpathentry kind="lib" path="logging.jar">
<attributes>
<attribute name="org.eclipse.ajdt.aspectpath"
value="org.eclipse.ajdt.aspectpath"/>
</attributes>
</classpathentry>
I've tried various permutations, and the only ways I can get it to not work are by removing the AspectJ nature or removing the jar from the build path.
Are there any other factors that may be affecting your workspace that you've omitted?
One other point about your logging aspect that I found in a similar project; Separate before and after advice will result in JoinPoint instances being created twice for every method call, this can cause a problem for garbage collection if your logging type weaves a lot of methods. Instead you could consider using around advice to log both the entry and exit, this also makes it easier to add in any method execution time logging if you decide to later.
Update: Based on your comments, I added a third project (aj_client) to my workspace and went through the following steps:
Client.java contains a single method:
public static void main(String[] args) {
Main.main(args);
}
When run, this fails with a NoClassDefFoundError:
Exception in thread "main" java.lang.NoClassDefFoundError: org/aspectj/lang/Signature
at Client.main(Client.java:6)
Caused by: java.lang.ClassNotFoundException: org.aspectj.lang.Signature
To address this, I modified the .classpath of aj_client so it has aspectjrt on it (by manually adding the AspectJ Runtime Library classpath container to the .classpath) and reran, the program executes and outputs the logging statements:
Entering function: doSomethingAa with input parameters: [java.lang.String message] = [Tal] [java.util.Map map] = [{FirstKey=FirstValue, SecondKey=SecondValue}]
log4j:WARN No appenders could be found for logger (A.class).
log4j:WARN Please initialize the log4j system properly.
Exit function: void target.Main.doSomethingAa(int, String, Map)
Entering function: doSomethingAa with input parameters: [java.lang.String message] = [Guy] [java.util.Map map] = [{Happy=Birthday, Tal=Guy}]
Exit function: void target.Main.doSomethingAa(int, String, Map)
Entering function: doSomethingB with input parameters: [java.lang.String name] = [TalG]
Exit function: void target.Main.doSomethingB(int, String)
Entering function: doSomethingB with input parameters: [java.lang.String name] = [GuyG]
Exit function: void target.Main.doSomethingB(int, String)
Finished running main
The .classpath file for aj_client looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.ajdt.core.ASPECTJRT_CONTAINER"/>
<!-- the other jars for the logging and target projects -->
<classpathentry kind="lib" path="/aj_target/target.jar"/>
<classpathentry kind="lib" path="/aj_target/log4j-1.2.14.jar"/>
<classpathentry kind="lib" path="/aj_target/logging.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
I also tried pointing to my aspectjrt in my Maven repository and Eclipse plugin, with the same result (the logging messages were output), i.e. replace:
<classpathentry kind="con" path="org.eclipse.ajdt.core.ASPECTJRT_CONTAINER"/>
with
<!--aspectjrt from Maven repository-->
<classpathentry kind="lib" path="C:/maven-2.2.0/repo/aspectj/aspectjrt/1.5.3/aspectjrt-1.5.3.jar"/>
or
<!--aspectjrt from Eclipse plugin -->
<classpathentry kind="lib" path="C:/eclipse-3.5/eclipse/plugins/org.aspectj.runtime_1.6.5.20090618034232/aspectjrt.jar"/>
Having proven that the logging code is woven, I went back and changed Logging.aj to use getLog().info() calls again, and found the logging statements are no longer output. To remedy this I added a log4j.xml configuration file (just specifying the root appender)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
</layout>
</appender>
<root>
<priority value ="debug" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
This resulted in the following output:
DEBUG class - A
INFO Logging - Exit function: void target.Main.doSomethingAa(int, String, Map)
INFO Logging - Entering function: doSomethingB with input parameters: [java.lang.String name] = [TalG]
DEBUG class - B
INFO Logging - Exit function: void target.Main.doSomethingB(int, String)
INFO Logging - Entering function: doSomethingB with input parameters: [java.lang.String name] = [GuyG]
DEBUG class - B
INFO Logging - Exit function: void target.Main.doSomethingB(int, String)
Finished running main
Note You need to be careful to ensure you have cleaned, built, and exported logging.jar before cleaning, building, and exporting target.jar, then clean the client project. If you muck up the order at all you'll get mismatched content.
Summary
So it appears as long as your client project references a "target.jar" that was built with AspectJ (so the Logging.aj was woven), and you have an aspectjrt.jar on your classpath and you have configured log4j correctly the logging will be output.
You can specify the aspectjrt dependency by either adding the classpath container, or by specifying the path to a compatible aspectjrt.jar
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With