[weld] 对事不对bean

wuhaixing 2010-04-29
DI实现松散耦合的方式是让注入的bean类型可变,部署时或运行时都行。事件机制更进一步,根本就不需要知道交互双方的bean类型。container把生事者搞出来的事件交给事件的关注者。
听起来像observer/observable模式?不过有点不一样的地方:
  • 不仅生事者不知道关注方,关注方也不知道谁是生事者,他们被完全隔离开了,
  • 关注者可以定义一组“selectors”来限定他们想要关注的事件通告
  • 关注者可以马上收到通告,也可以让事件在当前事务结束后再送达

CDI的事件通告机制也多多少少的使用了我们在DI服务中见过的typesafe方式。
wuhaixing 2010-04-29
事件对象
事件的关注者需要了解事件的相关信息,生事者负责将信息封装进事件对象中传递给关注者。对事件对象的唯一要求就是他不能为可变类型的实例。事件还可以带有qualifiers,以便于关注者区分同一类型的不同事件。qualifier在这里的作用和topic选择器类似,让selector可以缩小其所关注的事件集。
事件的qualifier就是普通的qualifier,下面是一个例子:
@Qualifier
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Updated {}
wuhaixing 2010-04-29
关注者
带有标记为@Observes参数的方法就是observer方法。
public void onAnyDocumentEvent(@Observes Document document) { ... }

被标记的参数称为事件参数。事件参数的类型就是所关注的事件类型,这里就是Document。事件参数也可以带有qualifier。
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }

observe方法不是必须带有event qualifier的--这种情况下所有该类型的事件都会受到关注。如果带有qualifier标记,只有带有相同标记的事件才会被关注。
observe方法也可以带有其他参数,这些参数会由container注入:
public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }
wuhaixing 2010-04-29
生事者
生事者需要一个Event接口的参数化类型实例来发起事件,这个由container负责提供:
@Inject @Any Event<Document> documentEvent;

调用fire()方法就可以发起事件了,fire()方法中得带这事件对象作为参数:
documentEvent.fire(document);

所有符合下列条件的关注者方法都会看到这个事件:
  • 带有对应的event参数(Document类型)
  • 没有qualifier

container仅仅是负责将event对象赋值给event参数,并调用ovserver方法们。如果observer方法出现了异常,container停止调用,并且异常会由fire()方法向上传递。

在event上用qualifier有两种方式:
  • 在Event的注入点前面做标记
  • 把qualifier放到Event的select()方法中


第一种超级简单:
@Inject @Updated Event<Document> documentUpdatedEvent;

这个Event实例fired的事件,都会带上@Updated。收到这个事件的关注者方法要:
  • 带有对应的event参数(Document类型)
  • 所带的qualifier要是Event注入点所标记的qualifiers的子集


但这种方式不太灵活,CDI也允许我们通过创建 AnnotationLiteral类的子类来动态的生成qualifier,并把这个qualifier放到Event的select()方法中。
documentEvent.select(new AnnotationLiteral<Updated>(){}).fire(document);

qualifier可以有多个,标记在Event注入点和传递到select()方法中的是集成到一起的。
wuhaixing 2010-04-29
我有个条件
通常情况下,如果当前的context中没有observer的实例,container会创建一个observer的实例然后把event交给他。有时候你可能不想让container这么干,只想在当前context中已经存在observer实例的时候才递交event。

给@Observer标记加上receive = IF_EXISTS,就得到了一个conditional observer。
public void refreshOnDocumentUpdate(@Observes(receive = IF_EXISTS) @Updated Document d) { ... }

这个不能用在@Dependent scope中的bean上,因为那样他就永远都不会被调用了。
wuhaixing 2010-04-29
带member的事件qualifier

事件qualifier 也可以带annotation members:
@Qualifier
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Role {
   RoleType value();
}


member的值用来进一步明确传递给observer的消息:
public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }


qualifier的 members可以在event notifier注入点静态标明:
@Inject @Role(ADMIN) Event<LoggedIn> loggedInEvent;

也可以在select方法中动态说明。
先定义一个 AnnotationLiteral的抽象类:
abstract class RoleBinding 
   extends AnnotationLiteral<Role> 
   implements Role {}

然后在select()中:

documentEvent.select(new RoleBinding() {
   public void value() { return user.getRole(); }
}).fire(document);
wuhaixing 2010-04-29
多个qualifier
事件的qualifier可以组合:
@Inject @Blog Event<Document> blogEvent;
...
if (document.isBlog()) blogEvent.select(new AnnotationLiteral<Updated>(){}).fire(document);


当这个事件被触发后,下面的observer方法都能收到:
public void afterBlogUpdate(@Observes @Updated @Blog Document document) { ... }

public void afterDocumentUpdate(@Observes @Updated Document document) { ... }

public void onAnyBlogEvent(@Observes @Blog Document document) { ... }

public void onAnyDocumentEvent(@Observes Document document) { ... }}}
wuhaixing 2010-04-29
事务化的关注者
事务化的关注者会在 发起事件的 事务结束阶段 之前(或之后)收到事件通知。下面这个observer方法负责刷新application context中的查询结果, 但他只在Category tree更新事务成功之后才会执行:
public void refreshCategoryTree(@Observes(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }


weld支持5种 transactional observers:
  • IN_PROGESS observers 会立即被调用(默认值)
  • AFTER_SUCCESS observers 在transaction完成之后调用,而且transaction必须是正常结束的
  • AFTER_FAILURE observers 在没能正常结束的transaction完成之后调用
  • AFTER_COMPLETION observers 在transaction完成之后调用
  • BEFORE_COMPLETION observers 在transaction进入完成阶段之前调用


事务化的observer在statuful对象模型中非常重要,因为对象状态的持续时间通常都会比一个atomic transaction要长。
看看下面这个情况,application scope中缓存了一个JPA的查询结果:
@ApplicationScoped @Singleton
public class Catalog {
   @PersistenceContext EntityManager em;
   List<Product> products;
   @Produces @Catalog 
   List<Product> getCatalog() {
      if (products==null) {
         products = em.createQuery("select p from Product p where p.deleted = false")
            .getResultList();
      }
      return products;
   }
}

你总要不断的增加和删除Product,随之就要刷新Product目录。但是得等增删事务成功结束之后。
负责增删的方法可以发起事件:

@Stateless
public class ProductManager {
   @PersistenceContext EntityManager em;
   @Inject @Any Event<Product> productEvent;
   public void delete(Product product) {
      em.delete(product);
      productEvent.select(new AnnotationLiteral<Deleted>(){}).fire(product);
   }
 
   public void persist(Product product) {
      em.persist(product);
      productEvent.select(new AnnotationLiteral<Created>(){}).fire(product);
   }
   ...
}

Catalog关注相应的事件,就可以在事务成功结束之后进行相应的操作了:
@ApplicationScoped @Singleton
public class Catalog {
   ...
   void addProduct(@Observes(during = AFTER_SUCCESS) @Created Product product) {
      products.add(product);
   }

   void addProduct(@Observes(during = AFTER_SUCCESS) @Deleted Product product) {
      products.remove(product);
   }
}
Global site tag (gtag.js) - Google Analytics