Несогласованные результаты сборки при использовании AspectJ Load Time Weaving в приложении с весенней загрузкой

В настоящее время я использую AspectJ Load Time Weaving для перехвата конструктора базового объекта в целях аудита. Однако при запуске приложения я получаю невероятно противоречивые результаты, связанные с методом аспекта(), который аспект J вплетает в классы LTW.

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

java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf() 

В настоящее время я использую https://github.com/subes/invesdwin-instrument для динамического подключения агент инструментирования в JVM, поэтому нашим специалистам по развертыванию не нужно выполнять дополнительную настройку.

Мое основное приложение Spring:

@SpringBootApplication
@EntityScan(basePackages = {"ca.gc.cfp.model"})
public class CfpWsApplication {

  public static void main(final String[] args) {

    DynamicInstrumentationLoader.waitForInitialized();
    DynamicInstrumentationLoader.initLoadTimeWeavingContext();

    if (!InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
      throw new IllegalStateException(
          "Instrumentation is not available AspectJ weaver will not function.");
    }

    SpringApplication.run(CfpWsApplication.class, args);
  }

Аспект LTW:

@Aspect
public class BaseEntityAspect {
  Logger logger = LoggerFactory.getLogger(BaseEntityAspect.class);

  /** Application context property needed to fetch the AuditDate bean */
  @Autowired private ApplicationContext context;

  @AfterReturning(
      "onBaseEntityCreated() && !within(ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect)")
  public void injectAuditTimeStamp(JoinPoint joinPoint) {
    try {
      AuditDate auditDate = context.getBean(AuditDate.class);
      Object entityTarget = joinPoint.getTarget();

      // Inject the auditing date for this Entity instance
      if (entityTarget instanceof BaseEntity) {
        BaseEntity baseEnt = (BaseEntity) entityTarget;
        baseEnt.setAuditDate(auditDate.getAuditingTimeStamp());
      }
    } catch (NullPointerException e) {
      logger.error(
          e.getMessage()
              + " Not yet in the conext of an httpRequest, the AuditDate bean has not yet been instantiated.");
    }
  }

  @Pointcut(
      "execution(ca.gc.cfp.model.entity.BaseEntity.new(..)) && !within(ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect)")
  public void onBaseEntityCreated() {}
}

Класс конфигурации Aspect с временным фабричным методом, использующим утилиты Aspects, чтобы сообщить Spring, что он должен запросить у aspectJ тканый Aspect:

@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {

  /**
   * Static factory for access to the load time woven aspect. This allows the aspect to be injected
   * with the application context and beans from the spring IoC
   */
  @Bean
  public BaseEntityAspect getBaseEntityAspect() {
    return Aspects.aspectOf(BaseEntityAspect.class);
  }
}

aop.xml:

<aspectj>
    <weaver options="-verbose -showWeaveInfo -Xreweavable -debug">
        <include within="ca.gc.cfp.model" />
        <include within="ca.gc.cfp.model..*" /> 
        <include within="ca.gc.cfp.core.cfpws.repository.aspect..*"/>
    </weaver>
    <aspects>
        <aspect name="ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect" />
    </aspects>
</aspectj>

Во многих случаях, когда я запускаю эту конфигурацию, я получаю следующее:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect]: Factory method 'getBaseEntityAspect' threw exception; nested exception is org.aspectj.lang.NoAspectBoundException: Exception while initializing ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    ... 19 common frames omitted
Caused by: org.aspectj.lang.NoAspectBoundException: Exception while initializing ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
    at org.aspectj.lang.Aspects.aspectOf(Aspects.java:50) ~[aspectjrt-1.9.4.jar:1.9.4]
    at ca.gc.cfp.core.cfpws.configuration.AspectConfig.getBaseEntityAspect(AspectConfig.java:22) ~[classes/:na]
    at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$$1cae4c58.CGLIB$getBaseEntityAspect$0(<generated>) ~[classes/:na]
    at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$$1cae4c58$$FastClassBySpringCGLIB$$84edb9e.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at ca.gc.cfp.core.cfpws.configuration.AspectConfig$$EnhancerBySpringCGLIB$$1cae4c58.getBaseEntityAspect(<generated>) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_211]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_211]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_211]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_211]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    ... 20 common frames omitted
Caused by: java.lang.NoSuchMethodException: ca.gc.cfp.core.cfpws.repository.aspect.BaseEntityAspect.aspectOf()
    at java.lang.Class.getDeclaredMethod(Class.java:2130) ~[na:1.8.0_211]
    at org.aspectj.lang.Aspects.getSingletonOrThreadAspectOf(Aspects.java:134) ~[aspectjrt-1.9.4.jar:1.9.4]
    at org.aspectj.lang.Aspects.aspectOf(Aspects.java:45) ~[aspectjrt-1.9.4.jar:1.9.4]
    ... 31 common frames omitted

Однако это не всегда так, иногда приложение работает нормально, код выполняется так, как ожидалось, и проблем не возникает. Это ускользает от меня уже пару недель, и я не могу понять, что может быть причиной того, что этот код иногда работает, а иногда не работает. Может ли быть так, что прокси-серверы CGLIB скрывают метод aspectOf() от компилятора??

