5. Tigase内核

Tigas内核是为Tigase XMPP服务器创建的 IoC 的实现。它负责维护对象生命周期,并为bean之间的依赖关系解析提供机制。

此外,作为可选功能,Tigase内核能够使用提供的bean配置器来配置bean。

5.1. 基本

5.1.1. 什么是内核?

内核是 Kernel 类的一个实例,它负责管理 bean 的范围和可见性。内核处理 bean:

  • 一个bean的注册

  • 一个bean的注销

  • 一个bean的初始化

  • 一个bean的去初始化

  • bean的依赖注入

  • bean生命周期的处理

  • 基于注解的附加bean的注册 (作为 defaultBeanConfigurator 可选的使用注册类实现 BeanConfigurator

  • bean的配置 (作为 defaultBeanConfigurator 可选的通过注册类实现 BeanConfigurator )

内核负责依赖解性析和维护bean的生命周期。其他功能,比如正确配置bean是由在内核中工作的附加bean完成的。

内核通过名称来识别bean,因此每个内核可能只有一个名为 abc 的bean。如果多个bean具有相同的名称,那么最后一个注册此名称的将被使用,因为其注册将覆盖以前注册的bean。您可以使用任何您想在内核中命名的bean名称,但它不能:

  • service 因为这个名称是Tigase内核在使用 RegistrarBean 时在内部使用的(请参阅 RegistrarBean)

  • #KERNEL 结尾,因为Tigase内核也在内部使用此名称

小技巧

内核使用延迟初始化来初始化bean。这意味着,如果任何其他bean都不需要此bean,或者没有从内核手动检索此bean,则不会创建实例。

在注册bean期间,内核会检查是否有任何bean需要这个新注册的bean,如果是,则将创建新注册的bean的实例并将其注入到需要它的字段中。

5.1.2. 什么是内核范围?

每个内核都有自己的范围,可以在其中查找bean。默认情况下,注入依赖项时的内核可能仅在创建bean的新实例的同一个内核实例中或在直接父内核中查找它们。通过这种方式,可以在不同的内核范围内拥有相同名称的单独bean。

备注

如果 bean 被标记为 exportable,它在所有后代内核范围内也是可见的。

5.1.3. 什么是bean?

bean是类的命名实例,它有无参数构造函数并在内核中注册。

警告

无参数构造函数是必需的,因为内核将使用它来创建bean的实例,请参阅 bean生命周期

5.2. bean的生命周期

5.2.1. 创建一个bean的实例

一个bean的实例化

在此步骤中,内核创建该bean注册的类的实例(有关更多详细信息,请参阅 注册一个bean)。使用类的无参数构造函数创建bean的实例。

备注

Bean实例只为所需的bean(即注入某处的bean)创建。

备注

可以创建bean实例而不需要在任何地方注入它 - 这样的bean应该使用 @Autostart 进行注释。

配置一个bean (可选)

在此步骤中,内核将bean的类实例传递给配置器bean(如果可用,则为 BeanConfigurator 的实例),以对其进行配置。在此步骤中, BeanConfigurator 实例,其知道从文件加载的配置,并将此配置注入到带有 @ConfigField 注释的 bean字段中。默认情况下,配置器使用反射来访问这些字段。但是,如果一个bean有一个对应的公共 setter/getter 方法并用于一个用 @ConfigField 注释的字段(方法参数/返回类型匹配字段类型),那么配置器将使用它们而不是通过反射访问字段。

备注

如果配置中指定的字段没有值或值等于该字段的当前值,则配置器将跳过该字段的设置值(即使存在也不会调用 setter 方法) 。

在配置步骤结束时,如果bean实现了 ConfigurationChangedAware 接口,则调用 beanConfigurationChanged(Collection<String> changedFields) 方法以通知bean字段名称哪些值已更改。如果您需要更新bean配置,当您在bean中拥有所有可用配置时,这很有用。

备注

bean的配置可以在运行时更改,它将以与传递给bean的初始配置相同的方式被应用。所以请记住,getter/setter 可能会被多次调用 - 即使对于已经配置和初始化的bean。

注入依赖

此时,内核查找带有 @Inject 注释的bean类字段,并为每个字段查找一个值。在此步骤中,内核检查此内核中可用bean的列表,该列表与注释中指定的字段类型和附加约束相匹配。

当找到所需的值(bean的实例)时,内核会尝试使用反射将其注入。但是,如果为该字段定义了匹配的 getter/setter,它将被调用而不是反射。

备注

如果依赖关系发生变化,即由于重新配置,依赖字段的值将发生变化,如果存在,将调用 setter 。所以请记住,getter/setter 可能会被多次调用 - 即使对于已经配置和初始化的bean。

一个bean的初始化

当配置了bean并设置了依赖关系时,一个bean的初始化就差不多完成了。此时,如果bean实现了 Initializable 接口,内核会调用 initialize() 方法来允许bean在需要时正确初始化。

5.2.2. 销毁bean的实例

当bean被卸载时,对它的实例的引用就被删除了。但是,如果bean类实现了 UnregisterAware 接口,那么内核会调用 beforeUnregister() 方法。如果哪个bean在初始化期间获取了一些资源并应该立即释放它们,这非常有用。

备注

如果bean没有完全初始化(bean初始化步骤已通过),则不会调用此方法!

5.2.3. 重新配置一个bean (可选)

在任何时间点,bean都可能被内核中注册的默认bean配置器( BeanConfigurator 的实例)重新配置。这将以与 创建 bean的实例 部分中的 配置bean 所描述的相同方式发生。

5.2.4. 更新依赖项

由于重新配置或注册/取消注册或激活/停用某些其他bean,bean的依赖关系可能会发生变化。因此,Tigase内核将注入新的依赖项,如 Injecting dependencies 中所述

5.3. 一个bean的注册

注册bean的方法很少。

5.3.2. parent 设置为不实现 RegistrarBean 接口的类

如果 parent 设置为没有实现 RegistrarBean 接口的类,那么你的bean将被注册到父bean注册的同一个内核范围内。如果你这样做,即通过将parent设置为在 kernel1 中注册的bean的类,您的bean也将在 kernel1 中注册。结果,它将暴露给同一内核范围内的其他bean。这也意味着,你是否将以与 parent 设置为 parent 指向的类的注释的 parent 相同的方式配置它。

示例。

@Bean(name="bean1", parent=Kernel.class)
public class Bean1 {
    @ConfigField(desc="Description")
    private int field1 = 0;
    ....
}

@Bean(name="bean2", parent=Bean1.class)
public class Bean2 {
    @ConfigField(desc="Description")
    private int field2 = 0;
    ....
}

在这种情况下,这意味着 bean1 已在根/主内核实例中注册。同时,bean2 也被注册到根/主内核,因为它的 parent 注释属性的值指向没有实现 RegistrarBean 的类。

要在DSL中配置 bean1 实例中的 field1bean2 实例中的 field2 的值(有 DSL格式的更多信息,请查看 DSL file format 中的 Admin Guide),您需要在配置文件中使用以下条目:

bean1 {
    field1 = 1
}
bean2 {
    field2 = 2
}

如您所见,这导致 bean2 配置与 bean1 配置处于同一级别。

5.3.3. 调用内核方法

作为一个类

要将bean注册为类,您需要让Tigase内核的实例执行它的 registerBean() 方法,并传递您的 Bean1 类。

kernel.registerBean(Bean1.class).exec();

备注

为了能够使用此方法,您需要使用 @Bean 注释来注释 Bean1 类,并提供将用于注册bean的bean名称。

作为工厂

为此,您需要让Tigase内核的一个实例执行它的 registerBean() 方法,并传递您的 bean Bean5 类。

kernel.registerBean("bean5").asClass(Bean5.class).withFactory(Bean5Factory.class).exec();

作为一个实例

为此,您需要让Tigase内核的实例执行它的 registerBean() 方法,并传递您的 bean Bean41 类实例。

Bean41 bean41 = new Bean41();
kernel.registerBean("bean4_1").asInstance(bean41).exec();

警告

注册为实例的Bean不会注入依赖项。同样,提供的bean配置器也不会配置此bean实例。

5.3.4. 使用配置文件 (可选)

如果在配置文件中注册了一个支持注册的bean defaultBeanConfigurator,则可以这样做。默认情况下,Tigase XMPP服务器使用 DSLBeanConfigurator 提供支持,并且可以在DSL的配置文件中注册。由于使用配置文件注册bean是 Tigase XMPP服务器任务管理的一部分,因此在管理指南中 DSL file format 部分的 Defining bean 小节中进行了说明。

小技巧

这种方式允许管理员为bean选择不同的类。此选项应该被用于为应使用注释注册的默认bean提供替代实现。

警告

仅当注册为 defaultBeanConfigurato 的bean支持此功能时才有效。默认情况下,Tigase XMPP服务器使用 DSLBeanConfigurator 来提供支持。

5.4. 定义依赖项

所有依赖项都使用注释定义:

public class Bean1 {
  @Inject
  private Bean2 bean2;

  @Inject(bean = "bean3")
  private Bean3 bean3;

  @Inject(type = Bean4.class)
  private Bean4 bean4;

  @Inject
  private Special[] tableOfSpecial;

  @Inject(type = Special.class)
  private Set<Special> collectionOfSpecial;

  @Inject(nullAllowed = true)
  private Bean5 bean5;
}

内核根据字段类型自动确定所需bean的类型。因此,在 bean4 字段的情况下,无需指定bean的类型。

当有多个bean实例匹配所需的依赖字段时,类型需要是数组或集合。如果内核无法解析依赖关系,它将抛出异常,除非 @Inject 注解将 nullAllowed 设置为 true。这对于使某些依赖项可选的很有用。当多个bean匹配字段依赖时,为了帮助内核选择单个bean实例,您可以将所需bean的名称设置为字段 bean3,如注释中所示。

如果存在这些方法,则使用getter/setter插入依赖项,否则将它们直接插入到字段中。由于使用了设置器,可以检测依赖实例的变化并根据需要做出反应,即清除内部缓存。

警告

内核在注入期间仅使用其范围内可见的bean来解决依赖关系。这使得它无法注入未在同一内核中注册为bean或在此内核范围内不可见的类的实例(请参阅 Scope and visibility)。

警告

如果两个bean具有双向依赖关系,则要求其中至少一个为 null (使其成为可选依赖关系)。在其他情况下,它将创建无法满足的循环依赖,并且内核将在运行时抛出异常。

5.5. 嵌套内核和导出的bean

Tigase内核允许使用嵌套内核。这允许您创建复杂的应用程序并在范围内保持适当的bean分离和可见性,因为每个模块(子内核)都可以在其自己的范围内工作。

可以使用以下两种方式之一创建子内核:

5.5.1. 手动注册新内核

您可以创建一个新内核的实例并将其注册为父内核中的bean。

Kernel parent = new Kernel("parent");
Kernel child = new Kernel("child");
parent.registerBean(child.getName()).asInstance(child).exec();

5.5.2. RegistrarBean的使用

你可以创建一个实现 RegistrarBean 接口的bean。对于所有实现此接口的bean,都会创建子内核。您可以在 RegistrarBean 类的实例中访问这个新内核,因为一旦 RegistrarBean 实例被创建或销毁,register(Kernel)unregister(Kernel) 方法就会被调用。

还有一个名为 RegistrarBeanWithDefaultBeanClass 的接口。如果您想要或需要创建一个bean,该bean将允许您配置许多具有相同类但名称不同的子bean,并且在设置配置之前您不知道这些bean的名称,则此接口非常有用。您需要做的就是实现这个接口,并在方法 getDefaultBeanClass() 中返回类,该类应该用于配置中定义的所有子bean,不会为其配置类。

作为这种用例的一个例子是 dataSource bean,它允许管理员轻松配置许多数据源而无需传递它们的类名,即。

dataSource {
    default () { .... }
    domain1 () { .... }
    domain2 () { .... }
}

通过这个配置,我们只定义了3个名为 default, domain1domain2 的bean。所有这些bean都将是由 dataSource bean 的 getDefaultBeanClass() 方法返回的类的实例。

5.5.3. 范围和可见性

在父内核中注册的bean对在第一级子内核中注册的bean可见。但是,在子内核中注册的bean对在父内核中注册的bean不可用,除非它们对创建子内核的bean可见( RegistrarBean 的实例)。

可以导出beans,以便它们可以在第一级子内核之外可见。

为此,您需要使用注释或调用 exportable() 方法将bean标记为可导出。

使用注释。

@Bean(name = "bean1", exportable = true)
public class Bean1 {
}

调用 exportable()

kernel.registerBean(Bean1.class).exportable().exec();

5.5.4. 依赖图

内核允许创建依赖图。以下行将以 Graphviz 支持的格式生成它。

DependencyGrapher dg = new DependencyGrapher(krnl);
String dot = dg.getDependencyGraph();

5.6. 配置

内核核心不提供任何方式来配置创建的bean。这样做你需要通过在配置中提供它的实例并在内核中注册这个实例来使用 DSLBeanConfigurator 类。

示例。

Kernel kernel = new Kernel("root");
kernel.registerBean(DefaultTypesConverter.class).exportable().exec();
kernel.registerBean(DSLBeanConfigurator.class).exportable().exec();
DSLBeanConfigurator configurator = kernel.getInstance(DSLBeanConfigurator.class);
Map<String, Object> cfg = new ConfigReader().read(file);
configurator.setProperties(cfg);
// and now register other beans...

5.6.1. DSL和内核范围

DSL是 Tigase XMPP服务器管理指南:DSL文件格式部分 中解释的基于结构的格式。 重要的是要知道内核和bean结构会影响DSL中的配置。

示例内核和bean类。

@Bean(name = "bean1", parent = Kernel.class, active = true )
public class Bean1 implements RegistrarBean {
  @ConfigField(desc = "V1")
  private String v1;

  public void register(Kernel kernel) {
    kernel.registerBean("bean1_1").asClass(Bean11.class).exec();
  }

  public void unregister(Kernel kernel) {}
}

public class Bean11 {
  @ConfigField(desc = "V11")
  private String v11;
}

@Bean(name = "bean1_2", parent = Bean1.class, active = true)
public class Bean12 {
  @ConfigField(desc = "V12")
  private String v12;
}

@Bean(name = "bean2", active = true)
public class Bean2 {
  @ConfigField(desc = "V2")
  private String v2;
}

public class Bean3 {
  @ConfigField(desc = "V3")
  private String v3;
}

public class Main {
  public static void main(String[] args) {
    Kernel kernel = new Kernel("root");
    kernel.registerBean(DefaultTypesConverter.class).exportable().exec();
    kernel.registerBean(DSLBeanConfigurator.class).exportable().exec();
    DSLBeanConfigurator configurator = kernel.getInstance(DSLBeanConfigurator.class);
    Map<String, Object> cfg = new ConfigReader().read(file);
    configurator.setProperties(cfg);

    configurator.registerBeans(null, null, config.getProperties());

    kernel.registerBean("bean4").asClass(Bean2.class).exec();
    kernel.registerBean("bean3").asClass(Bean3.class).exec();
  }
}

以下类将产生以下bean结构:

  • Bean1 中的 “bean1”

    • Bean11 中的 “bean1_1”

    • Bean12 中的 “bean1_2”

  • Bean2 中的 “bean4”

  • Bean3 中的 “bean3”

备注

这是一个简化的结构,实际结构稍微复杂一些。然而,这个版本更容易解释bean的结构和对配置文件结构的影响。

警告

尽管 Bean2 使用名称 bean2 进行了注解,但它是使用名称 bean4 注册的,因为该名称是在bean注册过程中在 main() 方法中传递的。

小技巧

由于 Bean12 的注释,Bean12 被注册为 Bean1 的子bean,名为 bean1_2

如前所述,DSL文件结构取决于bean的结构,将每个bean中的配置字段设置 bean名称的文件应如下所示:

'bean1' () {
    'v1' = 'bean1'

    'bean1_1' () {
        'v11' = 'bean1_1'
    }
    'bean1_2' () {
        'v12' = 'bean1_2'
    }
}
'bean4' () {
    'v2' = 'bean4'
}
'bean3' () {
    'v3' = 'bean3'
}