CDI Interceptor vs Spring AspectJ

CDI Interceptor vs Spring AspectJ

1. Вступление

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

В этой статье мы рассмотрим и сопоставим две из этих основных библиотек: перехватчики CDI и Spring AspectJ.

2. Настройка проекта перехватчика CDI

CDI официально поддерживается для Java EE, но некоторые реализации предоставляют поддержку для использования CDI в среде Java SE. Weld можно рассматривать как один из примеров реализации CDI, которая поддерживается в Java SE.

Чтобы использовать CDI, нам нужно импортировать библиотеку Weld в наше POM:


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

Самую последнюю библиотеку Weld можно найти в репозиторииMaven.

А теперь создадим простой перехватчик.

3. Представляем CDI Interceptor

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

@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 и может выдаватьException.

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

4. Примените CDI Interceptor

Давайте применим созданный перехватчик к некоторой бизнес-логике:

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

Мы создали этот простой сервис и аннотировали метод, который мы хотели перехватить, аннотацией@Audited.

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


    
        com.example.interceptor.AuditedInterceptor
    

Чтобы убедиться, что перехватчик действительно сработалlet’s now run the following test:

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);
    }
}

В этом быстром тесте мы сначала получаем bean-компонентSuperService из контейнера, затем вызываем для него бизнес-методdeliverService и проверяем, действительно ли был вызван перехватчикAuditedInterceptor, проверяя его переменные состояния.

Также у нас есть аннотированные методы@Before и@After, в которых мы инициализируем и завершаем работу контейнера Weld соответственно.

5. Рекомендации по CDI

Мы можем указать на следующие преимущества CDI-перехватчиков:

  • Это стандартная функция спецификации Java EE

  • Некоторые библиотеки реализаций CDI могут использоваться в Java SE

  • Может использоваться, когда у нашего проекта есть серьезные ограничения на сторонние библиотеки

Недостатками CDI-перехватчиков являются следующие:

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

  • Трудно понять, какие классы перехватываются в проекте

  • Отсутствие гибкого механизма применения перехватчиков к группе методов

6. Spring AspectJ

Spring поддерживает аналогичную реализацию функциональности перехватчика с использованием синтаксиса AspectJ.

Сначала нам нужно добавить следующие зависимости Spring и AspectJ в POM:


    org.springframework
    spring-context
    4.3.1.RELEASE


    org.aspectj
    aspectjweaver
    1.8.9

Самые последние версииSpring context,aspectjweaver можно найти в репозитории Maven.

Теперь мы можем создать простой аспект, используя синтаксис аннотации AspectJ:

@Aspect
public class SpringTestAspect {
    @Autowired
    private List accumulator;

    @Around("execution(* com.example.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. Spring AspectJ 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();
    }
}

Один важный аспект здесь, в аннотации@EnableAspectJAutoProxy, - который обеспечивает поддержку обработки компонентов, отмеченных аннотацией AspectJ's@Aspect, аналогично функциональности, обнаруженной в элементе XML Spring.

8. Особенности Spring AspectJ

Отметим несколько преимуществ использования Spring AspectJ:

  • Перехватчики отделены от бизнес-логики

  • Перехватчики могут извлечь выгоду из введения зависимости

  • Перехватчик имеет всю информацию о конфигурации

  • Добавление новых перехватчиков не потребует дополнения существующего кода.

  • Перехватчик имеет гибкий механизм выбора методов перехвата

  • Может использоваться без Java EE

И конечно несколько недостатков:

  • Вам нужно знать синтаксис AspectJ для разработки перехватчиков

  • Кривая обучения для перехватчиков AspectJ выше, чем для перехватчиков CDI

9. CDI Interceptor VS Spring AspectJ

Если ваш текущий проект использует Spring, то выбор Spring AspectJ - хороший выбор.

Если вы используете полноценный сервер приложений или ваш проект не использует Spring (или другие фреймворки, например, Google Guice) и является строго Java EE, то ничего не остается, кроме как выбрать перехватчик CDI.

10. Заключение

В этой статье мы рассмотрели две реализации шаблона перехватчика: перехватчик CDI и Spring AspectJ. Мы рассмотрели достоинства и недостатки каждого из них.

Исходный код примеров для этой статьи можно найти в нашем репозитории наGitHub.