AspectJ AOP LTW 不适用于 javaagent 的动态加载

Posted

技术标签:

【中文标题】AspectJ AOP LTW 不适用于 javaagent 的动态加载【英文标题】:AspectJ AOP LTW not working with dynamic loading of javaagent 【发布时间】:2018-08-05 10:40:35 【问题描述】:

Here is my sample non-working project.

它包含2个模块:

aop-lib - 用作库的方面。它包含以下类
    Wrap.java - 用于附加建议的注解 WrapDef.java - 就是上面提到的Wrap注解的定义。
aop-app - 使用上述方面库
    DynamicLoad.java - 动态加载 javaagent 的类 Main.java - 使用 Wrap 注释的主类。

目录结构如下:

.
├── README.md
├── aop-app
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── aop
│                       └── app
│                           ├── DynamicLoad.java
│                           └── Main.java
└── aop-lib
    ├── pom.xml
    └── src
        └── main
            └── java
                └── com
                    └── aop
                        └── app
                            └── lib
                                ├── Wrap.java
                                └── WrapDef.java

我正在尝试通过加载时间编织 (LTW) 在 aop-app 中使用方面库 aop-lib(一个 AOP 建议库),方法是动态加载 official docs 中提到的 javaagent。但它不起作用。

以下是Wrap.java的内容

@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Wrap  

以下是WrapDef.java的内容

@Aspect
public class WrapDef 
    private static final Logger logger = LoggerFactory.getLogger(WrapDef.class);

    public static boolean loaded = false;

    @Around("@annotation( wrapAnnotation ) && execution(* *(..))")
    public Object processSystemRequest(final ProceedingJoinPoint pjp, Wrap wrapAnnotation)
            throws Throwable 
        logger.debug("before wrap");
        Object o = pjp.proceed();
        logger.debug("after wrap");
        return o;
    

    static 
        System.out.println("Loading");
        WrapDef.loaded = true;
    
    public static void reportLoaded() 
        System.out.println("loaded : " + loaded);
    

以下是Main.java的内容:

public class Main 

    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    @Wrap
    public void myFunc()
        logger.debug("inside myFunc");
    

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException 

        boolean dynamicLoad = Boolean.getBoolean("dynamicLoad");
        if(dynamicLoad)
            Main.isAdviceClassLoaded();           //To see if WrapDef.java is loaded or not.
            if(!DynamicLoad.isAspectJAgentLoaded()) 
                logger.error("AspectJ Not Loaded. Existing.");
                System.exit(0);
            
            Main.isAdviceClassLoaded();           //To see if WrapDef.java is loaded or not.
        

        new Main().myFunc();
    

    private static void isAdviceClassLoaded() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException 
        java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
        m.setAccessible(true);
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Object test1 = m.invoke(cl, "com.aop.app.lib.WrapDef");
        boolean loaded = test1 != null;
        System.out.println("com.aop.app.lib.WrapDef Loaded : " + loaded);
    


使用javaagent 作为 cmd line arg,它工作得非常好:

