6

我是aspectj的新手...

我编写了以下方面,旨在将日志记录添加到类型的函数调用中public * doSomething*(..)。如果我的主类是同一个项目的一部分,则方面的编织不会出现故障并且代码会执行。如果我将编织的代码打包到一个 jar 中并从另一个 Eclipse 项目中调用它 - 建议不会执行。另一种情况是仅将方面(.aj)打包到一个单独的 jar 中,并将该 jar 添加到 eclipse 中的“方面路径”中,这使 eclipse 能够正确地编织方面。事情是我需要把它包装成一个罐子,然后从其他地方调用代码。那也不起作用(我想这并不奇怪......)为什么?

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);
    }
}

应该被告知的课程:

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");

    }

}

谢谢大家!

4

1 回答 1

9

我没有尝试在插件开发中使用 aspectj,所以可能还有一些额外的东西。但是,您需要做一些事情来确保目标在编译时正确编织并且可以运行。

  • 正在编织的插件需要依赖于包含方面的插件
  • 方面需要在类路径上的导出包中
  • 目标插件需要在类路径上有 aspectjrt,以便它可以处理切面
  • 编译时需要使用aspectj编译器来编织目标。

更新,我无法重现您的问题(即它在我的盒子上运行良好)。为了复制这种情况,我使用源目录中的单个 Logging.aj 文件创建了一个 AspectJ 项目。我将它作为 jar 文件(称为 logging.jar)导出到另一个项目的根目录(另一个项目也设置为包含“Main”类的 AspectJ 项目)。然后我修改了“主”项目的 Aspect Path 以包括 logging.jar 和方面,并且建议被编织到每个 doSomethingAa() 和 doSomethingB() 方法调用中。

我在您的代码中发现的唯一问题是您的静态方法调用是针对“A”而不是“Main”的。

这是主项目的 .classpath 文件中的条目:

<classpathentry kind="lib" path="logging.jar">
  <attributes>
    <attribute name="org.eclipse.ajdt.aspectpath"
        value="org.eclipse.ajdt.aspectpath"/>
  </attributes>
</classpathentry>

我尝试了各种排列,唯一能让它不起作用的方法是删除 AspectJ 特性或从构建路径中删除 jar。

是否还有其他可能影响您的工作空间的因素被您忽略了?


我在一个类似项目中发现的关于您的日志记录方面的另一点;单独的 before 和 after 通知将导致 JoinPoint 实例为每个方法调用创建两次,如果您的日志记录类型编织了很多方法,这可能会导致垃圾收集问题。相反,您可以考虑使用 around 建议来记录进入和退出,如果您决定稍后再添加任何方法执行时间记录,这也使得添加任何方法执行时间记录变得更加容易。


更新:根据您的评论,我将第三个项目(aj_client)添加到我的工作区并执行了以下步骤:

  1. 修改 Logging.aj 以执行 System.out 调用,排除 log4j 配置问题
    • 将 aj_logging(包含 Logging.aj 的 AspectJ 项目)导出到 logging.jar
    • 将 logging.jar 添加到 aj_target 的 Aspect Path
    • 将 aj_target(包含 Main.java 的 AspectJ 项目)导出到 target.jar
    • 在 aj_client 项目(没有 AspectJ 特性)中创建了一个新类 (Client.java)。
    • 将 target.jar、logging.jar(和 log4j.jar)添加到 aj_client 的 Java 构建路径并运行它。

Client.java 包含一个方法:

public static void main(String[] args) {
    Main.main(args);
}

运行时,这会失败并出现 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

为了解决这个问题,我修改了 aj_client 的 .classpath,使其上有 aspectjrt(通过手动将AspectJ 运行时库类路径容器添加到 .classpath)并重新运行,程序执行并输出日志语句:

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

aj_client 的 .classpath 文件如下所示:

<?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>

我还尝试在我的 Maven 存储库和 Eclipse 插件中指向我的 aspectjrt,结果相同(输出日志消息),即替换:

<classpathentry kind="con" path="org.eclipse.ajdt.core.ASPECTJRT_CONTAINER"/>

<!--aspectjrt from Maven repository-->
<classpathentry kind="lib" path="C:/maven-2.2.0/repo/aspectj/aspectjrt/1.5.3/aspectjrt-1.5.3.jar"/>

或者

<!--aspectjrt from Eclipse plugin -->
<classpathentry kind="lib" path="C:/eclipse-3.5/eclipse/plugins/org.aspectj.runtime_1.6.5.20090618034232/aspectjrt.jar"/>

在证明日志代码是编织的之后,我返回并更改 Logging.aj 以再次使用 getLog().info() 调用,发现日志语句不再输出。为了解决这个问题,我添加了一个 log4j.xml 配置文件(只需指定根附加程序)

<?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>

这导致以下输出:

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

注意 在清理、构建和导出 target.jar 之前,您需要小心确保已清理、构建和导出 logging.jar,然后清理客户端项目。如果你把订单搞砸了,你会得到不匹配的内容。


概括

因此,只要您的客户端项目引用了使用 AspectJ 构建的“target.jar”(因此 Logging.aj 被编织),并且您的类路径上有一个aspectjrt.jar并且您已正确配置 log4j,它就会出现被输出。

您可以通过添加类路径容器或指定兼容的 aspectjrt.jar 的路径来指定 aspectjrt 依赖项

于 2009-08-12T11:08:29.643 回答