Injection
Main article: Injection
Contents
Table of Contents |
---|
exclude | Contents|Injection |
---|
printable | false |
---|
|
What's the difference between the @Component
and @InjectComponent
annotations?
The @Component
annotation is used to define the type of component, and its parameter bindings. When using @Component
, the template must not define the type, and any parameter bindings are merged in:
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
<a t:id="home" class="nav">Back to home</a>
|
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
@Component(parameters={ "page=index" })
private PageLink home;
|
...
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
<t:form t:id="login"> .... </t:form>
|
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
@InjectComponent
private Form login;
|
Again, we're matching the field name to the component id, and you would get an error if the component is not defined in the template.
What's the difference between the @InjectPage
and @InjectContainer
annotations?
The @InjectPage
annotation is used to inject some page in the application into a field of some other page. You often see it used from event handler methods:
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
@InjectPage
private ConfirmRegistration confirmRegistration;
Object onSuccessFromRegistrationForm()
{
confirmRegistration.setStatus("Registration accepted");
confirmRegistration.setValidationCode(userRegistrationData.getValidationCode());
return confirmRegistration;
}
|
...
In a mixin, it injects the component to which the mixin is attached.
I get an exception because I have two services with the same interface, how do I handle this?
It's not uncommon to have two or more services that implement the exact same interface. When you inject, you might start by just identifying the type of service to inject:
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
@Inject
private ComponentEventResultProcessor processor;
|
...
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
@InjectService("ComponentEventResultProcessor")
private ComponentEventResultProcessor processor;
|
...
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
@Marker(
{ Primary.class, Traditional.class })
public ComponentEventResultProcessor buildComponentEventResultProcessor(
Map<Class, ComponentEventResultProcessor> configuration)
{
return constructComponentEventResultProcessor(configuration);
}
|
...
Code Block |
---|
controls | true |
---|
linenumbers | true |
---|
|
@Inject
@Traditional @Primary
private ComponentEventResultProcessor processor;
|
The two marker annotations, @Traditional
and @Primary
, ensure that only a single service matches.
What's the difference between @Inject
and @Environmental
?
@Inject
is relatively general; it can be used to inject resources specific to a page or component (such as ComponentResources, Logger, or Messages), or it can inject services or other objects obtained from the Tapestry IoC container. Once the page is loaded, the values for these injections never change.
@Environmental
is different; it exposes a request-scoped, dynamically bound value
Footnote |
---|
. The term "Environmental" was chosen as the value "comes from the environment", whatever that means. A name more evocative of its function still has not occurred to the Tapestry team! |
. :- "Request scoped"Request scoped: different threads (processing different requests) will see different values when reading the field.
- "Dynamically bound": the value is explicitly placed into the Environment, and can be overridden at any time.
Environmentals are a form of loosely connected communication between an outer component (or even a service) and an inner component. Example: the Form component places a FormSupport
object into the environment. Other components, such as TextField, use the FormSupport
when rendering to perform functions such as allocate unique control names or register client-side validations. The TextField doesn't require that the Form component be the immediate container component, or even an ancestor: a Form on one page may, indirectly, communicate with a TextField on some entirely different page. Neither component directly links to the other, the FormSupport
is the conduit that connects them.
The term "Environmental" was chosen as the value "comes from the environment".
But wait ... I see I used the @Inject
annotation and it still worked. What gives?
In certain cases, Tapestry exposes a service (which can be injected) that is a proxy to the environmental; this is primarily for common environmentals, such as JavaScriptSupport, that may be needed outside of component classes. You can see this in TapestryModule:
Code Block |
---|
| Java |
---|
| Java |
---|
title | TapestryModule.java (partial) |
---|
|
/**
* Builds a proxy to the current {@link JavaScriptSupport} inside this thread's {@link Environment}.
*
* @since 5.2.0
*/
public JavaScriptSupport buildJavaScriptSupport()
{
return environmentalBuilder.build(JavaScriptSupport.class);
}
|
This kind of logic is based on the EnvironmentalShadowBuilder service.
Ok, but Request is a singleton service, not an environmental, and I can inject that. Is Tapestry really thread safe?
Yes, of course Tapestry is thread safe. The Request service is another special case, as seen in TapestryModule:
Code Block |
---|
| Java |
---|
| Java |
---|
title | TapestryModule.java (partial) |
---|
|
public Request buildRequest()
{
return shadowBuilder.build(requestGlobals, "request", Request.class);
}
|
RequestGlobals is a per-thread service. The Request service is a global singleton created by the PropertyShadowBuilder service, but is just a proxy. It has no internal state; invoking a method on the Request service just turns around and extracts the Request object from the per-thread RequestGlobals and invokes the same method there.
I use @Inject
on a field to inject a service, but the field is still null, what happened?
This can happen when you use the wrong @Inject
annotation; for example, com.google.inject.Inject instead of org.apache.tapestry5.ioc.annotations.Inject. This can occur when you have TestNG on the classpath, for example, and your IDE is too helpful. Double check your imports when things seem weird.
Also remember that @Inject
on fields works for components and for service implementations or other objects that Tapestry instantiates, but not on arbitrary objects (that are created via Java's new keyword).
...
display-footnotes