[weld] 不要再用new,new 只是个传说

wuhaixing 2010-03-30
CDI最显著的特性之一就是依赖注入,确切的说是类型安全的依赖注入。CDI考虑的非常全面,下面将一一介绍。
  1. 在哪里注入?
  2. 注入的是什么?
  3. 修饰再修饰,最终总会明确
  4. A还是B,到时再决定
  5. 不满足,不确定的解决办法
  6. 幕后的client proxies
  7. 用get()
  8. 我知道你引用了我
wuhaixing 2010-03-30
注入点
你可以用 @Inject 注解告诉container:”我需要一个bean,请您帮我创建一个放在这里,谢谢!“。 在实例化这个类时,container就会把你需要的bean创建(或者找出来)给你放好。@Inject可以在三个地方出现。
1.Bean的构造方法之前,container将会提供构造方法中的参数。

public class Checkout {
   private final ShoppingCart cart;
   @Inject
   public Checkout(ShoppingCart cart) {
      this.cart = cart;
   }
}

注意,只能有一个带@Inject的构造方法。

2.set方法之前,container将会为你提供域初始化方法中的参数。

public class Checkout {
   private ShoppingCart cart;
   @Inject
   void setShoppingCart(ShoppingCart cart) {
      this.cart = cart;
   }
}


带@Inject的set方法当然可以有多个了。而且对于session bean,并不要求是business方法

3.直接放在field之前,那容器就不是提供set方法的参数了,是直接给你的field赋值。

public class Checkout {
   private @Inject ShoppingCart cart;
}

这个比JSF的managed bean更方便,你都不需要为cart生成getter和setter方法。

依赖注入总是在bean的实例创建的时候发生的,简单的说,container做了如下操作:
  1. container调用bean的构造方法(默认的或被@Inject标注了带参数的),得到一个bean实例
  2. 接下来,container初始化所有Injected的field,赋值
  3. 再接下来,container调用bean的所有初始化方法(调用顺序很飘逸,要当心)
  4. 最后,调用标记了@PostConstruct的方法(没有就算了)

(唯一复杂的点就是container可能在初始化子类的field之前调用父类的初始化方法)
注意,使用构造方法注入的主要优势就是可以让bean有金刚不坏之身,immutable,不怕并发。

至于非bean里的依赖注入是怎么搞的,原文没说,我也没研究,盼解!

对于那些由container调用的方法,CDI并没有忘记,你们的参数容器也会准备好的。比如producer 方法:

@Produces Checkout createCheckout(ShoppingCart cart) {
    return new Checkout(cart);
}


这时候不需要@Inject出场。这种情况除了适用于producer方法,还有observer 和 disposer。

wuhaixing 2010-03-30
引什么入室?
CDI规范中定义了一个称之为typesafe解析的流程。container在决定该往注入点放哪个bean时要遵照该流程。乍一看,这个算法显得很复杂,但其实是非常直白的。typesafe解析是在系统初始化的时候进行的,也就是说如果bean的依赖条件无法得到满足,container会马上通知你。

这样做的目的是为了让同一个bean类型能够有多个bean实现,然后:
  • 允许bean的使用者用qualifier选择自己所需的bean实现
  • 允许部署人员根据特定部署环境所需,不改变bean的使用者代码,通过启用alternative来选择所需的bean实现
  • 让bean们在彼此独立的modules中独立存在

对于特定的类型,如果你只有一个bean实现,而且注入点的类型与此一致,那么A插头就会被插进A插座。这是最简单的情况,一开始你遇到的也基本上都是这种情况。

以后事情也许会变得复杂了,这时候你需要在多个bean实现之间做出选择,那么神器qualifier就该出现了。
wuhaixing 2010-03-30
修饰

当有多个bean实例实现了同一bean类型的时候,要用qualifier标注注入点来说明使用哪个bean实例。比如PaymentProcessor的两个实现:
@Synchronous
public class SynchronousPaymentProcessor implements PaymentProcessor {
   public void process(Payment payment) { ... }
}


@Asynchronous
public class AsynchronousPaymentProcessor implements PaymentProcessor {
   public void process(Payment payment) { ... }
}


接下来是 @Synchronous 和 @Asynchronous是qualifier标注:

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Synchronous {}


@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Asynchronous {}


在@Inject出现或不需出现的地方,都可以用刚刚定义的qualifier标注所需的bean:
1.直接放在field前

@Inject @Synchronous PaymentProcessor syncPaymentProcessor;

@Inject @Asynchronous PaymentProcessor asyncPaymentProcessor;


2.属性初始化方法

@Inject
public void setPaymentProcessors(@Synchronous PaymentProcessor syncPaymentProcessor, 
                                 @Asynchronous PaymentProcessor asyncPaymentProcessor) {
   this.syncPaymentProcessor = syncPaymentProcessor;
   this.asyncPaymentProcessor = asyncPaymentProcessor;
}


3.构造方法

@Inject
public Checkout(@Synchronous PaymentProcessor syncPaymentProcessor, 
                @Asynchronous PaymentProcessor asyncPaymentProcessor) {
   this.syncPaymentProcessor = syncPaymentProcessor;
   this.asyncPaymentProcessor = asyncPaymentProcessor;
}