EDIT/UPDATE: мне удалось удалить использование вышеуказанной сторонней зависимости для динамической загрузки java-агента в контекст приложения Spring. Вместо этого я использовал аргументы javaagent, и он отлично работает в моей среде IDE. Однако создание и запуск через Maven в терминале по-прежнему вызывают проблемы. Я указал аргумент javaagent через переменную среды: MAVEN_OPTS. После этого maven, кажется, улавливает это, но мой класс все еще не сплетен.


person Pensai    schedule 04.09.2019    source источник
comment
Или, может быть, ваша сборка IDE и сборка maven сбивают друг друга? Один настроен правильно, а другой нет?   -  person Kieveli    schedule 04.09.2019


Ответы (2)


Что касается вашего собственного ответа: плагин AspectJ Maven помогает вам с переплетением времени компиляции (CTW), а не переплетением времени загрузки (LTW). Для LTW необходимо убедиться, что агент плетения активен до того, как будет загружен какой-либо из целевых классов, поскольку LTW работает на уровне загрузчика классов. AspectJ Maven необходим только в том случае, если вы используете аспекты в собственном синтаксисе (не на основе аннотаций) или хотите использовать CTW.

Я ничего не знаю об этом инструменте invesdwin-instrument, но ткач AspectJ предлагает собственную возможность присоединения его во время выполнения. Сторонний инструмент не должен быть необходим. В любом случае, я рекомендую изменить командную строку Java, чтобы убедиться, что агент плетения находится на месте до того, как что-либо еще будет загружено в ваш контейнер. Специалисты по развертыванию должны помочь вам убедиться, что параметр -javaagent:... присутствует. Это их работа! Обойти это, чтобы облегчить им жизнь, но потенциально непредсказуемое поведение вашего приложения не улучшит его стабильность.

person kriegaex    schedule 05.09.2019
comment
Интересно, спасибо за дополнительные подробности. AspectJrt, weaver и инструменты находятся в моем пути к классам. С включенным LoadTimeWeaving вы говорите, что мне не нужны никакие дополнительные инструменты для обеспечения доступности агента во время выполнения, если я укажу параметр -javaagent в командной строке? - person Pensai; 05.09.2019
comment
Еще одна вещь, с точки зрения влияния на остальную часть команды разработчиков, я предполагаю, что им также потребуется перенастроить свою среду Eclipse, чтобы учесть это изменение. Другая проблема заключается в том, что некоторые люди используют Maven с терминала, который, похоже, не знает или не загружает соответствующий javaagent. Я чувствую, что что-то вроде этого может быть полезно: stackoverflow.com/questions/14777909/ - person Pensai; 05.09.2019
comment
(1) Вам нужен только aspectjweaver.jar в пути к классам, а не два других. Справочная информация: Weaver — это надмножество runtime, tools — это надмножество weaver, которое в данном случае вам не нужно. не нужно, потому что вы не хотите использовать содержащийся в нем компилятор. (2) При правильной настройке дополнительные инструменты не требуются, см. Руководство по Spring. (3) Да, команде необходимо настроить свои конфигурации запуска IDE. (4) Возможен запуск тестов или приложений с агентом Java от Maven. - person kriegaex; 06.09.2019
comment
Простая и безопасная альтернатива всему этому — использовать переплетение во время компиляции через плагин AspectJ Maven и полностью избавиться от переплетения во время загрузки. Тогда ваше приложение также будет запускаться быстрее, потому что во время загрузки не требуется плетения. Кроме того, вам нужен только aspectjrt.jar (среда выполнения AspectJ) в пути к классам, очень маленький JAR размером всего 120 КБ. Для производственных аспектов, которые всегда должны быть сотканы, это всегда мой выбор. Только аспекты отладки или отслеживания, которые должны быть активны только тогда, когда вы хотите проанализировать проблемы, указывают на использование LTW. - person kriegaex; 06.09.2019
comment
Я ценю ваши подробные ответы на этот kriegaex, они были очень проницательными. Я рассмотрю переплетение времени компиляции, если оно будет быстрее, я думаю, это будет что-то важное для рассмотрения. По мере роста нашего приложения время запуска увеличивалось, и мы хотели бы сохранить его на низком уровне. Я собираюсь принять ваш пост в качестве ответа, поскольку наши обсуждения были достаточно проницательными, чтобы заставить меня двигаться в правильном направлении и лучше понять вещи. - person Pensai; 06.09.2019

Согласно комментарию Киевели. Мой TL думал так же. Я новичок в Maven, Spring boot и AOP. Глядя на это извне, Maven и сборка из Eclipse кажутся «забивающими» друг друга.

Я использовал maven через MINGW64 для очистки/установки/сборки, и в проекте не указано использование компилятора AspectJ, что, вероятно, является причиной того, что фабричный метод не вплетен в класс.

Мы обсуждаем решение этой проблемы, поскольку используем Jenkins для автоматизации сборки на нашем сервере. Я думаю, что этот плагин Maven может быть решением. https://www.mojohaus.org/aspectj-maven-plugin/

person Pensai    schedule 04.09.2019