[weld] 不要再用new,new 只是个传说
wuhaixing
2010-03-30
CDI最显著的特性之一就是依赖注入,确切的说是类型安全的依赖注入。CDI考虑的非常全面,下面将一一介绍。
|
|
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做了如下操作:
(唯一复杂的点就是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实现,而且注入点的类型与此一致,那么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),解决办法是:
注意还有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类型:
通常,遇到这种不能代理的依赖关系时很好解决,比如我们有个X先生是无法代理的,那么只要如此这般,这般如此就行了。
|
|
wuhaixing
2010-04-01
用get()
注入并不是灵丹妙药,有时候你会觉得她不是那么方便。比如遇到如下情况的时候:
这时候,你可以注入一个带有bean类型参数的Instance接口的实例: @Inject Instance<PaymentProcessor> paymentProcessorSource; 然后就可以用Instance的get() 方法得到一个contextual的bean实例: PaymentProcessor p = paymentProcessorSource.get(); 你还是可以用qualifier,有下面两种方式:
第一种方式很简单 @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需要知道是为谁提供服务(或注入点的相关信息)才能完成他们的工作。比如:
作用域为@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用的还是比较少。。 |