Как я могу создать Pointcut или Around для расширенных классов с общим интерфейсом? - PullRequest
0 голосов
/ 08 апреля 2020

У меня есть абстрактный класс обслуживания.

abstract class AbstractService<T> {
    public void saveNew(T entity) {
    }
}

И у меня есть еще два абстрактных класса, расширяющих AbstractService и реализующих общий интерфейс.

abstract class MoreAbstractService2<T extends Some2>
        extends AbstractService<T>
        implements SharedInterface {
}
abstract class MoreAbstractService3<T extends Some3>
        extends AbstractService<T>
        implements SharedInterface {
}

Теперь я хочу проверить аргумент entity в методе saveNew(T) этих двух расширяющихся сервисов.

Как определить @Pointcut и (или) @Around для следующих условий?

  • расширяет AbstractService класс
  • реализует SharedInterface интерфейс

Ответы [ 3 ]

1 голос
/ 08 апреля 2020

Следующий код может быть использован для упомянутой валидации.

Точка отсечения состоит в том, чтобы перехватить выполнение специфицированного c метода для подклассов AbstractService, а код logi c равен только для проверки, если SharedInterface является суперинтерфейсом целевого компонента.

Требуется использование isAssignableFrom(), поскольку интерфейсы, проксируемые прокси-сервером AOP, не включают SharedInterface. Насколько я понимаю, выражение pointcut для соответствия второму критерию будет невозможно по той же причине и, следовательно, обрабатывает требование в коде logi c.

Надеюсь, это поможет

@Aspect
@Component
public class ValidationAspect {

    @Pointcut("execution(* package.to.AbstractService+.saveNew(..))")
    public void isAbstractServiceType() {
    }

    @Around("isAbstractServiceType() && args(entity) && target(bean)")
    public void validateEntityArugments(ProceedingJoinPoint pjp, Object entity, Object bean) throws Throwable {
        if (SharedInterface.class.isAssignableFrom(bean.getClass())) {
            System.out.println(entity);
            // ..validate
        }
        pjp.proceed();
    }
}
1 голос
/ 09 апреля 2020
Решение

RG имеет несколько недостатков:

  • Точка соответствует слишком большому количеству точек соединения.
  • Таким образом, необходимо отразить, чтобы отфильтровать ненужные.

Я собираюсь показать вам автономное решение AspectJ (нет Spring, я не пользователь Spring), но аспект будет выглядеть точно так же в Spring, вам нужно только сделать его @Component или объявите фабрику @Bean в вашей конфигурации. Но это же относится и ко всем классам, которые вы хотите перехватить, конечно.

Поскольку я предпочитаю полный MCVE со всеми необходимыми классами зависимостей, чтобы вы могли копировать, компилировать и запустить его, и поскольку я также добавил отрицательные тестовые случаи (подклассы, только расширяющие абстрактный базовый класс или только реализующие интерфейс), это много кода. Поэтому, пожалуйста, потерпите меня:

Абстрактные классы, интерфейс и вспомогательные классы:

package de.scrum_master.app;

public abstract class AbstractService<T> {
  public void saveNew(T entity) {
    System.out.println("Saving new entity " + entity);
  }
}
package de.scrum_master.app;

public class Some2 {}
package de.scrum_master.app;

public class Some3 {}
package de.scrum_master.app;

public abstract class MoreAbstractService2<T extends Some2>
  extends AbstractService<T>
  implements SharedInterface {}
package de.scrum_master.app;

public abstract class MoreAbstractService3<T extends Some3>
  extends AbstractService<T>
  implements SharedInterface {}
package de.scrum_master.app;

public interface SharedInterface {
  void doSomething();
}

Приложение драйвера (AspectJ + POJO, а не Spring):

