views:

695

answers:

1

Hi all,

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!

+7  A: 

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.

  • The plugin that is being woven needs to have a dependency on the plugin containing the aspect
  • The aspect needs to be in the Exported Packages on the classpath
  • The target plugin needs to have aspectjrt on the classpath so it can handle the aspects
  • The aspectj compiler needs to be used to weave the target when it is compiled.


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:

  1. Modified Logging.aj to do System.out calls, ruling out log4j configuration issues
  2. exported aj_logging (AspectJ project containing Logging.aj) to logging.jar
  3. added logging.jar to the Aspect Path of aj_target
  4. exported aj_target (AspectJ project containing Main.java) to target.jar
  5. Created a new class (Client.java) in the aj_client project (which has no AspectJ nature).
  6. added target.jar, logging.jar (and log4j.jar) to the Java Build Path of aj_client and ran it.

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/"&gt;
  <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

Rich Seller
I think I've done all you've mentioned and still no luck...
Yaneeve
this is an interesting problem, if no-one else can answer I'll have a look when I get the chance.
Rich Seller
+1 for all the research. That will enable the bounty auto-select feature to kick in if no other better answer is proposed before the end of this bounty.
VonC
Thanks :) The class A vs. Main is just a typo. I've done all you said and that of course works. Thing is I need yet another level of indirection. I have a third eclipse project that must call on the project/jar that had the aspects woven into it without it being an aspectJ project or more specifically without having the aspects woven into its classes.
Yaneeve
What I did was wrap up the woven code into a jar and included that jar and the aspectJ jars into a normal java project classpath. I then called the functions that had been woven (I checked the bytecode to make sure the aspects really had been woven). The advice did not get invoked...
Yaneeve
Only when adding the jar containing the binary aspect (.class) to the "aspect path" (that, after setting the third project as an aspectJ project) did the woven advice finally run.
Yaneeve
Just reread your comment, so to clarify, you have 3 projects. One with an aspect in it, one with a "Main" class that is woven by the aspect, and a third that invokes the Main class, and wants the woven Main class to be executed without having aspectj on the classpath of the third project?
Rich Seller
Almost correct. I do not mind having the aspectJ runtime jar in the classpath. I wish, though, to not have to define the "Aspect Path" and to include into it the jar with the aspect.
Yaneeve
Sounds as if you have left nothing for chance :) I will get a team member on it promptly... I hope that it will work - what you did does sound a lot what I did but perhaps I did muck things up :)
Yaneeve
You do need to be very careful with the builds, ensure you clean each project so no old code is left around to be woven. When checking this I had a few false starts because I'd bundled old code in one or other jars.
Rich Seller