SeamFramework.orgCommunity Documentation
Web Beans suporta três principais mecanismos de injeção de dependências:
Injeção de parametros no construtor:
public class Checkout {
private final ShoppingCart cart;
@Initializer
public Checkout(ShoppingCart cart) {
this.cart = cart;
}
}
Initializer injeção por parâmetro de método:
public class Checkout {
private ShoppingCart cart;
@Initializer
void setShoppingCart(ShoppingCart cart) {
this.cart = cart;
}
}
E injeção direta de campos:
public class Checkout {
private @Current ShoppingCart cart;
}
A injeção de dependências sempre ocorre quando a instância do Web Bean é instanciada.
Em primeiro lugar, a gerenciador do Web Bean chama o construtor do Web Bean para obter uma instância do Web Bean.
Em seguida, o gerenciador do Web Bean inicializa os valores de todos os campos injetados do Web Bean.
Em seguida, o gerenciador do Web Bean chama todos os métodos do Web Bean.
Finalmente, o método @PostConstruct
do Web Bean, se for o caso, é chamado.
Injeção de parâmetros no construtor não é suportado em EJB beans, uma vez que o EJB é instanciado pelo container EJB e não pelo gerenciador do Web Bean.
Parameters of constructors and initializer methods need not be explicitly annotated when the default binding type @Current
applies. Injected fields, however, must specify a binding type, even when the default binding type applies. If the field does not specify a binding type, it will not be injected.
Métodos produtores também suportam injeção de parâmetros:
@Produces Checkout createCheckout(ShoppingCart cart) {
return new Checkout(cart);
}
Por fim, métodos de observação (que iremos detalhar em Capítulo 9, Eventos), métodos de disposal e métodos destrutores, todos suportam injeção de parâmetros.
The Web Beans specification defines a procedure, called the typesafe resolution algorithm, that the Web Bean manager follows when identifying the Web Bean to inject to an injection point. This algorithm looks complex at first, but once you understand it, it's really quite intuitive. Typesafe resolution is performed at system initialization time, which means that the manager will inform the user immediately if a Web Bean's dependencies cannot be satisfied, by throwing a UnsatisfiedDependencyException
or AmbiguousDependencyException
.
O objetivo deste algoritmo é permitir que vários Web Beans implementem o mesmo tipo de API e também:
permitir que o cliente escolha a implementação que lhe melhor convier utilizando anotações de binding (binding annotations),
permitir ao implantador (deployer) da aplicação escolher qual a implentação é adequada para uma determinada implantação, sem alterações para o cliente, por ativar ou desativar tipos de implantação (deployment types), ou
permitir uma implementação de uma API sobrescrever uma outra implementação da mesma API em tempo de implantação, sem alterações no cliente, utilizando precedência do tipo de implantação (deployment type precedence).
Vamos explorer como o gerenciador do Web Beans determina qual o Web Bean deve ser injetado.
If we have more than one Web Bean that implements a particular API type, the injection point can specify exactly which Web Bean should be injected using a binding annotation. For example, there might be two implementations of PaymentProcessor
:
@PayByCheque
public class ChequePaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
@PayByCreditCard
public class CreditCardPaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
Onde @PayByCheque
e @PayByCreditCard
são anotações de binding:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCreditCard {}
Um desenvolvedor cliente de um Web Bean utiliza a anotação de binding para especificar exatamente quais Web Bean devem ser injetados.
Utilizando injeção por campos (field injection):
@PayByCheque PaymentProcessor chequePaymentProcessor;
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;
Utilizando injeção de método de inicialização:
@Initializer
public void setPaymentProcessors(@PayByCheque PaymentProcessor chequePaymentProcessor,
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
this.chequePaymentProcessor = chequePaymentProcessor;
this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}
Ou utilizando injeção de construtor
@Initializer
public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor,
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
this.chequePaymentProcessor = chequePaymentProcessor;
this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}
Binding annotations may have members:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayBy {
PaymentType value();
}
Em cada caso, o valor do membro é significante:
@PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor;
@PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;
You can tell the Web Bean manager to ignore a member of a binding annotation type by annotating the member @NonBinding
.
Um ponto de injeção pode até mesmo especificar múltiplas anotações de binding:
@Asynchronous @PayByCheque PaymentProcessor paymentProcessor
Neste caso, só o Web Bean que tem ambas anotações de binding são elegíveis para injeção.
Mesmo métodos produtores podem especificar anotações de binding:
@Produces
@Asynchronous @PayByCheque
PaymentProcessor createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
return new AsynchronousPaymentProcessor(processor);
}
Web Beans defines a binding type @Current
that is the default binding type for any injection point or Web Bean that does not explicitly specify a binding type.
Há duas situações comuns nas quais é necessário indicar explicitamente @Current
:
em um campo, a fim de declará-lo como um campo injetado com o tipo de binding padrão, e
em um Web Bean, que tem um outro tipo de binding além do tipo padrão de binding.
All Web Beans have a deployment type. Each deployment type identifies a set of Web Beans that should be conditionally installed in some deployments of the system.
For example, we could define a deployment type named @Mock
, which would identify Web Beans that should only be installed when the system executes inside an integration testing environment:
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@DeploymentType
public @interface Mock {}
Suponha que temos alguns Web Beans que interajam com um sistema externo para processar pagamentos:
public class ExternalPaymentProcessor {
public void process(Payment p) {
...
}
}
Uma vez que esse Web Bean não especifica explicitamente um tipo de implantação, ele tem o tipo de implantação padrão @Production
.
For integration or unit testing, the external system is slow or unavailable. So we would create a mock object:
@Mock
public class MockPaymentProcessor implements PaymentProcessor {
@Override
public void process(Payment p) {
p.setSuccessful(true);
}
}
But how does the Web Bean manager determine which implementation to use in a particular deployment?
Web Beans defines two built-in deployment types: @Production
and @Standard
. By default, only Web Beans with the built-in deployment types are enabled when the system is deployed. We can identify additional deployment types to be enabled in a particular deployment by listing them in web-beans.xml
.
Going back to our example, when we deploy our integration tests, we want all our @Mock
objects to be installed:
<WebBeans>
<Deploy>
<Standard/>
<Production/>
<test:Mock/>
</Deploy>
</WebBeans
>
Now the Web Bean manager will identify and install all Web Beans annotated @Production
, @Standard
or @Mock
at deployment time.
The deployment type @Standard
is used only for certain special Web Beans defined by the Web Beans specification. We can't use it for our own Web Beans, and we can't disable it.
The deployment type @Production
is the default deployment type for Web Beans which don't explicitly declare a deployment type, and may be disabled.
If you've been paying attention, you're probably wondering how the Web Bean manager decides which implementation ExternalPaymentProcessor
or MockPaymentProcessor
to choose. Consider what happens when the manager encounters this injection point:
@Current PaymentProcessor paymentProcessor
There are now two Web Beans which satisfy the PaymentProcessor
contract. Of course, we can't use a binding annotation to disambiguate, since binding annotations are hard-coded into the source at the injection point, and we want the manager to be able to decide at deployment time!
The solution to this problem is that each deployment type has a different precedence. The precedence of the deployment types is determined by the order in which they appear in web-beans.xml
. In our example, @Mock
appears later than @Production
so it has a higher precedence.
Whenever the manager discovers that more than one Web Bean could satisfy the contract (API type plus binding annotations) specified by an injection point, it considers the relative precedence of the Web Beans. If one has a higher precedence than the others, it chooses the higher precedence Web Bean to inject. So, in our example, the Web Bean manager will inject MockPaymentProcessor
when executing in our integration testing environment (which is exactly what we want).
It's interesting to compare this facility to today's popular manager architectures. Various "lightweight" containers also allow conditional deployment of classes that exist in the classpath, but the classes that are to be deployed must be explicity, individually, listed in configuration code or in some XML configuration file. Web Beans does support Web Bean definition and configuration via XML, but in the common case where no complex configuration is required, deployment types allow a whole set of Web Beans to be enabled with a single line of XML. Meanwhile, a developer browsing the code can easily identify what deployment scenarios the Web Bean will be used in.
Deployment types are useful for all kinds of things, here's some examples:
@Mock
and @Staging
deployment types for testing
@AustralianTaxLaw
for site-specific Web Beans
@SeamFramework
, @Guice
for third-party frameworks which build on Web Beans
@Standard
for standard Web Beans defined by the Web Beans specification
I'm sure you can think of more applications...
The typesafe resolution algorithm fails when, after considering the binding annotations and and deployment types of all Web Beans that implement the API type of an injection point, the Web Bean manager is unable to identify exactly one Web Bean to inject.
It's usually easy to fix an UnsatisfiedDependencyException
or AmbiguousDependencyException
.
To fix an UnsatisfiedDependencyException
, simply provide a Web Bean which implements the API type and has the binding types of the injection point or enable the deployment type of a Web Bean that already implements the API type and has the binding types.
To fix an AmbiguousDependencyException
, introduce a binding type to distinguish between the two implementations of the API type, or change the deployment type of one of the implementations so that the Web Bean manager can use deployment type precedence to choose between them. An AmbiguousDependencyException
can only occur if two Web Beans share a binding type and have exactly the same deployment type.
There's one more issue you need to be aware of when using dependency injection in Web Beans.
Clientes de um Web Bean injetado, geralmente não possuem uma referência direta a uma instância do Web Bean .
Imagine that a Web Bean bound to the application scope held a direct reference to a Web Bean bound to the request scope. The application scoped Web Bean is shared between many different requests. However, each request should see a different instance of the request scoped Web bean!
Now imagine that a Web Bean bound to the session scope held a direct reference to a Web Bean bound to the application scope. From time to time, the session context is serialized to disk in order to use memory more efficiently. However, the application scoped Web Bean instance should not be serialized along with the session scoped Web Bean!
Therefore, unless a Web Bean has the default scope @Dependent
, the Web Bean manager must indirect all injected references to the Web Bean through a proxy object. This client proxy is responsible for ensuring that the Web Bean instance that receives a method invocation is the instance that is associated with the current context. The client proxy also allows Web Beans bound to contexts such as the session context to be serialized to disk without recursively serializing other injected Web Beans.
Unfortunately, due to limitations of the Java language, some Java types cannot be proxied by the Web Bean manager. Therefore, the Web Bean manager throws an UnproxyableDependencyException
if the type of an injection point cannot be proxied.
Os seguintes tipos Java não podem ser "proxied" pelo gerenciador do Web Bean:
classes que são declaradas final
ou que tenham um método final
,
classes que não têm um construtor não privado sem parâmetros, e
arrays e tipos primitivos.
It's usually very easy to fix an UnproxyableDependencyException
. Simply add a constructor with no parameters to the injected class, introduce an interface, or change the scope of the injected Web Bean to @Dependent
.
A aplicação pode obter uma instância da interface Manager
por injeção:
@Current Manager manager;
O objeto Manager
fornece um conjunto de métodos para a obtenção de uma instância de um Web Bean programaticamente.
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class);
Binding annotations may be specified by subclassing the helper class AnnotationLiteral
, since it is otherwise difficult to instantiate an annotation type in Java.
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new AnnotationLiteral<CreditCard
>(){});
If the binding type has an annotation member, we can't use an anonymous subclass of AnnotationLiteral
instead we'll need to create a named subclass:
abstract class CreditCardBinding
extends AnnotationLiteral<CreditCard
>
implements CreditCard {}
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new CreditCardBinding() {
public void value() { return paymentType; }
} );
Enterprise Web Beans support all the lifecycle callbacks defined by the EJB specification: @PostConstruct
, @PreDestroy
, @PrePassivate
and @PostActivate
.
Web Beans simples suportam apenas chamadas a @PostConstruct
e @PreDestroy
.
Both enterprise and simple Web Beans support the use of @Resource
, @EJB
and @PersistenceContext
for injection of Java EE resources, EJBs and JPA persistence contexts, respectively. Simple Web Beans do not support the use of @PersistenceContext(type=EXTENDED)
.
A chamada ao @PostConstruct
sempre ocorre após todas as dependências serem injetadas.
There are certain kinds of dependent objects Web Beans with scope @Dependent
that need to know something about the object or injection point into which they are injected in order to be able to do what they do. For example:
The log category for a Logger
depends upon the class of the object that owns it.
Injection of a HTTP parameter or header value depends upon what parameter or header name was specified at the injection point.
Injeção do resultado da avaliação de uma expressão EL depende da expressão que foi especificada no ponto de injeção.
A Web Bean with scope @Dependent
may inject an instance of InjectionPoint
and access metadata relating to the injection point to which it belongs.
Let's look at an example. The following code is verbose, and vulnerable to refactoring problems:
Logger log = Logger.getLogger(MyClass.class.getName());
This clever little producer method lets you inject a JDK Logger
without explicitly specifying the log category:
class LogFactory {
@Produces Logger createLogger(InjectionPoint injectionPoint) {
return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
}
Podemos agora escrever:
@Current Logger log;
Not convinced? Then here's a second example. To inject HTTP parameters, we need to define a binding type:
@BindingType
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
@NonBinding public String value();
}
We would use this binding type at injection points as follows:
@HttpParam("username") String username;
@HttpParam("password") String password;
O seguinte método produtor faz o trabalho:
class HttpParams
@Produces @HttpParam("")
String getParamValue(ServletRequest request, InjectionPoint ip) {
return request.getParameter(ip.getAnnotation(HttpParam.class).value());
}
}
(Note that the value()
member of the HttpParam
annotation is ignored by the Web Bean manager since it is annotated @NonBinding.
)
The Web Bean manager provides a built-in Web Bean that implements the InjectionPoint
interface:
public interface InjectionPoint {
public Object getInstance();
public Bean<?> getBean();
public Member getMember():
public <T extends Annotation
> T getAnnotation(Class<T
> annotation);
public Set<T extends Annotation
> getAnnotations();
}