producermethods.xml
9.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ ]>
<chapter id="producermethods">
<title>Producer methods</title>
<para>
Producer methods let us overcome certain limitations that arise when a container, instead of the application, is
responsible for instantiating objects. They're also the easiest way to integrate objects which are not beans into
the CDI environment. <!--(We'll meet a second approach in <xref linkend="xml"/>, a chapter covering the Weld XML
extension).-->
</para>
<para>According to the spec:</para>
<blockquote>
<para>A producer method acts as a source of objects to be injected, where:</para>
<itemizedlist>
<listitem>
<para>the objects to be injected are not required to be instances of beans,</para>
</listitem>
<listitem>
<para>the concrete type of the objects to be injected may vary at runtime or</para>
</listitem>
<listitem>
<para>
the objects require some custom initialization that is not performed by the bean constructor
</para>
</listitem>
</itemizedlist>
</blockquote>
<para>For example, producer methods let us:</para>
<itemizedlist>
<listitem>
<para>expose a JPA entity as a bean,</para>
</listitem>
<listitem>
<para>expose any JDK class as a bean,</para>
</listitem>
<listitem>
<para>
define multiple beans, with different scopes or initialization, for the same implementation class, or
</para>
</listitem>
<listitem>
<para>vary the implementation of a bean type at runtime.</para>
</listitem>
</itemizedlist>
<para>
In particular, producer methods let us use runtime polymorphism with CDI. As we've seen, alternative beans are
one solution to the problem of deployment-time polymorphism. But once the system is deployed, the CDI
implementation is fixed. A producer method has no such limitation:
</para>
<programlisting role="JAVA"><![CDATA[@SessionScoped
public class Preferences implements Serializable {
private PaymentStrategyType paymentStrategy;
...
@Produces @Preferred
public PaymentStrategy getPaymentStrategy() {
switch (paymentStrategy) {
case CREDIT_CARD: return new CreditCardPaymentStrategy();
case CHECK: return new CheckPaymentStrategy();
case PAYPAL: return new PayPalPaymentStrategy();
default: return null;
}
}
}]]></programlisting>
<para>Consider an injection point:</para>
<programlisting role="JAVA"><![CDATA[@Inject @Preferred PaymentStrategy paymentStrategy;]]></programlisting>
<para>
This injection point has the same type and qualifier annotations as the producer method, so it resolves to the
producer method using the usual CDI injection rules. The producer method will be called by the container to
obtain an instance to service this injection point.
</para>
<section>
<title>Scope of a producer method</title>
<para>
The scope of the producer method defaults to <literal>@Dependent</literal>, and so it will be called
<emphasis>every time</emphasis> the container injects this field or any other field that resolves to the
same producer method. Thus, there could be multiple instances of the <literal>PaymentStrategy</literal>
object for each user session.
</para>
<para>
To change this behavior, we can add a <literal>@SessionScoped</literal> annotation to the method.
</para>
<programlisting role="JAVA"><![CDATA[@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy() {
...
}]]></programlisting>
<para>
Now, when the producer method is called, the returned <literal>PaymentStrategy</literal> will be bound to
the session context. The producer method won't be called again in the same session.
</para>
<note>
<para>
A producer method does <emphasis>not</emphasis> inherit the scope of the bean that declares the
method. There are two different beans here: the producer method, and the bean which declares it. The
scope of the producer method determines how often the method will be called, and the lifecycle of the
objects returned by the method. The scope of the bean that declares the producer method determines the
lifecycle of the object upon which the producer method is invoked.
</para>
</note>
</section>
<section>
<title>Injection into producer methods</title>
<para>
There's one potential problem with the code above. The implementations of
<literal>CreditCardPaymentStrategy</literal> are instantiated using the Java <literal>new</literal>
operator. Objects instantiated directly by the application can't take advantage of dependency injection and
don't have interceptors.
</para>
<para>
If this isn't what we want, we can use dependency injection into the producer method to obtain bean
instances:
</para>
<programlisting role="JAVA"><![CDATA[@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps,
CheckPaymentStrategy cps,
PayPalPaymentStrategy ppps) {
switch (paymentStrategy) {
case CREDIT_CARD: return ccps;
case CHEQUE: return cps;
case PAYPAL: return ppps;
default: return null;
}
}]]></programlisting>
<para>
Wait, what if <literal>CreditCardPaymentStrategy</literal> is a request-scoped bean? Then the producer
method has the effect of "promoting" the current request scoped instance into session scope. This is almost
certainly a bug! The request scoped object will be destroyed by the container before the session ends, but
the reference to the object will be left "hanging" in the session scope. This error will
<emphasis>not</emphasis> be detected by the container, so please take extra care when returning bean
instances from producer methods!
</para>
<para>
There's at least three ways we could go about fixing this bug. We could change the scope of the
<literal>CreditCardPaymentStrategy</literal> implementation, but this would affect other clients of that
bean. A better option would be to change the scope of the producer method to <literal>@Dependent</literal>
or <literal>@RequestScoped</literal>.
</para>
<para>But a more common solution is to use the special <literal>@New</literal> qualifier annotation.</para>
</section>
<section>
<title>Use of <literal>@New</literal> with producer methods</title>
<para>Consider the following producer method:</para>
<programlisting role="JAVA"><![CDATA[@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy(@New CreditCardPaymentStrategy ccps,
@New CheckPaymentStrategy cps,
@New PayPalPaymentStrategy ppps) {
switch (paymentStrategy) {
case CREDIT_CARD: return ccps;
case CHEQUE: return cps;
case PAYPAL: return ppps;
default: return null;
}
}]]></programlisting>
<para>
Then a new <emphasis>dependent</emphasis> instance of <literal>CreditCardPaymentStrategy</literal> will be
created, passed to the producer method, returned by the producer method and finally bound to the session
context. The dependent object won't be destroyed until the <literal>Preferences</literal> object is destroyed,
at the end of the session.
</para>
</section>
<section>
<title>Disposer methods</title>
<para>
Some producer methods return objects that require explicit destruction. For example, somebody needs to
close this JDBC connection:
</para>
<programlisting role="JAVA"><![CDATA[@Produces @RequestScoped Connection connect(User user) {
return createConnection(user.getId(), user.getPassword());
}]]></programlisting>
<para>Destruction can be performed by a matching <emphasis>disposer method</emphasis>, defined
by the same class as the producer method:</para>
<programlisting role="JAVA"><![CDATA[void close(@Disposes Connection connection) {
connection.close();
}]]></programlisting>
<para>
The disposer method must have at least one parameter, annotated <literal>@Disposes</literal>, with the same
type and qualifiers as the producer method. The disposer method is called automatically when the context ends
(in this case, at the end of the request), and this parameter receives the object produced by the producer
method. If the disposer method has additional method parameters, the container will look for a bean that
satisfies the type and qualifiers of each parameter and pass it to the method automatically.
</para>
</section>
<!--
vim:et:ts=3:sw=3:tw=120
-->
</chapter>