$ java -javaagent:deploy/lib/aspectjweaver-1.9.1.jar -classpath aop-app-1.0.jar:deploy/lib/* com.aop.app.Main
14:02:45.384 [main] DEBUG com.aop.app.lib.WrapDef - before wrap
14:02:45.391 [main] DEBUG com.aop.app.Main - inside myFunc
14:02:45.391 [main] DEBUG com.aop.app.lib.WrapDef - after wrap

但是,通过动态加载javaagent,它会给出以下输出:

$ java -DdynamicLoad=true -DAGENT_PATH=deploy/lib/aspectjweaver-1.9.1.jar -classpath aop-app-1.0.jar:deploy/lib/* com.aop.app.Main
com.aop.app.lib.WrapDef Loaded : false                   //The WrapDef is NOT loaded before JAVAAGENT is Loaded - which is correct
java.lang.UnsupportedOperationException: AspectJ weaving agent was neither started via '-javaagent' (preMain) nor attached via 'VirtualMachine.loadAgent' (agentMain)
loading javaAgent deploy/lib/aspectjweaver-1.9.1.jar
loaded javaAgent deploy/lib/aspectjweaver-1.9.1.jar      //The JAVAAGENT is Dynamically Loaded - which is correct
com.aop.app.lib.WrapDef Loaded : false                   //The WrapDef is STILL NOT loaded even AFTER JAVAAGENT is Loaded - THIS IS THE ISSUE
15:53:08.543 [main] DEBUG com.aop.app.Main - inside myFunc

official docs 确实说any classes loaded before attachment will not be woven。但是,正如您在上面的输出中看到的相反,WrapDef 类根本没有加载。

另外,请注意,我在 aop-lib/pom.xml 中使用了 aspectj-maven-plugin,具有以下选项:

<outxml>true</outxml>                           //creates META-INF/aop-ajc.xml
<showWeaveInfo>true</showWeaveInfo>             //supposed to create <weaver options="-showWeaveInfo"/> BUT DOES NOT WORK
<verbose>true</verbose>                         //supposed to create <weaver options="-verbose"/> BUT DOES NOT WORK

因此,它在aop-lib-1.0.jar 内创建META-INF/aop-ajc.xml,内容如下:

<aspectj>
<aspects>
<aspect name="com.aop.app.lib.WrapDef"/>
</aspects>
</aspectj>

但是showWeaveInfoverbose对应的其他标签没有在META-INF/aop-ajc.xml中创建。这是另一件在这里行不通的事情。

如果您需要任何其他信息 - 我会提供。

感谢任何帮助。

【问题讨论】:

【参考方案1】:

解释很简单:您正在直接在类Main 中测试编织代理,该类已经在您从该类附加该代理之前 加载。因此,您必须避免过早加载您喜欢编织的类。我建议您将方法myFunc()(顺便说一句,这个名字很可怕)放到另一个类中。怎么样?

package com.aop.app;

import com.aop.app.lib.Wrap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Application 
  private static final Logger logger = LoggerFactory.getLogger(Application.class);

  @Wrap
  public void myFunc()
    logger.debug("inside myFunc");
  

  public static void main(String[] args) 
    new Application().myFunc();
  

然后在Main.main(..) 的最后一行启动你想要编织的实际应用程序:

Application.main(null);

这将产生以下输出:

com.aop.app.lib.WrapDef Loaded : false
java.lang.UnsupportedOperationException: AspectJ weaving agent was neither started via '-javaagent' (preMain) nor attached via 'VirtualMachine.loadAgent' (agentMain)
loading javaAgent aop-app/target/deploy/lib/aspectjweaver-1.9.1.jar
loaded javaAgent aop-app/target/deploy/lib/aspectjweaver-1.9.1.jar
com.aop.app.lib.WrapDef Loaded : false
Loading
07:56:21.703 [main] DEBUG com.aop.app.lib.WrapDef - before wrap
07:56:21.716 [main] DEBUG com.aop.app.Application - inside myFunc
07:56:21.716 [main] DEBUG com.aop.app.lib.WrapDef - after wrap

P.S.:你真的认为你的切面库的用户在 JVM 命令行上指定两个属性而不是只使用-javaagent:/path/to/aspectweaver.jar 更容易吗?无论如何,您可能有理由使用动态编织器附件。在某种程度上,我很高兴有人使用我不久前自己添加到 AspectJ 的功能。 ;-)

【讨论】:

感谢您的回答。你能回答一下为什么&lt;showWeaveInfo&gt;true&lt;/showWeaveInfo&gt; 没有在aop.xml 中创建&lt;weaver options="-showWeaveInfo"/&gt; 标记吗?我该怎么做才能让它发生? 另外,我在使用 com.sun.tools.jar 时遇到问题,因为它没有加载到服务器上的 jre 类路径中。我在这里问了一个问题 - ***.com/questions/51707812/… 。你也可以在这里帮我吗? 问题 1:AspectJ Maven 是用于编译时编织的,它对 LTW 没有任何影响。您必须按照您想要的方式自定义您的 aop.xml,例如你可以使用 Maven 资源插件或任何看起来合适的东西。如果您对默认文件不满意,将完整文件作为资源提供而不使用&lt;outxml&gt; 会更容易。问题 2:您需要 JDK 来实现动态编织器附件技巧,而不仅仅是 JRE。我仍然建议使用标准的 Java 代理机制而不是这种魔法(尽管它是我自己发明的)。 我使用的是 jdk 本身,而不是 jre(我在链接的问题中提到过)。即使那样,它也没有从lib/ 文件夹中挑选罐子。我还提到了我用来运行 java 项目的命令,即/usr/local/java/jdk1.8.0_161/bin/java 那么祝你在另一个问题上好运。这里超出了范围。这是一个类路径问题,我相信你会解决的。 P.S.:而且您仍然不相信我应该从命令行使用 Java 代理来省去麻烦,是吗?您仍然宁愿添加其他命令行开关,摆弄 JDK 类路径等,而不是仅仅依赖 JRE 和已建立的 Java 代理机制。你想要它,现在解决它。

以上是关于AspectJ AOP LTW 不适用于 javaagent 的动态加载的主要内容,如果未能解决你的问题,请参考以下文章

Spring AOP 与 AspectJ

SpringBoot中使用LoadTimeWeaving技术实现AOP功能

Spring详解------AspectJ 实现AOP

Spring的AOP AspectJ切入点语法详解(转)

Spring详解------AspectJ 实现AOP

什么是AOP?AspectJ了解一下!