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.