Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: restore

Wiki Markup
{scrollbar}

Injection

Main article: Injection FAQ

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
controlstrue
linenumberstrue
  <a t:id="home" class="nav">Back to home</a>
Code Block
controlstrue
linenumberstrue
  @Component(parameters={ "page=index" })
  private PageLink home;

Here the type of component is defined by the field type. The field name is matched against the t:id in the template. The page parameter is set in the Java class, and the informal class parameter is set in the template. If the tag in the template was <t:pagelink>, or if the template tag included the attribute t:type="pagelink", then you would see an exception.

By contrast, @InjectComponent expects the component to be already defined, and doesn't allow any configuration of it:

Code Block
controlstrue
linenumberstrue
  <t:form t:id="login"> .... </t:form>
Code Block
controlstrue
linenumberstrue
  @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.

...

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
controlstrue
linenumberstrue
  @InjectPage
  private ConfirmRegistration confirmRegistration;

  Object onSuccessFromRegistrationForm()
  {
    confirmRegistration.setStatus("Registration accepted");
    confirmRegistration.setValidationCode(userRegistrationData.getValidationCode());

    return confirmRegistration;
  }

This code pattern is used to configure peristent properties of a page before returning it; Tapestry will send a client redirect to the page to present the data.

...

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
controlstrue
linenumberstrue
	@Inject
	private ComponentEventResultProcessor processor;

Which results in the error: Service interface org.apache.tapestry5.services.ComponentEventResultProcessor is matched by 3 services: AjaxComponentEventResultProcessor, ComponentEventResultProcessor, ComponentInstanceResultProcessor. Automatic dependency resolution requires that exactly one service implement the interface.

We need more information than just the service interface type in order to identify which of the three services to inject. One possibility is to inject with the correct service id:

Code Block
controlstrue
linenumberstrue
	@InjectService("ComponentEventResultProcessor")
	private ComponentEventResultProcessor processor;

This works ... but it is clumsy. If the service id, "ComponentEventResultProcessor", ever changes, this code will break. It's not refactoring safe.

Instead, we should use marker annotations. If we look at TapestryModule, where the ComponentEventResultProcessor service is defined, we'll see it identifies the necessary markers:

Code Block
controlstrue
linenumberstrue
    @Marker(
    { Primary.class, Traditional.class })
    public ComponentEventResultProcessor buildComponentEventResultProcessor(
            Map<Class, ComponentEventResultProcessor> configuration)
    {
        return constructComponentEventResultProcessor(configuration);
    }

When a service has marker annotations, the annotations present at the point of injection (the field, method parameter, or constructor parameter) are used to select a matching service. The list of services that match by type is then filtered to only include services that have all of the marker annotations present at the point of injection.

Code Block
controlstrue
linenumberstrue
    @Inject
	@Traditional @Primary
	private ComponentEventResultProcessor processor;

The two marker annotations, @Traditional and @Primary, ensure that only a single service matches.

...

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
titleTapestryModule.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.

...

Yes, of course Tapestry is thread safe. The Request service is another special case, as seen in TapestryModule:

Code Block
Java
Java
titleTapestryModule.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.

...

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).

Wiki Markup
{scrollbar}