Перехоплювач CDI проти Spring AspectJ

1. Вступ

Шаблон Interceptor, як правило, використовується для додавання нових, наскрізних функціональних можливостей або логіки в додатку, і має надійну підтримку у великій кількості бібліотек.

У цій статті ми розглянемо та порівняємо дві з цих основних бібліотек: перехоплювачі CDI та Spring AspectJ.

2. Налаштування проекту перехоплювача CDI

CDI офіційно підтримується для Jakarta EE, але деякі реалізації забезпечують підтримку використання CDI в середовищі Java SE. Зварювання можна розглядати як один із прикладів реалізації CDI, який підтримується в Java SE.

Для використання CDI нам потрібно імпортувати бібліотеку Weld у наш POM:

 org.jboss.weld.se weld-se-core 3.0.5.Final 

Найновішу бібліотеку Weld можна знайти у сховищі Maven.

Давайте тепер створимо простий перехоплювач.

3. Представляємо перехоплювач CDI

Для того, щоб позначити класи, які нам потрібно було перехопити, давайте створимо прив'язку перехоплювача:

@InterceptorBinding @Target( { METHOD, TYPE } ) @Retention( RUNTIME ) public @interface Audited { }

Після того, як ми визначили прив'язку перехоплювача, нам потрібно визначити фактичну реалізацію перехоплювача:

@Audited @Interceptor public class AuditedInterceptor { public static boolean calledBefore = false; public static boolean calledAfter = false; @AroundInvoke public Object auditMethod(InvocationContext ctx) throws Exception { calledBefore = true; Object result = ctx.proceed(); calledAfter = true; return result; } }

Кожен метод @AroundInvoke приймає аргумент javax.interceptor.InvocationContext , повертає java.lang.Object і може створити виняток .

Отже, коли ми анотуємо метод за допомогою нового інтерфейсу @Audit , спочатку буде викликаний auditMethod , а вже потім також продовжується цільовий метод.

4. Застосуйте перехоплювач CDI

Давайте застосуємо створений перехоплювач на деякій бізнес-логіці:

public class SuperService { @Audited public String deliverService(String uid) { return uid; } }

Ми створили цю просту службу та прокоментували метод, який ми хотіли перехопити, анотацією @Audited .

Щоб увімкнути перехоплювач CDI, потрібно вказати повне ім'я класу у файлі beans.xml , який знаходиться в каталозі META-INF :

  com.baeldung.interceptor.AuditedInterceptor  

Щоб перевірити, чи справді спрацював перехоплювач, давайте запустимо наступний тест :

public class TestInterceptor { Weld weld; WeldContainer container; @Before public void init() { weld = new Weld(); container = weld.initialize(); } @After public void shutdown() { weld.shutdown(); } @Test public void givenTheService_whenMethodAndInterceptorExecuted_thenOK() { SuperService superService = container.select(SuperService.class).get(); String code = "123456"; superService.deliverService(code); Assert.assertTrue(AuditedInterceptor.calledBefore); Assert.assertTrue(AuditedInterceptor.calledAfter); } }

У цьому швидкому тесті ми спочатку отримуємо компонент SuperService з контейнера, а потім викликаємо на ньому бізнес-метод DeliveryService і перевіряємо, чи справді викликався перехоплювач AuditedInterceptor , перевіряючи його змінні стану.

Також у нас є анотовані методи @Before та @After, в яких ми ініціалізуємо та вимикаємо контейнер Weld відповідно.

5. Міркування щодо CDI

Ми можемо вказати на такі переваги перехоплювачів CDI:

  • Це стандартна особливість специфікації Джакарти EE
  • Деякі бібліотеки реалізації CDI можуть використовуватися в Java SE
  • Може використовуватися, коли наш проект має серйозні обмеження щодо сторонніх бібліотек

