为什么我的方面在其原始设置中执行,而不是打包为单独的 jar 并从其他地方调用时执行?

发布于 2024-07-30 17:47:52 字数 3090 浏览 3 评论 0原文

我是方面的新手...

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

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

    }

}

谢谢大家!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

浊酒尽余欢 2024-08-06 17:47:52

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

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

更新,我无法重现您的问题(即它在我的盒子上运行良好)。 为了复制这种情况,我在源目录中使用单个 Logging.aj 文件创建了一个 AspectJ 项目。 我将其作为 jar 文件(称为logging.jar)导出到另一个项目的根目录(另一个项目也设置为包含“Main”类的 AspectJ 项目)。 然后,我修改了“主”项目的方面路径以包含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小路。

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


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


更新:根据您的评论,我在工作区中添加了第三个项目 (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,得到相同的结果(日志记录messages were output),即替换:

<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"/>

证明了日志记录代码已编织完毕,我返回并更改 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

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


总结

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

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

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
    • exported aj_logging (AspectJ project containing Logging.aj) to logging.jar
    • added logging.jar to the Aspect Path of aj_target
    • exported aj_target (AspectJ project containing Main.java) to target.jar
    • Created a new class (Client.java) in the aj_client project (which has no AspectJ nature).
    • 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/">
  <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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文