CDI Interceptor vs Spring AspectJ – CDI拦截器 VS Spring AspectJ

最后修改: 2016年 8月 17日

1. Introduction

1.介绍

The Interceptor pattern is generally used to add new, cross-cutting functionality or logic in an application, and has solid support in a large number of libraries.

拦截器模式通常用于在应用程序中添加新的、交叉的功能或逻辑,并在大量的库中得到坚实的支持。

In this article, we’ll cover and contrast two of these major libraries: CDI interceptors and Spring AspectJ.

在这篇文章中,我们将介绍和对比这些主要库中的两个。CDI拦截器和Spring AspectJ。

2. CDI Interceptor Project Setup

2.CDI拦截器项目设置

CDI is officially supported for Jakarta EE but some implementations provide support to use CDI in Java SE environment. Weld can be considered as one example of CDI implementation which is supported in Java SE.

CDI被正式支持在Jakarta EE中,但是一些实现提供了在Java SE环境中使用CDI的支持。Weld可以看作是在Java SE中支持的CDI实现的一个例子。

In order to use CDI we need to import the Weld library in our POM:

为了使用CDI,我们需要在我们的POM中导入Weld库。

<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se-core</artifactId>
    <version>3.0.5.Final</version>
</dependency>

The most recent Weld library can be found in the Maven repository.

最新的Weld库可以在Maven资源库中找到。

Let’s now create a simple interceptor.

现在我们来创建一个简单的拦截器。

3. Introducing the CDI Interceptor

3.介绍CDI拦截器

In order to designate classes we needed to intercept, let’s create the interceptor binding:

为了指定我们需要拦截的类,让我们创建拦截器绑定。

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

After we’ve defined the interceptor binding we need to define the actual interceptor implementation:

在我们定义了拦截器绑定之后,我们需要定义实际的拦截器实现。

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

Every @AroundInvoke method takes a javax.interceptor.InvocationContext argument, returns a java.lang.Object, and can throw an Exception.

每个@AroundInvoke方法都需要一个javax.interceptor.InvocationContext参数,返回一个java.lang.Object,并且可以抛出一个Exception

And so, when we annotate a method with the new @Audit interface, auditMethod will be invoked first, and only then the target method proceeds as well.

因此,当我们用新的@Audit接口注释一个方法时,auditMethod将被首先调用,然后才是目标方法也被调用。

4. Apply the CDI Interceptor

4.应用CDI拦截器

Let’s apply the created interceptor on some business logic:

让我们把创建的拦截器应用于一些业务逻辑。

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

We’ve created this simple service and annotated the method we wanted to intercept with the @Audited annotation.

我们已经创建了这个简单的服务,并用@Audited注解来注解我们想要拦截的方法。

To enable the CDI interceptor one need to specify the full class name in the beans.xml file, located in the META-INF directory:

要启用CDI拦截器,需要在beans.xml文件中指定全类名称,该文件位于META-INF目录中。

<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/beans_1_2.xsd">
    <interceptors>
        <class>com.baeldung.interceptor.AuditedInterceptor</class>
    </interceptors>
</beans>

To validate that interceptor has indeed worked 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);
    }
}

In this quick test, we first get the bean SuperService from the container, then invoke business method deliverService on it and check that interceptor AuditedInterceptor was actually called by validating its state variables.

在这个快速测试中,我们首先从容器中获取Bean SuperService,然后对其调用业务方法deliverService,并通过验证其状态变量检查拦截器AuditedInterceptor是否真的被调用。

Also we have @Before and @After annotated methods in which we initialize and shutdown Weld container respectively.

此外,我们还有@Before@After注释的方法,在这些方法中我们分别初始化和关闭Weld容器。

5. CDI Considerations

5.CDI的考虑因素

We can point out the following advantages of CDI interceptors:

我们可以指出CDI拦截器的以下优势。

  • It is a standard feature of Jakarta EE specification
  • Some CDI implementations libraries can be used in Java SE
  • Can be used when our project has severe limitations on third-party libraries

The disadvantages of the CDI interceptors are the following:

CDI拦截器的缺点如下。

  • Tight coupling between class with business logic and interceptor
  • Hard to see which classes are intercepted in the project
  • Lack of flexible mechanism to apply interceptors to a group of methods

6. Spring AspectJ

6.Spring AspectJ[/strong

Spring supports a similar implementation of interceptor functionality using AspectJ syntax as well.

Spring也支持使用AspectJ语法来实现类似的拦截器功能。

First we need to add the following Spring and AspectJ dependencies to POM:

首先,我们需要在POM中添加以下Spring和AspectJ的依赖项。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>

The most recent versions of Spring context, aspectjweaver can be found in the Maven repository.

Spring contextaspectjweaver的最新版本可以在Maven资源库中找到。

We can now create a simple aspect using AspectJ annotation syntax:

现在我们可以使用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;
    }
}

We created an aspect which applies to all the methods of SpringSuperService class – which, for simplicity, looks like this:

我们创建了一个适用于SpringSuperService类的所有方法的方面–为了简单起见,它看起来像这样。

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

7. Spring AspectJ Aspect Apply

7.Spring AspectJ Aspect Apply

In order to validate that aspect really applies to the service, let’s write the following unit test:

为了验证该方面真的适用于服务,让我们写出以下单元测试。

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

In this test we inject our service, call the method and check the result.

在这个测试中,我们注入我们的服务,调用方法并检查结果。

Here’s what the configuration looks like:

下面是配置的样子。

@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.

这里的一个重要方面是@EnableAspectJAutoProxy注解–它能够支持处理用AspectJ的@Aspect注解标记的组件,类似于Spring的XML元素中的功能。

8. Spring AspectJ Considerations

8.Spring AspectJ的注意事项

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

让我们指出使用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
  • The Learning curve for the AspectJ interceptors is higher than for the CDI interceptors

9. CDI Interceptor vs Spring AspectJ

9.CDI拦截器与Spring AspectJ的对比

If your current project uses Spring then considering Spring AspectJ is a good choice.

如果你目前的项目使用Spring,那么考虑Spring AspectJ是一个不错的选择。

If you are using a full-blown application server, or your project doesn’t use Spring (or other frameworks eg Google Guice) and is strictly Jakarta EE then there is nothing left than to choose the CDI interceptor.

如果你使用的是一个完整的应用服务器,或者你的项目没有使用Spring(或其他框架,如Google Guice),而是严格意义上的Jakarta EE,那么除了选择CDI拦截器之外,就没有别的办法了。

10. Conclusion

10.结论

In this article we have covered two implementations of interceptor pattern: CDI interceptor and Spring AspectJ. We have covered advantages and disadvantages each of them.

在这篇文章中,我们已经介绍了拦截器模式的两种实现。CDI拦截器和Spring AspectJ。我们已经介绍了它们各自的优势和劣势。

The source code for examples of this article can be found in our repository on GitHub.

本文实例的源代码可以在我们在GitHub上的存储库中找到。