CUBA Component References



The basic idea of a component-based system is the separation of complex functionality in loosly coupled, independent modules. As an application usually consists of multiple components, the EJB standard defines a flexible concept for inter-component interaction. In general, components are defined to depend on the interface of other components only and refer to them by a symbolic lookup name. The mapping between the symbolic name and a concrete component (implementation) is done during application assembly respectively deployment. The lookup is either performed by the component itself or by the container which in this case provides the references by dependency injection (see below). These concepts can be found in CUBA too.

The following code shows a component implementation looking up a second component and calling its interface functions:
 
public class ACompImpl extends AbstractComponent implements AComp {
  public String foo() throws RemoteException {
    try {
      System.out.println("foo");
      ClientContextI cc = getContext().getClientContext();
      BComp bcomp = (BComp)cc.getComponent("ComponentB");
      String bar = bcomp.bar();
      return "foo" + bar;
    }
    catch(Exception x) {
      throw new ComponentException(x.getMessage());
    }
  }
}

The code above performs a global lookup which can only be performed through a client context. A component can obtain a client context by calling getClientContext() on its own component context. Global lookups are simple to achieve because they do not require any particular configuration work. Every component just behaves like an external client, using the global component name as lookup identifier. However, global lookups have some important drawbacks as known from the EJB standard.

For more information about the differences of global and local component lookups, see the EJB specification. CUBA also supports local lookups as they are known from the EJB standard. In fact every lookup performed through a component context is a local lookup. A local lookup would simplify the code above like this.
 
public class ACompImpl extends AbstractComponent implements AComp {
  public String foo() throws RemoteException {
    try {
      System.out.println("foo");
      BComp bcomp = (BComp)getContext().getComponent("bref");
      String bar = bcomp.bar();
      return "foo" + bar;
    }
    catch(Exception x) {
      throw new ComponentException(x.getMessage());
    }
  }
}

However, in this case the local lookup name "bref" must be declared in the component's deployment descriptor and mapped to a globally defined component, implementing the appropiate interface. The following component deployment descriptor shows the reference declaration:
 
<component-jar>
  <component>
    <component-name>ComponentA</component-name>
    <external-interface>acomp.AComp</external-interface>
    <component-class>acomp.ACompImpl</component-class>
    <component-refs>
      <component-ref ref-name="bref" interface="bcomp.BComp" type="external"/>
    </component-refs>
  </component>
</component-jar>

The mapping to a concrete component is not standardized in the EJB specification and is part of the vendor-specific extension descriptor of a J2EE application. Using the CUBA component in the wired container requires a declaration like this in the wired-application.xml.
 
<?xml version="1.0" encoding="UTF-8"?>
<wired-application>
  <modules>
    <wired>compref.jar</wired>
  </modules>

  <references>
  <component-references component-name="ComponentA">

    <component-refs>
     <component-ref ref-name="bref" refers-global-name="ComponentB" type="external" />
    </component-refs>
  </component-references>
  </references>

</wired-application>

As most applications contain only a single implementation for a component interface, the wired container provides an auto-resolvement for convenience purposes.

<?xml version="1.0" encoding="UTF-8"?>
<wired-application>
  <modules>
    <wired>compref.jar</wired>
  </modules>

  <references resolve="auto" />

</wired-application>

This will cause the wired container to link any component reference to the one-and-only matching provider component for the requested interface. Auto-resolvement is only performed for those references which are not resolved explicitely, i.e. manual and automatic resolvement can also be mixed. A value of "auto" takes all provider components in account, which either provide exactly the required interface or a derivation of it. The value "auto-exact" limits the automatic matching to exact interface equality.
The additional attribute info causes the wired container to print information about the component resolvement after successful initialization. A value of "auto" prints out all auto-resolvements to standard out, a value of "all" prints both automatic and manual resolvements. The output is provided as an XML sniplet, using the schema for manual resolvement specifications as shown above. This allows e.g. to directly copy the output of an auto resolvement to an application descriptor again.

Dependency Injection

As an alternative to looking up dependent parts in a component's code, the lookup can also be performed behind the scenes by the container which then passes references by calling appropriate setter methods or setting public members. This concept is also known as 'dependency injection'. The following example shows a modification of the ACompImpl above making use of dependency injection:

public class ACompImpl extends AbstractComponent implements AComp {
  @Component BComp bref; // will be initialized by container
  public String foo() throws RemoteException {
    try {
      System.out.println("foo");
      // Lookup no longer required here
      String bar = bref.bar();
      return "foo" + bar;
    }
    catch(Exception x) {
      throw new ComponentException(x.getMessage());
    }
  }
}

If you like to use dependency injection without code annotations, the annotation @Component can be ommited and the member to initialize must be added to the reference declaration in the component's XML descriptor like this:
 
<component-jar>
  <component>
    <component-name>ComponentA</component-name>
    <external-interface>acomp.AComp</external-interface>
    <component-class>acomp.ACompImpl</component-class>
    <component-refs>
      <component-ref ref-name="bref" interface="bcomp.BComp"
        type="external" injection-target="bref"/>

    </component-refs>
  </component>
</component-jar>

ATTENTION:
Dependency injection is sometimes propagated as an approach for component tests by simply instanciating component implementation classes and passing them as dependent resources to other component instances. We strongly recommend to keep from using such a technique because it bypasses any container-provided services (e.g. container-managed transactions). CUBA's wired container is also perfectly suitable as a test container for EJBs - see page CUBA-Testing.html for further details.

An example for component references in CUBA is available at examples/compref.


Home Introduction Javadoc