Недоліками перехоплювачів CDI є наступні:

  • Тісне зв’язок між класом з бізнес-логікою та перехоплювачем
  • Важко зрозуміти, які класи перехоплюються в проекті
  • Відсутність гнучкого механізму застосування перехоплювачів до групи методів

6. Весняний аспектJ

Spring підтримує подібну реалізацію функцій перехоплювача, використовуючи також синтаксис AspectJ.

Спочатку нам потрібно додати наступні залежності Spring та AspectJ до POM:

 org.springframework spring-context 5.2.8.RELEASE   org.aspectj aspectjweaver 1.9.2 

Найновіші версії контексту Spring, аспектjweaver можна знайти у сховищі Maven.

Тепер ми можемо створити простий аспект, використовуючи синтаксис анотацій AspectJ:

@Aspect public class SpringTestAspect { @Autowired private List accumulator; @Around("execution(* com.baeldung.spring.service.SpringSuperService.*(..))") public Object auditMethod(ProceedingJoinPoint jp) throws Throwable { String methodName = jp.getSignature().getName(); accumulator.add("Call to " + methodName); Object obj = jp.proceed(); accumulator.add("Method called successfully: " + methodName); return obj; } }

Ми створили аспект, який застосовується до всіх методів класу SpringSuperService - який для простоти виглядає так:

public class SpringSuperService { public String getInfoFromService(String code) { return code; } }

7. Весняний аспектJ Aspect Apply

Для того, щоб перевірити цей аспект насправді стосується послуги, давайте напишемо наступний модульний тест:

@RunWith(SpringRunner.class) @ContextConfiguration(classes = { AppConfig.class }) public class TestSpringInterceptor { @Autowired SpringSuperService springSuperService; @Autowired private List accumulator; @Test public void givenService_whenServiceAndAspectExecuted_thenOk() { String code = "123456"; String result = springSuperService.getInfoFromService(code); Assert.assertThat(accumulator.size(), is(2)); Assert.assertThat(accumulator.get(0), is("Call to getInfoFromService")); Assert.assertThat(accumulator.get(1), is("Method called successfully: getInfoFromService")); } }

У цьому тесті ми вводимо наш сервіс, викликаємо метод і перевіряємо результат.

Ось як виглядає конфігурація:

@Configuration @EnableAspectJAutoProxy public class AppConfig { @Bean public SpringSuperService springSuperService() { return new SpringSuperService(); } @Bean public SpringTestAspect springTestAspect() { return new SpringTestAspect(); } @Bean public List getAccumulator() { return new ArrayList(); } }

One important aspect here in the @EnableAspectJAutoProxy annotation – which enables support for handling components marked with AspectJ's @Aspect annotation, similar to functionality found in Spring's XML element.

8. Spring AspectJ Considerations

Let's point out a few of the advantages of using Spring AspectJ:

  • Interceptors are decoupled from the business logic
  • Interceptors can benefit from dependency injection
  • Interceptor has all the configuration information in itself
  • Adding new interceptors wouldn't require augmenting existing code
  • Interceptor has flexible mechanism to choose which methods to intercept
  • Can be used without Jakarta EE

And of course a few of the disadvantages:

  • You need to know the AspectJ syntax to develop interceptors
  • Крива навчання для перехоплювачів AspectJ вища, ніж для перехоплювачів CDI

9. Перехоплювач CDI проти Spring AspectJ

Якщо ваш поточний проект використовує Spring, тоді розгляд Spring AspectJ - хороший вибір.

Якщо ви використовуєте повномасштабний сервер додатків, або ваш проект не використовує Spring (або інші фреймворки, наприклад, Google Guice) і є строго Джакартою EE, тоді нічого не залишається, як вибрати перехоплювач CDI.

10. Висновок

У цій статті ми розглянули дві реалізації шаблону перехоплювача: перехоплювач CDI та Spring AspectJ. Ми розглянули переваги та недоліки кожного з них.

Вихідний код для прикладів цієї статті можна знайти в нашому сховищі на GitHub.