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

Yan*_*eve 5 java eclipse aspectj jar

我是aspectj的新手......

我编写了以下方面,旨在将记录添加到类型的函数调用public * doSomething*(..).如果我的主类是同一个项目的一部分,则执行方面编织时不会出现故障并执行代码.如果我将编织的代码打包到jar中并从另一个eclipse项目调用它 - 则不会执行建议.另一种情况是将方面(.aj)仅打包到一个单独的jar中并将该jar添加到eclipse中的"Aspect Path",这使得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);
    }
}
Run Code Online (Sandbox Code Playgroud)

应该被告知的班级:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

谢谢大家!

Ric*_*ler 8

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

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

更新,我一直无法重现您的问题(即它在我的盒子上工作正常).为了复制这种情况,我使用源目录中的单个Logging.aj文件创建了一个AspectJ项目.我将它作为jar文件(称为logging.jar)导出到另一个项目的根目录(另一个项目也设置为包含"Main"类的AspectJ项目).然后我修改了"main"项目的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>
Run Code Online (Sandbox Code Playgroud)

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

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


关于您在类似项目中发现的日志记录方面的另一点; 在建议之前和之后分开将导致为每个方法调用创建两次JoinPoint实例,如果您的日志记录类型编织了很多方法,这可能会导致垃圾回收问题.相反,您可以考虑使用around建议来记录入口和出口,如果您稍后决定,这也可以更容易地添加任何方法执行时间记录.


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

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

Client.java包含一个方法:

public static void main(String[] args) {
    Main.main(args);
}
Run Code Online (Sandbox Code Playgroud)

运行时,会因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
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我修改了aj_client的.classpath,使其上有aspectjrt(通过手动将AspectJ Runtime Library类路径容器添加到.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
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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

<classpathentry kind="con" path="org.eclipse.ajdt.core.ASPECTJRT_CONTAINER"/>
Run Code Online (Sandbox Code Playgroud)

<!--aspectjrt from Maven repository-->
<classpathentry kind="lib" path="C:/maven-2.2.0/repo/aspectj/aspectjrt/1.5.3/aspectjrt-1.5.3.jar"/>
Run Code Online (Sandbox Code Playgroud)

要么

<!--aspectjrt from Eclipse plugin -->
<classpathentry kind="lib" path="C:/eclipse-3.5/eclipse/plugins/org.aspectj.runtime_1.6.5.20090618034232/aspectjrt.jar"/>
Run Code Online (Sandbox Code Playgroud)

在证明编写了日志代码之后,我回过头来改变Logging.aj再次使用getLog().info()调用,并发现不再输出日志记录语句.为了解决这个问题,我添加了一个log4j.xml配置文件(只是指定了根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>
Run Code Online (Sandbox Code Playgroud)

这导致以下输出:

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
Run Code Online (Sandbox Code Playgroud)

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


摘要

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

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