4.producer, disposer 和 observer方法的参数

@Produces
PaymentProcessor getPaymentProcessor(@Synchronous PaymentProcessor syncPaymentProcessor,
                                     @Asynchronous PaymentProcessor asyncPaymentProcessor) {
   return isSynchronous() ? syncPaymentProcessor : asyncPaymentProcessor;
}


注意看上面的代码,当在producer方法上加上qualifier的时候,你就可以根据当前的状态提供合适的bean了。

如果没有显式的声明qualifier,那么就是使用了默认的@Default。
”直接说明直接匹配的bean类型不就行了吗?干嘛非要搞个qualifier出来?“,哦,这个问题很初级,qualifier和interface的作用是一样的,是为了避免直接依赖,降低耦合度。

自带的qualifier @Default 和 @Any

当没有为bean或注入点声明qualifier的时候,container默认qualifier为@Default。那接下来的问题就是,如果不想对bean做限制该怎么办呢,怎么去掉默认的qualifier?还是用qualifier注释,有一个@Any,所有的bean都具有@Any 修饰,所以你可以在注入点显式的声明@Any,默认的@Default就不在了。这样你就可以不受qualifier的限制,只限定bean 类型了。看看下面这个情况:
@Inject 
void initServices(@Any Instance<Service> services) { 
   for (Service service: services) {
      service.init();
   }
}


带有member的qualifier

Java注解可以有member,我们可以利用qualifier的成员来进一步进行区分,这样就能减少qualifier注解的数量。比如我们可以将代表几个支付方式的qualifier整合为一个:

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface PayBy {
   PaymentMethod value();
}


这样我们就可以在使用qualifier时选择合适的成员值:
private @Inject @PayBy(CHECK) PaymentProcessor checkPayment;


也可以用@Nonbinding让container忽略qualifier的成员。
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface PayBy {
   PaymentMethod value();
   @Nonbinding String comment() default "";
}



qualifier的组合


注入点可以标注多个qualifier:
@Inject @Synchronous @Reliable PaymentProcessor syncPaymentProcessor;

当然就只能注入同时被这些注解一起标注的bean。

@Synchronous @Reliable
public class SynchronousReliablePaymentProcessor implements PaymentProcessor {
   public void process(Payment payment) { ... }
}
wuhaixing 2010-03-31
部署的时候再选择

Alternative是一个bean,是专门为特定的模块或部署环境定制的。下面这个alternative在一个bean中模拟实现了@Synchronous PaymentProcessor  和 @Asynchronous PaymentProcessor:
@Alternative @Synchronous @Asynchronous
public class MockPaymentProcessor implements PaymentProcessor {
   public void process(Payment payment) { ... }
}


通常@Alternative beans是不启用的,我们需要在beans.xml中启用,container才会创建他们的实例,并注入到别的bean中。这种启用仅在beans.xml所在的包中才有效。

<beans
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
   <alternatives>
         <class>org.mycompany.mock.MockPaymentProcessor</class>
   </alternatives>
</beans>


当依赖关系模糊不清时,container会在启用的alternative中找找看,如果有合适的,他就把这个bean交给使用者。
wuhaixing 2010-03-31
不满足,不确定的解决办法
由于qualifier和disabled的bean(@Alternativ)的影响,类型安全的解析算法有时会有点失灵,container无法找出完全匹配的bean。那他就会放弃部署,通知我们发现了无法满足的,或无法确定的依赖关系。

在你部署过程中,你很可能会遇到这个问题。
对于无法满足的依赖关系(没找到bean),解决办法是:
  • 创建一个完全满足条件的bean(直接吧),包括bean类型和所有的qualifier。
  • 看看你的bean能否被注入点所在的模块找到,就是检查一下classpath
  • 如果你的bean是@Alternative bean,那别忘了在beans.xml里面启用。


对于无法明确的依赖关系(找到了好几个bean),解决办法是:
  • 增加qualifier来区分实现了同一类型的bean
  • 用 @Alternative 标注可以禁用的bean
  • 把bean从注入点所在classpath里挪走
  • 看看beans.xml里还有没有能禁用的@Alternative bean

注意还有Producer方法返回的bean呢,请见FAQ

记住基本国策,只能有一个!
wuhaixing 2010-03-31
client proxies

除非是@Dependent的bean,使用者通常并不能直接接触到被注入的bean。

想像一下,假设一个request作用域的bean被注入到一个application作用域的bean中,不同的request中能见到的application作用域的bean都是相同的,可他们所见到的request作用域的bean应该是不同的。

再想想另一种情况,一个session作用域中的bean直接引用了一个application作用域中的bean,为了提高内存的使用效率,session context中的bean会一次又一次的被序列化到硬盘中保存起来,可所引用的application中的bean是不需要保存到硬盘中的,因为他会一直保持不变,随时都能引用。

因此,除非是默认的作用域@Dependent中的bean,container必须通过代理的方式为使用者提供bean的引用。 由代理负责确保被调用的是在恰当的context中的bean实例,还要能保证session context实例中的bean被序列化时不会重复递归,把所有引用的bean全都序列化了。

