【发布时间】:2021-04-16 22:30:37
【问题描述】:
我知道 JakartaEE(以前的 JavaEE)从 JavaEE 6 开始就支持 CDI。
据我所知,SpringBoot 没有 CDI,只有 DI。
SpringBoot 是否支持 CDI(上下文和依赖注入)或提供一些替代方案?
【问题讨论】:
标签: spring-boot dependency-injection cdi java-ee-8
我知道 JakartaEE(以前的 JavaEE)从 JavaEE 6 开始就支持 CDI。
据我所知,SpringBoot 没有 CDI,只有 DI。
SpringBoot 是否支持 CDI(上下文和依赖注入)或提供一些替代方案?
【问题讨论】:
标签: spring-boot dependency-injection cdi java-ee-8
整个 Spring Framework 本身实际上是一个替代方案。这里的不同之处在于它在 Spring 中没有那么清晰地分离——而是它是 Spring 框架错综复杂的一部分。
Spring 框架为 CDI 中的几乎所有功能提供了替代方案。为了论证和完整性,我们可以做一个比较,讨论异同;
在 CDI 中,您可以通过以下方式定义可注入或托管类:
@[ManagedBean / Named]
@[ApplicationScoped / Dependant / RequestScoped / Singleton / ViewScoped]
public class MyClass {
@PostConstruct private void init() { ... }
@PreDestroy private void destroy() { ... }
}
等价于相同定义的弹簧如下所示:
@[Bean / Controller / Component / Repository / Service]
public class MyClass {
@PostConstruct private void init() { ... }
@PreDestroy private void destroy() { ... }
}
当一个类在 Spring 中定义为 @Component 或 @Bean 时,它也可以采用范围:
@[Bean / Component]
@Scope([
SCOPE_APPLICATION / SCOPE_PROTOTYPE / SCOPE_REQUEST / SCOPE_SESSION /
SCOPE_SINGLETON / "websocket"
])
public class MyClass { ... }
就像 CDI 一样,Spring 也是可扩展的,如果开发人员需要特殊行为,可以使用其他范围进行扩展。
使用 CDI,我们通过使用 @Inject 注释成员来注入并让框架注入托管对象。在 Spring 中注入托管对象的等效注解是 @Autowired。
在 CDI 中定义信号时,我们会注入如下成员:
@Inject
private Event<Payload> event;
然后我们将通过调用event.fire(new Payload()) 或event.fireAsync(new Payload()) 向所有侦听器发送信号。一个事件监听器(或观察者,因为它们在 CDI 中被调用)然后将通过向托管对象添加一个类似这样的方法来定义:
public void onPayload(@Observes Payload event) { ... }
以类似的方式,Spring 应用程序中的信号定义如下:
@Autowired
private ApplicationEventPublisher event;
在 Spring 中,然后通过调用 event.publishEvent(new Payload()) 触发事件。 Spring 中事件监听器的定义略有不同:
@Component
public class Observer {
@EventListener public void onPayload(Payload payload) { ... }
}
在 CDI 中,我们可以通过定义生产者来创建“生产”特定对象的方法。这些本质上就像工厂一样。以后可以通过使用@Inject 注入这些对象来创建这些对象。默认情况下,生产者是@Dependant,但他们也可以定义范围。
@RequestScoped
public class LoggerFactory {
@Produces
public Logger createLogger() {
return new Logger();
}
}
要在 Spring 中做同样的事情,我们会这样做:
@Configuration
public class LoggerFactory {
@Bean @Scope(SCOPE_REQUEST)
public Logger createLogger() {
return new Logger();
}
}
当然,您可以在 Spring 中使用 @Autowired 来注入这些对象的实例,而不是使用 @Inject。
拦截器允许我们用自定义行为“修改”或“装饰”方法。在 CDI 中,我们通常首先定义一个注解。这个注解之后会被我们的自定义代码用来装饰方法:
@Target( { METHOD, TYPE } ) @Retention( RUNTIME )
public @interface MyInterceptor { }
然后我们为那个拦截器定义一个行为:
@Interceptor @MyInterceptor
public class MyInterceptorHandler {
@AroundInvoke public Object interception(InvocationContext ctx) throws Exception {
/* You can do something additional here ... */
Object result = ctx.proceed();
/* ... and/or here ... */
return result;
}
}
所以在这里我们可以准确地选择我们想要插入额外代码的位置以及何时继续调用装饰方法。要装饰拦截方法,您可以执行以下操作:
@ApplicationScoped
public class MyService {
@MyInterceptor public String operation(String str) {
return str;
}
}
在 Spring 中,它既相似又不同。 Spring 本身并没有专门的类似功能,而是在框架中添加了对 AspectJ 的支持。因此,要在 Spring 中创建类似的拦截器,您可以执行以下操作:
@Aspect
public class MyInterceptorHandler {
@Around("execution("*com.mycompany.project.MyService.*(..))")
public Object interception(ProceedingJoinPoint jp) throws Throwable {
/* You can do something additional here ... */
Object result = jp.proceed();
/* ... and/or here ... */
return result;
}
}
此时我们不需要再做任何事情,因为拦截将应用于MyService的所有方法,由@Around注解指定。
所以这篇文章有点冗长。但它确实显示了 CDI 和 Spring 之间的一些异同。使用这个小方法,对于最常见的任务,如果您了解 Spring,则可以轻松迁移到 CDI,反之亦然。
【讨论】:
Spring 是single-vendor 产品。 CDI 是由几个不同的公司和产品(Weld、OpenWebBeans)实现的multi-vendor standard,从版本 6 开始在 Java EE 中,从版本 8 开始在 Jakarta EE 中。Spring 和 CDI 都实现了JSR-330 specification,但是除此之外,彼此完全没有关系。
【讨论】: