Quarkus简易教程(一)——CDI

2,205 阅读6分钟

CDI

预览

CDI(context and dependency inject)2.0是jsr36为JakartaEE7的新特性

如果你熟悉Spring那一套IOC和AOP机制,那么这里面有些概念是相同的

比如说单例注入,声明注入,拦截器(AOP)等

我们直接来看一个例子吧

@ApplicationScoped //类似于Spring的@Component 把他作为一个bean注入到容器中
public class Translator {

    @Inject //@Autowired声明注入
    Dictionary dictionary; 

    @Counted  //拦截注解的AOP声明 JakartaEE里面我们称之为interceptor拦截器
    String translate(String sentence) {
      // ...
    }
}

细节

@Inject

它是根据什么注入的呢?由于java是强类型语言,所以其注入的必定是与它关系最近的一个类型安全的示例,即注入的bean类型是以Dictionary为上界

如果存在多个实例可以匹配呢?

会直接注入失败,此时就需要这种形式,去获取到所有的实现类自己注入

@Inject Instance<Target> targets;

其余的注入情况呢?比如说构造器注入或者setter注入呢?

package com.example;


import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class InjectBean {
    public Bean2 bean2;

    public InjectBean(){
        System.out.println("inject single");
    }

    public InjectBean(Bean1 bean1){
        System.out.println("one param");
    }

    @Inject
    public void setBean2(Bean2 bean2) {
        this.bean2 = bean2;
    }
}

setter注入很显然可以实现

那么这种情况下的构造器注入会选择哪一个构造器呢,是无参构造器

如果需要选择某一个构造器进行注入请在上面加上@Inject,如果只含有一个构造器那么会自动选择这个构造器(这里和标准CDI不太一样)

@Qualifier

这是帮助容器区分实现相同类型的bean的元注解

在讲述这个注解之前我们先来看看在容器角度看bean是什么样子的

1,bean具有一个类型 2,bean具有“修饰符”

当冲突时,只有注入点需要的bean与容器中某一个bean从类型到修饰符全部对应上才注入

对于这样的注入点@Inject Bean bean 实际上则是这样的@Inject @Default Bean bean

而这个修饰符就是一个特殊的注解,一个被@Qualifier注解的注解

举个例子:这是我写的一个修饰符注解

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.PARAMETER,ElementType.FIELD})
public @interface SuperiorBean {
}
//修饰某一个bean
@Singleton
@SuperiorBean
public class Bean2 implements Target{
}
//选择修饰符进行注入
@Inject
    public void setBean2(@SuperiorBean Bean2 bean2) {
        this.bean2 = bean2;
    }
  @Inject
  @SuperiorBean
   Target target;


@named

这个用法和我们上面自定义修饰符一致,不过他是基于字符串的,类似于指定bean name注入

@Singleton
@Named("bean1")
public class Bean1 implements Target{
}
  @Inject
    @Named("bean1")
    Target target;

bean范围Scope

@javax.enterprise.context.ApplicationScoped多个注入点共享的一个单例bean,但是是由惰性创建的(被client proxy代理创建)
@javax.inject.Singleton直接创建的单例
@javax.enterprise.context.RequestScoped与当前请求绑定的bean
@javax.enterprise.context.Dependent这是一个伪范围。 这些实例不是共享的,而且每个注入点都生成一个依赖bean的新实例。 依赖bean的生命周期绑定到注入它的bean——它将随着注入它的bean一起创建和销毁。
@javax.enterprise.context.SessionScoped. 当前javax.servlet.http.Httpsession作用域,当quarkus-undertow可用的时候才会使用

那么ApplicationScoped和Singleton有什么区别

1,注入对象不同前者实际上是class xxxxxxxx_ClientProxy是真实实例的一个子类代理

其包含这些方法

1638764929616.png

后者则是真实实例

2@ApplicationScoped bean也可以在运行时销毁和重新创建。 @ApplicationScoped bean也可以在运行时销毁和重新创建。 现有的注入点只能工作,因为注入的代理将委托给当前实例。 现有的注入点只能工作,因为注入的代理将委托给当前实例。

