如何访问 WEB-INF/lib/ 罐子?

How to get access to WEB-INF/lib/ jars?

我有 Gradle 项目 Spring BootAspectJ.

想要直接从 WEB-INF/libs 动态加载 aspectjweaverspring-instrument javaagents(其中 Spring 启动定位所有依赖项)

Gradle 依赖关系:

AgentLoader:

public class AgentLoader {

private static final Logger LOGGER = LoggerFactory.getLogger(AgentLoader.class);

public static void loadJavaAgent() {
    if (!isAspectJAgentLoaded()) {
        LOGGER.warn("Aspect agent was not loaded!");
    }
}

public static boolean isAspectJAgentLoaded() {
    try {
        Agent.getInstrumentation();
    } catch (NoClassDefFoundError e) {
        return false;
    } catch (UnsupportedOperationException e) {
        LOGGER.info("Dynamically load AspectJAgent");
        return dynamicallyLoadAspectJAgent();
    }
    return true;
}

public static boolean dynamicallyLoadAspectJAgent() {
    String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
    int p = nameOfRunningVM.indexOf('@');
    String pid = nameOfRunningVM.substring(0, p);
    try {
        VirtualMachine vm = VirtualMachine.attach(pid);
        String jarFilePath = AgentLoader.class.getClassLoader().getResource("WEB-INF/libs/aspectjweaver-1.9.6.jar").toString();
        vm.loadAgent(jarFilePath);
        jarFilePath = AgentLoader.class.getClassLoader().getResource("WEB-INF/libs/spring-instrument-5.3.2.jar").toString();
        vm.loadAgent(jarFilePath);
        vm.detach();
    } catch (Exception e) {
        LOGGER.error("Exception while attaching agent", e);
        return false;
    }
    return true;
 }
}

但发现 null

getResource() 的 return 值

处理此问题的最佳解决方案是什么?

尼基塔,今天是你的幸运日。我只是有片刻时间,很好奇如何从 https://www.eclipse.org/aspectj/doc/released/README-187.html 制作我的代码片段,这显然是您之前发现的,可以在 Spring Boot 的上下文中工作。我刚刚使用了我的 Maven Spring Boot playground 项目。根据您使用的 Java 版本,您需要确保 JDK 8 中的 tools.jar 被定义为系统范围的依赖项并且也被复制到可执行文件 Spring uber JAR,或者你需要确保 Java attach API 在 Java 9+ 中被激活。这是我为 Java 8:

所做的

Maven:

<dependency>
  <groupId>com.sun</groupId>
  <artifactId>tools</artifactId>
  <version>1.8</version>
  <scope>system</scope>
  <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
<!-- (...) -->
<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <mainClass>spring.aop.DemoApplication</mainClass>
    <!-- Important for tools.jar on Java 8 -->
    <includeSystemScope>true</includeSystemScope>
  </configuration>
</plugin>

<includeSystemScope> 选项是必需的,否则 Boot 不知道如何找到附加 API classes。只需在 Gradle 中做一些等效的事情就可以了。

Java:

您需要知道,为了附加一个代理,它必须是文件系统上的一个文件,而不仅仅是任何资源或输入流。这就是附加 API 的工作原理。所以不幸的是,您必须先将它从 uber JAR 复制到文件系统。以下是您的操作方法:

public static boolean dynamicallyLoadAspectJAgent() {
  String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
  int p = nameOfRunningVM.indexOf('@');
  String pid = nameOfRunningVM.substring(0, p);
  try {
    VirtualMachine vm = VirtualMachine.attach(pid);
    ClassLoader classLoader = AgentLoader.class.getClassLoader();

    try (InputStream nestedJar = Objects.requireNonNull(classLoader.getResourceAsStream("BOOT-INF/lib/aspectjweaver-1.9.4.jar"))) {
      File targetFile = new File("aspectjweaver.jar");
      java.nio.file.Files.copy(nestedJar, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
      vm.loadAgent(targetFile.getAbsolutePath());
    }
    try (InputStream nestedJar = Objects.requireNonNull(classLoader.getResourceAsStream("BOOT-INF/lib/spring-instrument-5.1.9.RELEASE.jar"))) {
      File targetFile = new File("spring-instrument.jar");
      java.nio.file.Files.copy(nestedJar, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
      vm.loadAgent(targetFile.getAbsolutePath());
    }

    vm.detach();
  }
  catch (Exception e) {
    LOGGER.error("Exception while attaching agent", e);
    return false;
  }
  return true;
}

此外,在我的例子中,文件在 BOOT-INF/lib 之下,而不是 WEB-INF/lib


更新: 你说你在某个地方遇到了这个后续问题(为了便于阅读而重新格式化):

failed to access class
  org.aspectj.weaver.loadtime.Aj$WeaverContainer
from class
  org.aspectj.weaver.loadtime.Aj
(
  org.aspectj.weaver.loadtime.Aj$WeaverContainer is in
    unnamed module of
    loader 'app';
  org.aspectj.weaver.loadtime.Aj is in
    unnamed module of
    loader org.springframework.boot.loader.LaunchedURLClassLoader @3e9b1010
)
at org.aspectj.weaver.loadtime.Aj.preProcess(Aj.java:108)

这意味着Aj无法找到自己的内部classAj.WeaverContainer。这表明它们在不同的时间点和不同的 classloader 中加载。当从可执行 JAR 开始远程调试我的示例 Boot 应用程序时,我看到应用程序 classloader 实际上是 LaunchedURLClassLoader 的父级,即在父级中加载的 class 是试图访问另一个 class 仅可用于其子 classloader,这在 Java 中是不可能的。反之亦然。

也许不从代理加载程序内部导入和引用 AspectJ 编织器 classes 会有所帮助。尝试注释掉 loadJavaAgent()isAspectJAgentLoaded() 方法并删除 import org.aspectj.weaver.loadtime.Agent;。然后在您的应用程序中直接调用 AgentLoader.dynamicallyLoadAspectJAgent() 并查看是否有帮助。关于代理加载,我有更多的技巧,但我们先让它尽可能简单。