Read also: JSF and Factory pattern - part I
We can use CDI in JSF, so we can consider the below example the simplest factory method pattern implementation. Basically, the Java EE annotations and dependency injection helps us to simply the things pretty much:
We can use CDI in JSF, so we can consider the below example the simplest factory method pattern implementation. Basically, the Java EE annotations and dependency injection helps us to simply the things pretty much:
public class
Foo {
@Produces
public String sayHelloFoo() {
return "Foo says hello !";
}
}
@Named
@RequestScoped
public class
MyBean {
@Inject
private String foo;
public void fooSays(){
System.out.println(foo);
}
}
The sayHelloFoo() is known
as a producer method and in this case it produces a String. The
"beneficiary" of this String is MyBean, which
practically injects this type via @Inject as above.
CDI knows where to inject the produced String because
the produced type is the same as the injected type.
But, a producer
method can produce anything else, like primitive data types or objects. For
example, let's suppose that we have the Foo object:
public class
Foo {
public String sayHelloFoo() {
return "Foo says hello !";
}
}
Now, the
producer will produce Foo instances instead of String:
public class
FooProducer {
@Produces
public Foo fooFactory() {
return new Foo();
}
}
And MyBean will
inject Foo type
(with other words, the container is injecting the beans created by the factory
using the @Inject annotation):
@Named
@RequestScoped
public class
MyBean {
@Inject
private Foo foo;
public void fooSays(){
System.out.println(foo.sayHelloFoo());
}
}
But, this
example will produce an error of type: WELD-001409: Ambiguous dependencies
for type Foo with qualifiers @Default. Remember that we said that CDI relies
on types to determine where to inject the produced type, but in this case we
have two instances of the Foo:
Possible
dependencies:
- Producer Method [Foo] with qualifiers [@Any
@Default] declared as [[BackedAnnotatedMethod] @Produces public
beans.FooBuzzProducer.fooFactory()],
- Managed Bean [class beans.Foo] with
qualifiers [@Any @Default],
So, the
container doesn't know which instance to inject! This is causing an ambiguity!
The
solution relies on CDI qualifiers. The qualifiers have the power to
disambiguate the beans. For this, we use the @Qualifier and @interface
annotations, as below (further reading about qualifiers: Create
Qualifiers for CDI Beans and Using Qualifiers):
@Qualifier
@Retention(RUNTIME)
@Target({METHOD,
FIELD})
public @interface
FooQualifier
{
}
Further,
the qualifier is used to annotate the producer method:
public class
FooProducer {
@Produces @FooQualifier
public Foo fooFactory() {
return new Foo();
}
}
And the
injection points also:
@Named
@RequestScoped
public class
MyBean {
@Inject @FooQualifier
private Foo foo;
public void fooSays(){
System.out.println(foo.sayHelloFoo());
}
}
Now, let's
have an example with a producer for two types, Foo and Buzz.
public class
Foo
{
public String sayHelloFoo() {
return "Foo says hello !";
}
}
public class
Buzz
{
public String sayHelloBuzz() {
return "Buzz says hello !";
}
}
For each
of them we need a qualifier:
@Qualifier
@Retention(RUNTIME)
@Target({METHOD,
FIELD})
public
@interface FooQualifier
{
}
@Qualifier
@Retention(RUNTIME)
@Target({METHOD,
FIELD})
public
@interface BuzzQualifier
{
}
Now, we
have a producer for Foo and Buzz:
public class
FooBuzzProducer
{
@Produces @FooQualifier
public Foo fooFactory() {
return new Foo();
}
@Produces @BuzzQualifier
public Buzz buzzFactory() {
return new Buzz();
}
}
And,
finally exploit the factory in MyBean:
@Named
@RequestScoped
public class
MyBean {
@Inject @FooQualifier
private Foo foo;
@Inject @BuzzQualifier
private Buzz buzz;
public void fooSays(){
System.out.println(foo.sayHelloFoo());
}
public void buzzSays(){
System.out.println(buzz.sayHelloBuzz());
}
}
Use @Any and factory pattern
Further,
let's complicate the things and let's suppose that we have the following
interface:
public
interface CharacterType
{
public String sayHello();
}
Think that
you have several implementations of this interface (e.g. Foo, Buzz). Based on the above examples, you can implement
the factory pattern by writing a producer method for each implementation. This
way you will obtain a pretty clumsy to maintain code because adding a new
implementation will involve adding a new producer, removing an existing implementation
will require to remove the producer and so on. Java EE comes to rescue this
situation via @Any annotation, which allows an injection point to refer to all beans
or all events of a certain bean type. With other words, it will
allows us to instruct the container that all beans implementing the given
interface (e.g. CharacterType) should be injected at that injection
point. Further, you can use a simple trick to distinguish between the injected
beans. This trick take usage of annotation literals and enum types. First, we
define a qualifier that uses an enum of character types:
@Qualifier
@Retention(RUNTIME)
@Target({METHOD,
FIELD, PARAMETER, TYPE})
public
@interface CharacterQualifier
{
Type value();
enum Type {
FOO,
BUZZ
}
}
Next, each
CharacterType
implementation is annotated with this qualifier and the corresponding type:
@CharacterQualifier(CharacterQualifier.Type.FOO)
@Dependent
public class
Foo
implements CharacterType
{
@Override
public String sayHello() {
return "Foo says hello !";
}
}
@CharacterQualifier(CharacterQualifier.Type.BUZZ)
@Dependent
public class
Buzz
implements CharacterType
{
@Override
public String sayHello() {
return "Buzz says hello !";
}
}
The next
step consist in creating an AnnotationLiteral. In order to select the dependency you need to compare
it with the enum type (FOO, BUZZ) of the qualifier(CharacterQualifier) of each implementation by creating an AnnotationLiteral of the corresponding
type:
@SuppressWarnings("AnnotationAsSuperInterface")
public class
CharacterLiteral
extends AnnotationLiteral<CharacterQualifier>
implements CharacterQualifier {
private static final long serialVersionUID =
1L;
private final Type type;
public CharacterLiteral(Type type) {
this.type = type;
}
@Override
public Type value() {
return type;
}
}
Now the
factory class is pretty simple:
@Dependent
public class
CharactersFactory
{
@Inject @Any
private Instance<CharacterType> characters;
public CharacterType getCharacter(CharacterQualifier.Type
type) {
CharacterLiteral characterLiteral = new
CharacterLiteral(type);
Instance<CharacterType> characterType =
characters.select(characterLiteral);
return characterType.get();
}
}
The client
of our factory needs to inject this factory and invoke the getCharacter() method.
Of course, the client needs to indicate the character type (e.g. BUZZ type):
@Named
@RequestScoped
public class
MyBean {
@Inject
CharactersFactory factory;
public void characterSays() {
CharacterType buzz = factory.getCharacter(CharacterQualifier.Type.BUZZ);
System.out.println(buzz.sayHello());
}
}
Niciun comentariu :
Trimiteți un comentariu