Возможно ли компилировать AspectJ времени компиляции в проекты, упакованные в WAR? - PullRequest
0 голосов
/ 13 июня 2019

Существует много документации относительно встраивания в файлы JAR, например, примеры, описанные в книге AspectJ в действии: второе издание .

Тем не менее, я почти не вижу вопросов, связанных с переплетением файлов WAR. Из-за популярности файлов WAR и библиотеки AspectJ мне трудно поверить, что они не поддерживаются, и поэтому я надеюсь на некоторые ответы.

Допустим, у вас есть "устаревший" Java-проект, который упаковывается в файл WAR с помощью плагина maven-war-plugin. Имейте в виду, что изменить его с WAR на JAR-упаковку практически невозможно из-за сопутствующего риска.

Теперь, допустим, я хотел представить AOP для некоторых сквозных функций. В связи с характером наших приложений лучше всего создать отдельный проект. В общем, классы аспектов не будут находиться в том же проекте, что и унаследованный проект. Это будет отдельно.

После этой части я застрял. Из чтения книги и другой документации онлайн, кажется, есть два варианта для ткачества времени компиляции:

  1. Плетение источников напрямую. Это может быть сделано путем создания классов аспектов непосредственно в устаревшем проекте, а затем с помощью плагина aspectj-maven-plugin. Это не совсем вариант, потому что я хочу, чтобы моя логика AOP влияла на несколько проектов, а не только на один проект.
  2. Создайте отдельную библиотеку и скомпилируйте ее, используя плагин aspectj-maven-plugin. Затем создайте другой проект maven, который будет взят из нетканого приложения и библиотеки аспектов, и сплетет его вместе. Пример взят из книги:
<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. В случае истечения срока действия ссылки ответ на вопрос:

тебе нужно разобрать его, сплести .jar и собрать его обратно. Вы не можете напрямую сплести банку во время войны

Но возможно ли это? Я не вижу никакой документации для этого.

Есть похожие вопросы по SO, которые либо не имеют никаких ответов, либо являются неправильными / устаревшими.

Буду признателен за любую помощь в этом, так как я не знаю, каким должен быть мой следующий шаг.

1 Ответ

1 голос
/ 14 июня 2019

Я попытаюсь представить два разных решения, а затем вы сможете выбрать / выбрать или смешать / сопоставить способ реализации вашего решения.


Пример 1 : Вас попросили вывести некоторый текст в stdout до и после выполнения метода org.apache.log4j.Logger.info(Object o), найденного в log4j.

(Это очень придуманный пример, но я использую его, потому что меня попросили сделать что-то не совсем отличное в прошлом!)

Полагаю, вы используете Multi-Module Maven Project .

Для этого мы можем использовать jar log4j (скомпилированные классы), применять к ним Aspect и выплевывать новый набор скомпилированных классов во время загрузки. Мы делаем это, используя функциональность weaveDependency , которую вы уже вызывали в собственном модуле. Поэтому вместо зависимости от log4j в ваших проектах вы будете зависеть от ltwjar-enhanced-log4j, как описано ниже.

Имея это в виду, создайте иерархию вашего проекта следующим образом:

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

Настройте ваш родительский файл pom.xml так, чтобы он выглядел примерно так (мы установили пару свойств здесь только для удобства / согласованности - существуют другие способы управления версиями, такие как dependencyManagement и импортированные зависимости , но это уже другой вопрос!):

<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", чтобы использовать 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 создайте драйвер / жгут, который показывает, что все работает. Тип 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 мы "улучшаем" log4j, оборачивая совет в Logger.info(Object o)
  2. Переупаковываем log4j.jar в новую банку ltwjar-enhanced-log4j.jar, которая содержит тканый тип
  3. Мы зависим от ltwjar-enhanced-log4j.jar вместо log4j в нашем коде приложения ...
  4. ... И при этом совет придерживается

Хорошо, как это вписывается в военный файл, о котором изначально спрашивали?

Простой - не помещайте код в ваш модуль warfile. Если вам нужно что-то поместить туда, настройте его. Вместо этого переместите весь свой код в yourproject-domain или yourproject-logic или ... (введите имя модуля здесь), и ваш warfile зависит от этого модуля. Это хорошая практика, так как она включает Разделение проблем : ваше приложение не должно знать, что оно работает под зонтиком боевого файла; он может быть так же легко запущен, как Spring Boot tomcat uberwar и т. д. Забота об упаковке (война) отличается от заботы бизнеса (домена).

Сегодня вечером я обновлю другой пример библиотеки общих аспектов, если кто-то не побьет меня. Также, если что-то из этого не имеет смысла, кричите в комментариях!

...