是否可以在编译时将 AspectJ 编织到 WAR 打包项目中?

Is it possible to compile-time weave AspectJ into WAR packaged projects?

有很多关于编织成 JAR 文件的文档,例如书中描述的示例 AspectJ in Action: Second Edition

但是,我几乎看不到任何关于编入 WAR 文件的问题。由于 WAR 文件和 AspectJ 库的流行,我很难相信它不受支持,所以我希望得到一些答案。

假设您有一个 "legacy" Java 项目,该项目正在使用 maven-war-plugin 插件打包为 WAR 文件。请记住,由于所涉及的风险,将其从 WAR 更改为 JAR 包装几乎是不可能的。

现在,假设我想为一些横切功能引入 AOP。由于我们应用程序的性质,我最好的选择是创建一个单独的项目。所以基本上,方面 类 不会与遗留项目在同一个项目中。它将是分开的。

在这部分之后,我卡住了。从阅读本书和其他在线文档来看,编译时编织似乎有两种选择:

  1. 直接编织资源。这可以通过直接在遗留项目中创建方面 类 来完成,然后使用 aspectj-maven-plugin 插件。这不是一个真正的选择,因为我希望我的 AOP 逻辑影响多个项目,而不仅仅是一个项目。
  2. 创建一个单独的库并使用 aspectj-maven-plugin 插件编译它。然后创建另一个 maven 项目,它将采用 unwoven 应用程序和方面库,并将它们编织在一起。书中的例子:
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <weaveDependencies>
                    <weaveDependency>
                        <groupId>ajia.helloworld</groupId>
                        <artifactId>application-unwoven</artifactId>
                    </weaveDependency>
                </weaveDependencies>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>ajia.helloworld</groupId>
                        <artifactId>profiling-aspect-library</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
            </configuration>
        </plugin>
    </plugins>
</build>

第二个选项的问题是 <weaveDependency> 要求应用程序是 JAR 文件。至少,据我所知。

我确实在 AspectJ 邮件列表上遇到过类似的问题:http://aspectj.2085585.n4.nabble.com/Weaving-into-a-war-file-td2082924.html。 如果 link 过期,问题的答案是:

you need to take it apart, weave the .jar and put it back together. You cannot directly weave the jar within the war

但这可能吗?我也没有看到任何相关文档。

SO 上有类似的问题,要么根本没有任何回复,要么 wrong/outdated。

我将不胜感激,因为我不知道我的下一步应该是什么。

我将尝试提出两种截然不同的解决方案,然后您可以pick/choose 或 mix/match 如何实施您的解决方案。


示例 1:您被要求将一些文本转储到 [=18= 中找到的 org.apache.log4j.Logger.info(Object o) 方法的 stdout 前和 post-execution ].

(这是一个非常人为的例子,但我使用它是因为我被要求做一些与过去并不完全不同的事情!)

我假设您使用的是 Multi-Module Maven Project

为了实现这一点,我们可以使用 log4j jar(已编译 类),对它们应用一个方面,并吐出一组新的 load-time-woven 已编译 类.我们通过利用您已经在其自己的模块中调用的 weaveDependency 功能来实现这一点。因此,您将不再依赖项目中的 log4j,而是依赖 ltwjar-enhanced-log4j,如下所述。

考虑到这一点,创建您的项目层次结构:

ltwjar
      /ltwjar-ltwjar-enhanced-log4j
      /ltwjar-runtime

将您的父级 pom.xml 设置为如下所示(我们在这里设置了几个属性只是为了 convenience/consistency - 还有其他方法可以控制版本控制,例如 dependencyManagement and imported depdendencies ,但那是针对不同的问题!):

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>ltwjar</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <aspectj.version>1.8.13</aspectj.version>
        <log4j.version>1.2.17</log4j.version>
    </properties>

    <modules>
        <module>ltwjar-enhanced-log4j</module>
        <module>ltwjar-runtime</module>
    </modules>
</project>

