Ioc - Annotation 加载器

Jul 10, 2017 10:38:44 AM

作者:wendalzozoh

为什么需要 Ioc 注解加载器

无论是 XML 还是 JSON,都需要你创建一个新的配置文件,在里面描述你的对象依赖关系。一般的来说,一个应用大多数的对象依赖关系,是固定的,即,在项目发布以后是不必调整的。如果将这些依赖关系通通写到配置文件中,颇有点"脱了裤子放屁"的感觉,最理想的情况是,将可能变动的依赖关系写到配置文件里,而将不怎么会变动的依赖关系写成 Java 的注解 (Annotation), 如果能这样的话,一切就圆满了。

但是,真的可以吗?

我可以负责任的告诉你,完全是可以滴 ^_^

首先这篇文章,会详细讲解一下如果通过注解来配置你的容器对象,而 Ioc 复合加载器一篇,将会告诉你如何组合多个加载器,这样你就可以把你的对象依赖关系分别写到 xml,json,以及 Java 注解里,组合使用了。

如何使用 AnnotationIocLoader

同 JsonLoader 一样,你可以直接 new 一个 AnnoationIocLoader

  1. Ioc ioc = new NutIoc(new AnnotationIocLoader("com.you.app.package0", "com.you.app.package1"));

当然在 Nutz.Mvc 中,你可以通过 IocProvider 来初始化 Ioc 容器,所以你可以在你的 MainModule 上这么声明

  1. @IocBy(type = AnnotationIocProvider.class,
  2. args = { "com.you.app.package0",
  3. "com.you.app.package1"})
  4. public class MainModule {
  5. ....

这样,你在

  • com.you.app.package0
  • com.you.app.package1
    这两包下,所有声明了 @IocBean 这个注解的对象,都会被认为是容器对象。是的,通过注解 @IocBean, AnnotationIocLoader 就能辨别你指定的包中,哪些类是可以交由容器管理的。

那么,@IocBean里面还能声明什么信息,我怎么为我的容器对象设置注入内容呢? 请继续看下面内容 ^_^

指定对象的名称

任何一个 Ioc 容器管理的对象,都必须有一个名字,以便通过:

  1. MyObject obj = ioc.get(MyObject.class, "myObjectName");

来获取对象。

因此,你可以为你的对象这么声明:

  1. @IocBean(name="myObjectName")
  2. public class MyObject {
  3. ...

如果你的对象名字为你对象类名的首字母小写形式,你可以省略名字这个属性, 即

  1. @IocBean
  2. public class MyObject {
  3. ...

  1. @IocBean(name="myObject")
  2. public class MyObject {
  3. ...

效果是一样的。

还有另外一种方法,你可以为你的对象声明一个单独注解:

  1. @InjectName("myObjectName")
  2. @IocBean
  3. public class MyObject {
  4. ...

列位,看到这里,可能有人会问了,这不是脱裤子放屁吗? @IocBean 可以有 name 属性,而你又搞了一个@InjectName 注解专门声明名字,你到底打算让我们用哪一个?你逗我们玩哪?

首先,我得跟大家声明一下,这的确是一点点历史问题,原先的 @InjectName 是给 Nutz.Mvc 用的,它如果发现了子模块声明了这个属性,就交付给 Ioc 容器管理。 后来,我们发现,介个名字和 @IocBean 的名字必须是相同的,所以在 AnnotationIocLoader 里,我们做了如下优先级的判断:

  • 如果发现 @IocBean 有 name 属性,这个对象就采用这个名字
  • 如果没有 @IocBean(name="xxxx"),哪么就看看有没有声明了 @InjectName
  • 还没有的话,就用对象的类名首字母小写形式作为这个对象的名称
    因此对于一个 Nutz.Mvc 的模块类来说, @InjectName + @IocBean 是一个比较方便的写法。

但是现在我也承认, @IocBean 的 name 属性有点多余,或者 @InjectName 有点多余。 但是由于是过了几个版本以后才认识到的这个问题,所以我想,不如就留着这个设计,作为 Nutz 这个项目的一段 盲肠, 希望列位看官理解我们的苦衷,毕竟我们宣称了接口不会有重大变动之后,就要拿掉这个盲肠话,仿佛自己打了一记自己嘴巴。因此,人类的劣根性导致我们这么安慰自己:“没事没事,这个设计虽然有一点点臃肿,但是没人让人更难用,也过得去啦 -_-!”

不要单例?

默认的,Ioc 容器管理的对象都是单例的,你如果不想单例,你可以:

  1. @IocBean(name="myObject", singleton=false)
  2. public class MyObject {
  3. ...

为对象传递构造函数

当然 @IocBean 这点是不够,很多对象注入的时候,需要为构造函数声明信息,你可以这样:

  1. @IocBean(name="myObject", args={"a string", "refer:anotherObject", true, 234})
  2. public class MyObject {
  3. ...

看,简单不? 你的构造函数有多少个参数,你就一并在 "args" 属性里声明就好了, 那么你都能注入什么呢?

它注入的值同字段注入的值描述方式相同,请继续看下面一节,我们有更详细的解释

若没有声明args,那么就会找无参构造方法哦

为对象的字段注入

比如:

  1. @IocBean
  2. public class MyObject {
  3. @Inject("daoSlave") // 等价于 refer:daoSlave
  4. private Dao dao;
  5. @Inject("refer:another")
  6. private AnotherObject obj;
  7. @Inject // 先按名字找,然后按类型找
  8. private UserService users;
  9. ...

那么你到底能注入什么呢? 感兴趣的同学可以看这里:你都可以注入什么。当然,同 Json 的方式有点不同,你这里直接写 "refer:xxxx" 或者 "env:xxxx" 就好了。下面是一个列表

@Inject("refer:objName") 注入容器其他对象的引用
@Inject("refer:$ioc") 容器自身
@Inject("refer:$Name") 对象的名称,即你在 @InjectName 或者 @IocBean 里声明的 name
@Inject("refer:$Context") 容器上下文
@Inject("env:JAVA_HOME") 系统环境变量
@Inject("sys:user.home") 虚拟机属性
@Inject("jndi:jndiName") JNDI 资源
@Inject("file:/home/zzh/abc.txt") 文件对象
@Inject("java:com.my.SomeClass.staticPropertyName") 调用某 JAVA 类的静态属性
@Inject("java:com.my.SomeClass.someFunc") 调用某 JAVA 类的静态函数
@Inject("java:com.my.SomeClass.someFunc("p1",true)") 调用某 JAVA 类的带参数的静态函数
@Inject("java:$xh") 获得容器对象 xh ,相当于 "refer:xh"
@Inject("java:$xh.name") 容器对象某个属性
@Inject("java:$xh.getXXX()") 容器对象某个方法的返回值
@Inject("$xh.getXXX("some string", true, 34)") 容器对象某个方法的返回值

兼容性改变, 如果你硬要注入一个常量(例如true,false,字符串), 那么写成 ":true" ":false" ":hi"

基本上,看到上面的表格,你同时也能完全明白怎么 #为对象传递构造函数,不是吗?

声明对象的事件

在 Nutz.Ioc 容器里,一个对象有三种事件:

  • create - 当对象被创建时触发
  • fetch - 当对象被从容器中取出时触发
  • depose - 当对象被销毁时触发
    同 JSON 配置一样, Annotation 方式的配置也允许你声明这三种事件的处理方式
  1. @IocBean(
  2. create = "init",
  3. fetch = "com.myapp.MyObjectOnFetchTrigger",
  4. depose = 'onDepose'
  5. )
  6. public class MyObject {
  7. ...

同 JSON 的配置方式相同的是,你可以为该对象处理该事件的一个函数,比如上面的例子,MyObject 需要有一个函数init 来处理创建时的事件,还需要一个函数 onDepose 来处理注销时的事件。当然这两个函数不能是静态的,也不能有任何参数。 对一个事件,你还可以声明一个 IocEventTrigger 的实现类,来处理一个事件。比如上面的例子,我们就用 com.myapp.MyObjectOnFetchTrigger 来处理 fetch 事件的。

看到这里,可能有的同学会弱弱的问了,这个功能到底是干虾米滴? 我认为所有需要问这个问题的同学都可以无视这个功能,因为你根本不需要它。但是如果你想在对象创建的时候,做点初始化工作,比如为某几个字段设设值;或者你希望在容器注销你的对象时,你想同时释放点资源,比如数据连接池对象被销毁时,你需要释放一下池内的连接;又或者你想为你的对象做一个计数,每次从容器获取的时候,计数+1,用来统计你对象被使用的频率,我想你也很需要 fetch 这个事件。无论你采用对象的函数,还是自己实现 IocEventTrigger 这个接口,你会拿到容器里的对象实例,然后你想做什么完全随你喽 :)

如果要注入的字段在超类怎么办

  1. @IocBean(fields={"dao"})
  2. public AbcService extends Service {
  3. ...

比如上例,你的 AbcService 从 Service 继承,但是 Serivce 有一个私有字段为 "private Dao dao", 你怎么描述它的注入呢? 你可以通过 @IocBean 注解提供 fields 属性,描述你要注入超类那个字段,比如上面的例子,我们会为超类的 "dao" 字段注入一个名为 "dao" 容器对象。

但是,如果我想注入的容器对象同超类的那个字段名字不一样怎么办? 或者我不打算注入一个容器对象,我想注入一个字符串,或者布尔值怎么办? 呵呵,抱歉,截止到现在,我们还没有解决这个问题,不过很快我们会给出一个设计,并且我可以负责的说,给出新的设计一定会兼容现在的这个用法的。

工厂方法

工厂方法已经移入独立的章节

本页面的文字允许在知识共享 署名-相同方式共享 3.0协议GNU自由文档许可证下修改和再使用。

原文: http://nutzam.com/core/ioc/loader_annotation.html