可是,由于java语言自身的限制,container是不能提供某些java类型的代理的。一旦出现这样的注入点,而其作用域又不是@Dependent,那container只好放弃部署,告诉我们他没能力了。

你得注意下面这些java类型:
  • 没有不带参数的非private构造方法
  • final 的class或有class中有final的方法
  • arrays 和 primitive类型


通常,遇到这种不能代理的依赖关系时很好解决,比如我们有个X先生是无法代理的,那么只要如此这般,这般如此就行了。
  • 没构造方法就给他加上个没参数的构造方法
  • 把注入点的bean类型改成Instance<X>
  • 创建一个接口Y,让X实现Y,然后把注入点的bean类型改成Y(进水了?)
  • 如果都不行,那就别用代理了,把bean的作用域改成@Dependent吧


wuhaixing 2010-04-01
用get()
注入并不是灵丹妙药,有时候你会觉得她不是那么方便。比如遇到如下情况的时候:
  • 在程序跑起来后,所需的bean类型或者qualifier是要变的
  • 根据部署情况,可能没有能满足条件的bean
  • 需要循环处理某种类型的所有bean


这时候,你可以注入一个带有bean类型参数的Instance接口的实例:
@Inject Instance<PaymentProcessor> paymentProcessorSource;

然后就可以用Instance的get() 方法得到一个contextual的bean实例:
PaymentProcessor p = paymentProcessorSource.get();


你还是可以用qualifier,有下面两种方式:
  • 放在注入的Instance前面
  • 交给select()方法

第一种方式很简单
@Inject @Asynchronous Instance<PaymentProcessor> paymentProcessorSource;


这样get()得到的PaymentProcessor就是带有qualifier @Asynchronous的bean实例了。

或者,你也可以动态的指定qualifier,不过首先得用 @Any 代替默认的@Default。

@Inject @Any Instance<PaymentProcessor> paymentProcessorSource;


接下来我们还需要得到一个qualifier类型的实例。因为annotation是interface,所以我们不能用 new Asynchronous()来完成这个任务。从头开始创建一个annotation类型的实现类也很麻烦,那么我们就利用CDI提供AnnotationLiteral,通过创建他的子类来得到qualifier的实例。

abstract class AsynchronousQualifier
extends AnnotationLiteral<Asynchronous> implements Asynchronous {}


哦,有时候匿名类也成。(带有成员变量的qualifier不能这么搞!)
PaymentProcessor p = paymentProcessorSource
   .select(new AnnotationLiteral<Asynchronous>() {});


最后,我们就可以qualifier交给Instance的select()方法了:

Annotation qualifier = synchronously ?
      new SynchronousQualifier() : new AsynchronousQualifier();
PaymentProcessor p = anyPaymentProcessor.select(qualifier).get().process(payment);

wuhaixing 2010-04-01
我知道你引用了我-InjectionPoint

有时候,提供服务的bean需要知道是为谁提供服务(或注入点的相关信息)才能完成他们的工作。比如:
  • Logger的日志分类就是由其所在的类决定的
  • 注入的HTTP参数或头信息的值是由注入点所说明的参数或头信息名称决定的
  • 注入的EL表达式的计算结果是由注入点的表达式决定的。

作用域为@Dependent的bean可以引用InjectionPoint的实例,通过他来访问注入点的相关信息。
看下面的例子,这段代码很啰嗦,而且不利于重构:
Logger log = Logger.getLogger(MyClass.class.getName());

下面的做法会显得更聪明一点:
class LogFactory {
   @Produces Logger createLogger(InjectionPoint injectionPoint) { 
      return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); 
   }
}

这样我们就可以在任何需要log的地方写成:
@Inject Logger log;


上面的代码仅为了说明问题,请勿模仿。对于log,weld有专门的portal extension。

下面还有更精彩的,肯定能打动你!
来看看如何注入HTTP参数。先定义一个qualifier(原文此例有错):

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
   @Nonbinding public String value();
}

这个qualifier的用法如下:
@Inject @HttpParam("username") String username;
@Inject @HttpParam("password") String password;

我们还需要一个Producer方法:
class HttpParams
   @Produces @HttpParam("")
   String getParamValue(ServletRequest request, InjectionPoint ip) {
      return request.getParameter(ip.getAnnotated().getAnnotation(HttpParam.class).value());
   }
}


注意,如果没有值,container会忽略HttpParam的value() 成员的,因为你指明它是@Nonbinding的。

来看一下InjectionPoint接口:
public interface InjectionPoint { 
   public Type getType();
   public Set<Annotation> getQualifiers();
   public Bean<?> getBean();
   public Member getMember();
   public Annotated getAnnotated();
   public boolean isDelegate();
   public boolean isTransient();
}

container为你提供了这个接口的实现,你不用自己再写一个bean了。
lonvea 2010-04-04
你好像对weld超级感兴趣??我也是,正在使用weld做一个功能测试。
@New用的还是比较少。。
Global site tag (gtag.js) - Google Analytics