像这样设置您的 ltwjar-enhanced-log4j pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>ltwjar</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>ltwjar-enhanced-log4j</artifactId>

    <dependencies>
        <!-- we need this because the post-weave classes may (will) depend on aspectj types -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>

        <!-- ATTENTION! Scope this to provided otherwise it won't work - because of transitive dependencies,
        anything that depends on this module will end up getting the _actual_ log4j classes versus the ones we are enriching! -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.11</version>
                <configuration>
                    <!-- instruct aspectj to weave the classes in the jar! -->
                    <weaveDependencies>
                        <weaveDependency>
                            <groupId>log4j</groupId>
                            <artifactId>log4j</artifactId>
                        </weaveDependency>
                    </weaveDependencies>
                    <Xlint>warning</Xlint>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

现在让我们设置我们的存根 "runtime" 模块以利用 load-time-wove log4j ltw-enhanced-log4j 通过配置其 pom.xml 进行记录,因此:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example</groupId>
    <artifactId>ltwjar</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>ltwjar-runtime</artifactId>
  <dependencies>
    <!-- Note that this module doesn't care (itself) about aspectj.
         It _does_ care that it depends on the ltwjar-enhanced-log4j module
         and not log4j, however. So let's depend on that! -->
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>ltwjar-enhanced-log4j</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

好的,这就是我们的框架设置。现在让我们创建一个毫无意义的方面来证明这一点!

ltwjar-enhanced-log4j 中创建类型 com.example.aspect.LoggingAspect,如下所示:

package com.example.aspect;

import org.apache.log4j.Logger;

public aspect LoggingAspect {
    // this pointcut will match "info(Object o)" method executions where the
    // target is an instanceof Logger
    pointcut logInfoWithObject(Object obj, Logger logger) :
         execution(void info(Object)) && args(obj) && target(logger);

    // here is our advice - simply sysout something before the execution
    // proceeds and after it has finished - regardless of outcome (exception
    // or not).
    void around(Object obj, Logger logger) : logInfoWithObject(obj, logger) {
        System.out.println("Before Logger.info(Object o)");

        try {
            proceed(obj, logger);
        } finally {
            System.out.println("After Logger.info(Object o)");
        }
    }
}

最后在 ltwjar-runtime 中,创建一个显示一切正常的 driver/harness。输入 com.example.Driver:

package com.example;

import org.apache.log4j.Logger;

public class Driver {
    private static final Logger LOG = Logger.getLogger(Driver.class);

    public static void main(String[] args) {
        LOG.info("In main");
    }
}

现在从您的父项目 运行 mvn clean install 然后 mvn -pl ltwjar-runtime exec:java -Dexec.mainClass="com.example.Driver".

输出应该是这样的(因为你的类路径上没有 log4j.xml):

Before Logger.info(Object o)
log4j:WARN No appenders could be found for logger (com.example.Driver).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
After Logger.info(Object o)

注意第一行和最后一行。

这里发生了什么:

  1. ltwjar-enhanced-log4j 中,我们 "enhance" log4j 通过围绕 Logger.info(Object o)
  2. 包装一个建议
  3. 我们将 log4j.jar 重新打包到一个包含编织类型的新罐子 ltwjar-enhanced-log4j.jar
  4. 我们在应用程序代码中依赖 ltwjar-enhanced-log4j.jar 而不是 log4j...
  5. ...在这样做的过程中,建议坚持

好的,那么这如何适应最初询问的 war 文件?

简单 - 不要在 war 文件模块中放置任何代码。如果你必须把东西放在那里,让它配置。相反,将所有代码移动到 yourproject-domainyourproject-logic 或 ...(在此处插入模块名称)并让您的 war 文件依赖于 that 模块。这是一个很好的做法,因为它启用了 Separation of Concerns:您的应用程序不应该知道它 运行 在 war 文件保护伞下;它可以像 Spring Boot tomcat uberwar 一样容易 运行ning 等。包装问题 (war) 不同于业务问题 (域)。

今晚我将更新共享方面库的另一个示例,除非有人抢先一步。另外,如果其中一些没有意义,请在评论中大声疾呼!