正是这种惰性特征,可以允许小范围的注入到大范围的,比如请求作用域注入到ApplicationScope

花样bean注入

之前我已经介绍了一种在类定义上面加注解注入的方法了,那么还有哪些方法?

@Produces

这个注解你可以简单理解为Spring中的@bean注解

@Singleton
class Producer{
    @Produces
    UUID uuid = UUID.randomUUID();

    @Produces
    public List<String> p(){
        return List.of("dreamlike.vertx","@","gmail","com");
    }

    public UUID getUuid() {
        return uuid;
    }
}

然后你可以在任意地方这样注入

  @Inject
    UUID uuid;
    @Inject
    Producer producer;
   @Inject Iterable<String> list;

当然了你也可以搭配“修饰符”注解修饰返回的bean,也支持根据方法参数生成bean,简单来说约等于Spring中的@Bean注解的使用方法

生命周期回调的方法

其对应两个注解@PostConstruct @PreDestory

即初始化完毕和销毁前,这里必须强调拥有这两个注解的方法必须是“无副作用”的

拦截器

所谓的拦截器对应到Spring中就是一个环绕通知aop,只支持增强拥有某些注解的方法

标识注解

首先要做拦截方法得先标识需要被拦截方法

@InterceptorBinding //这个就是标识这个注解作为拦截注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Mark{}

拦截逻辑

@Interceptor //标识这个是拦截器
@Mark //需要匹配的注解
class MarkInterceptor{
    //编写环绕通知逻辑
    @AroundInvoke
    Object markInvocation(InvocationContext context) {
        //context包含调用点信息比如method paramter 之类的
        System.out.println("before");
        Object ret = null;
        try {
            ret = context.proceed();
        } catch (Exception e) {
            System.out.println("after exception");
        }
        System.out.println("after");
        return ret;
    }
}

使用

@Singleton
class MarkedBean{
    @Mark
    public String s(){
        System.out.println("invoke");
        return "ad";
    }

}

拦截顺序

如果想要控制多个切面的逻辑可以通过在拦截器上面加上@Priority注解来控制顺序

@Priority(2020)
@Interceptor //标识这个是拦截器
@Mark //需要匹配的注解
class MarkInterceptor{

老样子越小 优先级越高

其余拦截逻辑

@AroundConstruct 这个用于拦截标识的构造器函数

注意如果你在context.process()前调用context.getTarget()只会得到一个null

@AroundTimeout 则是与context.getTimer()相关联的

装饰器

虽然装饰器和拦截器很像 但是装饰器得去先实现要被装饰类的接口

interface Service{
    void doSomething(String s);
}
@Decorator
class ServiceDecorator implements Service{
    @Inject @Delegate @Any //这里就是获取实际被装饰的类
    Service target;
    @Override
    public void doSomething(String s) {
        System.out.println("before ");
        target.doSomething(s);
        System.out.println("after");
    }
}

注意:Decorator模式并不是单例,仔细想想每次装饰的都不一样自然也不太可能是单例

事件通知

事件通知就是程序员负责发出一个事件,由容器去寻找监听者调用监听的方法

编写一个事件对应的实体类

一个普通的java类即可

class TaskEnd{}

编写监听者

只要在对应监听方法上面加上对应事件实体类和一个注解即可声明一个监听者

class ServiceImp{
    public void startUp(@Observes TaskEnd taskEnd,double pi){
        System.out.println("task end"+pi);
    }
}

方法的参数必须包含一个存在的事件,其余声明容器中的bean,事件触发的时候会自动注入进来

触发事件

当我们调用这个trigger的时候就可以同步地触发事件了

@Singleton
class EventTrigger{
    @Inject
    Event<TaskEnd> endEvent;
    @Produces
    double pi = 3.14;

    public void trigger(){
        endEvent.fire(new TaskEnd());
    }
}