Contexts and Dependency Injection (CDI)
简单来讲就是Quarkus 基于 Java JSR CDI标准的实现。类似于Spring中的Bean的管理,如果事先用过Quarkus的话就会发现,在使用Quarkus中总有点Spring的味道。今天先看一下Quarkus中CDI的基本功能。
本章目标
- 1.Quarkus中的Bean是什么样子的。
- 2.如果有多个相同类型的bean如何处理。
- 3.Bean的注入方法有哪些。
- 4.什么是qualifiers。
- 5.bean scope 有哪些。
- 6.client proxies是什么?
- 7.有哪些Bean类型。
- 8.生命周期回调。
- 9.事件回调。
- 10.拦截器。
1.Quarkus中的Bean是什么样子的
import javax.inject.Inject;
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.metrics.annotation.Counted;
@ApplicationScoped ①
public class Translator {
@Inject ②
Dictionary dictionary;
}
上面代码中Translator就是Quarkus中的一个最基本的Bean,可以看到除了使用的注解不同之外,是不是很像Spring中的Bean的用法。所以我们在学习quarkus时多对比一下Spring。
① @ApplicationScoped 表示这个类是一个被管理的Bean,不过是延迟加载的。
② @Inject 表示注入一个Bean,也就是Translator依赖Dictionary。
无论Spring还是Quarkus中Bean的定义和注入方式都是类似的,只是实现方法还需要我们以后深入探究。
2.如果有多个相同类型的bean如何处理。
我们清楚在Spring中有多个ID或者类名相同的Bean可以在注入的时候指定Bean名称,来明确要注入的Bean,那Quarkus中是如何处理的呢?
a bean is assignable to an injection point if the bean has a bean type that matches the required type and has all the required qualifiers
官方文档是这样说的,这里有两点 :一是根据类型匹配注入点的;二是满足所有的注入点限定符。
那如果是一个注入点有多个满足条件的Bean的话怎么处理呢?
- 快速失败,在构建时就会抛出
AmbiguousResolutionException
异常.
ServiceA、ServiceB实现了Service接口,我们在注入Service时,在项目启动时就会报错。
- 注入多个,使用Instance实例包裹所有符合条件的Bean。= = 简单粗暴,但不优雅。
public class Translator {
@Inject
Instance<Service> services;
@GET
@Path("/single")
public String hello(){
services.forEach(service -> {
System.out.println(service.hello());
});
return "service.hello()";
}
}
3.Bean的注入方法有哪些
- @Inject注入
- Setter方法注入
- 构造器注入
非常简单也和Spring类似,这里只讲一下set方法注入,方法必须用@Inject修饰,同时不需要定义Bean变量,每一个参数都是一个注入点,也就是说可以在一个set方法注入多个Bean。
4.什么是qualifiers
qualifiers是用来标识一个Bean的,比如在注入时,需要满足qualifiers条件的才能注入。也就是一个bean的标识。它是一个注解,我们可以自定义一个注解来修饰bean。
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Superior {}
@Superior
@ApplicationScoped
public class ServiceB implements Service{
@Override
public String hello() {
return "ServiceB";
}
}
我们在ServiceB上添加了自定义的Qualifiers注解,项目正常启动并且可以正常执行,说明加了注解之后ServiceB就不在满足注入时的条件了。
5.bean scope 有哪些
Annotation | Description |
---|---|
@javax.enterprise.context.ApplicationScoped | 一个单例Bean,同时是懒加载的,只有被调用时才会初始化 |
@javax.inject.Singleton | 类似ApplicationScoped只是没有client proxy |
@javax.enterprise.context.RequestScoped | 每一次Http Request 创建一个bean |
@javax.enterprise.context.Dependent | 这个一个伪作用域,跟随注入它的bean的生命周期 |
@javax.enterprise.context.SessionScoped | 跟随HttpSession生命周期,只在quarkus-undertow下有效 |
6.client proxies是什么
简单来说,它是一个代理对象,对bean 方法的调用都是通过它来调用的。
实际注入一个Bean时也是注入的代理对象。
暂时先简单了解这个,自己还没整明白,不敢乱写。。。
代理对象的特点:
- 懒加载,在代理对象被调用时创建实例。
- 允许注入一个比当前作用域更小的作用域。
- 允许循环依赖,思考Spring中也只允许了单例的循环依赖
In rare cases it’s practical to destroy the beans manually. A direct injected reference would lead to a stale bean instance.
还没搞懂。
7.有哪些Bean类型
- Class beans 我们上面讲的Bean都是这个类型的
- Producer methods 通过方法产生的Bean
- Producer fields 通过属性产生的Bean
- Synthetic beans 合成Bean一般是第三方工具使用的
这里讲下 2 和 3:
@ApplicationScoped
public class ProduceBean {
@Produces
double PI = 3.1415; ①
@Produces
public List<String> names(){ ②
return Arrays.asList("jack", "rose");
}
}
@Path("produces")
public class ProducesResource {
@Inject
double PI;
@Inject
List<String> names;
@GET
public String test(){
System.out.println(PI);
names.forEach(System.out::println);
return "SUCCESS";
}
}
① 这个浮点数将被注册为Bean的元数据,可以被直接注入使用,由于没有定义这个属性的作用域,所以默认为@Dependent,将跟随注入它的bean的生命周期。 我们也可以添加生命周期限定。
②类似①只是注册的是方法的返回值类型。
8.生命周期回调
@ApplicationScoped
public class Translator {
@PostConstruct
void init() {
// ...
}
@PreDestroy
void destroy() {
// ...
}
}
9.事件回调
class TaskCompleted {
// ...
}
@ApplicationScoped
class ComplicatedService {
@Inject
Event<TaskCompleted> event; ①
void doSomething() {
// ...
event.fire(new TaskCompleted()); ②
}
}
@ApplicationScoped
class Logger {
void onTaskCompleted(@Observes TaskCompleted task) { ③
// ...log the task
}
}
① 用来触发事件的
② 添加触发事件
③ @Observes 监听事件触发
10.拦截器
定义一个拦截器注解
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
@InterceptorBinding
public @interface MyInterceptor {}
实现拦截器逻辑
@com.quarkus.annotation.MyInterceptor
@Interceptor
public class MyInterceptor {
@AroundInvoke
public Object around(InvocationContext context) throws Exception {
System.out.println("before");
// 继续下一个拦截器或者运行实际逻辑
Object res = context.proceed();
System.out.println("after");
return res;
}
}
使用拦截器注解指定要拦截的方法
@MyInterceptor
@GET
public String test(){
System.out.println(PI);
names.forEach(System.out::println);
return "SUCCESS";
}
结果:
before
3.1415
jack
rose
after
总结
本章只是简单介绍了Quarkus的CDI简单使用方式,和基本概念。后面会一篇更深入的文章。