Этот класс драйвера содержит некоторые внутренние c внутренние классы, подклассифицирующие заданные базовые классы и / или реализующие общий интерфейс. Два используются для положительных тестов (должны быть перехвачены), два для отрицательных тестов (не должны быть перехвачены). Каждый класс также содержит дополнительный метод в качестве еще одного отрицательного контрольного примера, который не следует сопоставлять - лучше, чем потом сожалеть.

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    // Should be intercepted
    InterceptMe1 interceptMe1 = new InterceptMe1();
    interceptMe1.saveNew(new Some2());
    interceptMe1.doSomething();
    interceptMe1.additional();
    printSeparator();

    // Should be intercepted
    InterceptMe2 interceptMe2 = new InterceptMe2();
    interceptMe2.saveNew(new Some3());
    interceptMe2.doSomething();
    interceptMe2.additional();
    printSeparator();

    // Should NOT be intercepted
    DontInterceptMe1 dontInterceptMe1 = new DontInterceptMe1();
    dontInterceptMe1.saveNew(new Some2());
    dontInterceptMe1.additional();
    printSeparator();

    // Should NOT be intercepted
    DontInterceptMe2 dontInterceptMe2 = new DontInterceptMe2();
    dontInterceptMe2.additional();
    printSeparator();
  }

  private static void printSeparator() {
    System.out.println("\n----------------------------------------\n");
  }

  static class InterceptMe1 extends MoreAbstractService2<Some2> {
    @Override
    public void doSomething() {
      System.out.println("Doing something in MoreAbstractService2<Some2>");
    }

    public void additional() {
      System.out.println("Additional method in MoreAbstractService2<Some2>");
    }
  }

  static class InterceptMe2 extends MoreAbstractService3<Some3> {
    @Override
    public void doSomething() {
      System.out.println("Doing something in MoreAbstractService3<Some3>");
    }

    public void additional() {
      System.out.println("Additional method in MoreAbstractService3<Some3>");
    }
  }

  static class DontInterceptMe1 extends AbstractService<Some2> {
    public void additional() {
      System.out.println("Additional method in AbstractService<Some2>");
    }
  }

  static class DontInterceptMe2 implements SharedInterface {
    @Override
    public void doSomething() {
      System.out.println("Doing something in SharedInterface");    }

    public void additional() {
      System.out.println("Additional method in SharedInterface");
    }
  }

}

Аспект:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class EntityValidationAspect {
  @Before(
    "execution(* saveNew(*)) && " + 
    "args(entity) && " + 
    "target(de.scrum_master.app.SharedInterface) && " + 
    "target(de.scrum_master.app.AbstractService)"
  )
  public void validateEntity(JoinPoint thisJoinPoint, Object entity) {
    System.out.println("-> Pre-save entity validation: " + entity);
  }
}

As Вы можете видеть, что аспект использует два target() pointcut, которые должны совпадать. Он также специально нацелен на любой метод saveNew с одним аргументом saveNew(*), связывая этот аргумент как параметр метода совета через args().

Ради демонстрации я ничего не проверяю (я не знаю как вы хотите это сделать), но просто распечатать объект. Таким образом, @Before совета достаточно. Если в случае отрицательной проверки вы хотите сгенерировать исключение, этот тип совета также подойдет. Если вам нужно сделать больше, например, манипулировать состоянием сущности или заменить его перед передачей его целевому методу, вместо этого вызвать альтернативный целевой метод или вообще не вызывать его, вернуть определенный c результат (в случае не void методы (здесь не применимо), обрабатывают исключения из целевого метода и т. д. c., вместо этого следует использовать @Around рекомендацию.

Журнал консоли:

-> Pre-save entity validation: de.scrum_master.app.Some2@28a418fc
Saving new entity de.scrum_master.app.Some2@28a418fc
Doing something in MoreAbstractService2<Some2>
Additional method in MoreAbstractService2<Some2>

----------------------------------------

-> Pre-save entity validation: de.scrum_master.app.Some3@5305068a
Saving new entity de.scrum_master.app.Some3@5305068a
Doing something in MoreAbstractService3<Some3>
Additional method in MoreAbstractService3<Some3>

----------------------------------------

Saving new entity de.scrum_master.app.Some2@1f32e575
Additional method in AbstractService<Some2>

----------------------------------------

Additional method in SharedInterface

----------------------------------------

Et voilà - аспект делает именно то, что вы просили, насколько я понимаю ваше требование. :-) Более конкретно, он не запускается в третьем случае, когда вызывается saveNew, но класс не реализует интерфейс.

1 голос
/ 08 апреля 2020

можно использовать следующим образом:

 within(com.somepackage.Super+)

, где com.somepackage.Super - полное имя базового класса, а + означает «все подклассы». Другой pointcut это

execution(* com.somepackage.Super+.*(..))
...