1. введение
Шаблон перехватчика обычно используется для добавления новых сквозных функций или логики в приложение и имеет надежную поддержку в большом количестве библиотек.
В этой статье мы рассмотрим и сравним две из этих основных библиотек: перехватчики CDI и Spring AspectJ.
2. Настройка проекта перехватчика CDI
CDI официально поддерживается для Jakarta EE, но некоторые реализации обеспечивают поддержку использования CDI в среде Java SE. Weld можно рассматривать как один из примеров реализации CDI, которая поддерживается в Java SE.
Чтобы использовать CDI, нам нужно импортировать библиотеку сварных швов в наш POM:
org.jboss.weld.se weld-se-core 3.0.5.Final
Самую последнюю библиотеку сварных швов можно найти в репозитории 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.перехватчик.Аргумент InvocationContext возвращает java.lang.Объект , и может вызвать Исключение .
И поэтому, когда мы аннотируем метод с помощью нового интерфейса @Audit , сначала будет вызван метод audit, и только затем будет запущен целевой метод.
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); } }
В этом быстром тесте мы сначала получаем компонент Super Service из контейнера, затем вызываем на нем бизнес-метод deliver Service и проверяем, что перехватчик AuditedInterceptor был фактически вызван путем проверки его переменных состояния.
Также у нас есть @Before и @After аннотированные методы, в которых мы инициализируем и выключаем Weldcontainer соответственно.
5. Соображения CDI
Мы можем отметить следующие преимущества перехватчиков CDI:
- Это стандартная функция спецификации Jakarta EE
- Некоторые библиотеки реализаций CDI могут использоваться в Java SE
- Может использоваться, когда наш проект имеет серьезные ограничения на сторонние библиотеки
Недостатками перехватчиков CDI являются следующие:
- Тесная связь между классом с бизнес-логикой и перехватчиком
- Трудно понять, какие классы перехватываются в проекте
- Отсутствие гибкого механизма применения перехватчиков к группе методов
6. Весенний аспект
Spring также поддерживает аналогичную реализацию функций перехватчика с использованием синтаксиса AspectJ.
Сначала нам нужно добавить следующие зависимости Spring и AspectJ в POM:
org.springframework spring-context 5.2.8.RELEASE org.aspectj aspectjweaver 1.9.2
Самые последние версии Spring context , aspectj weaver можно найти в репозитории 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; } }
Мы создали аспект, который применим ко всем методам класса Spring Super Service , который для простоты выглядит следующим образом:
public class SpringSuperService { public String getInfoFromService(String code) { return code; } }
7. Весенний аспект
Чтобы проверить, действительно ли этот аспект применим к службе, давайте напишем следующий модульный тест:
@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 @Aspect , аналогичной функциональности, найденной в XML-элементе Spring.
8. Соображения по весеннему аспекту
Давайте укажем на некоторые преимущества использования Spring AspectJ:
- Перехватчики отделены от бизнес – логики
- Перехватчики могут извлечь выгоду из внедрения зависимостей
- Перехватчик имеет всю информацию о конфигурации в себе
- Добавление новых перехватчиков не потребует дополнения существующего кода
- Перехватчик имеет гибкий механизм для выбора методов перехвата
- Может использоваться без использования EE
И конечно же несколько недостатков:
- Вам необходимо знать синтаксис AspectJ для разработки перехватчиков
- Кривая обучения для перехватчиков AspectJ выше, чем для перехватчиков CDI
9. Перехватчик CDI против Spring AspectJ
Если ваш текущий проект использует Spring, то рассмотрение Spring AspectJ является хорошим выбором.
Если вы используете полномасштабный сервер приложений или ваш проект не использует Spring (или другие фреймворки, например Google Guice) и строго ориентирован на EE, то вам ничего не остается, как выбрать перехватчик CDI.
10. Заключение
В этой статье мы рассмотрели две реализации шаблона перехватчика: CDI interceptor и Spring AspectJ. Мы рассмотрели преимущества и недостатки каждого из них.
Исходный код для примеров этой статьи можно найти в нашем репозитории на GitHub .