Versions Compared

Key

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

Scrollbar

Even today, with the overwhelming success of Spring and the rise of smaller, simpler approaches to building applications (in contrast to the heavyweight EJB 2.0 approach), many people still have trouble wrapping their heads around Inversion of Control.

...

So the IoC container is the "town" and in the world of the IoC container, everything has a name, a place, and a relationship to everything else in the container. Tapestry calls this world "The Registry".

Image RemovedImage Added

Here we're seeing a few services from the built-in Tapestry IoC module, and a few of the services from the Tapestry web framework module. In fact, there are over 100 services, all interrelated, in the Registry ... and that's before you add your own to the mix. The IoC Registry treats all the services uniformly, regardless of whether they are part of Tapestry, or part of your application, or part of an add-on library.

...

Dependency Injection

Main Article: Injection Tapestry IoC Overview

Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel = "injection" and space = currentSpace()

Inversion of Control refers to the fact that the container, here Tapestry IoC's Registry, instantiates your classes. It decides on when the classes get instantiated.

...

This code is for a metric that periodically counts the number of rows in a key database table. Other implementations of MetricProducer will be responsible for measuring CPU utilization, available disk space, number of requests per second, and so forth.

Code Block
java
java
public class TableMetricProducer implements MetricProducer
{
  . . . 

  public void execute() 
  {
    int rowCount = . . .;
    Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount);
    new QueueWriter().sendMetric(metric);
  }
}

We've omitted some of the details (this code will need a database URL or a connection pool to operate), so as to focus on the one method and it's relationship to the QueueWriter class.

Obviously, this code has a problem ... we're creating a new QueueWriter for each metric we write into the queue, and the QueueWriter presumably is going to open the JMS queue fresh each time, an expensive operation. Thus:

Code Block
java
java
public class TableMetricProducer implements MetricProducer
{
  . . . 

  private final QueueWriter queueWriter = new QueueWriter();

  public void execute() 
  {
    int rowCount = . . .;
    Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount);
    queueWriter.sendMetric(metric);
  }

That's better. It's not perfect ... a proper system might know when the application was being shutdown and would shut down the JMS Connection inside the QueueWriter as well.

Here's a more immediate problem: JMS connections are really meant to be shared, and we'll have lots of little classes collecting different metrics. So we need to make the QueueWriter shareable:

Code Block
java
java
  private final QueueWriter queueWriter = QueueWriter.getInstance();

... and inside class QueueWriter:

Code Block
java
java
public class QueueWriter
{
  private static QueueWriter instance;

  private QueueWriter()
  {
    ...
  }

  public static getInstance()
  {
    if (instance == null)
    {
      instance = new QueueWriter();
    }
    return instance;
  }
}

Much better! Now all the metric producers running inside all the threads can share a single QueueWriter. Oh wait ...

Code Block
java
java
  public synchronized static getInstance()
  {
    if (instance == null)
    {
      instance = new QueueWriter();
    }
    return instance;
  }

Is that necessary? Yes. Will the code work without it? Yes – 99.9% of the time. In fact, this is a very common error in systems that manually code a lot of these construction patterns: forgetting to properly synchronize access. These things often work in development and testing, but fail (with infuriating infrequency) in production, as it takes two or more threads running simultaneously to reveal the coding error.

...

We'll need to change TableMetricProducer to take the QueueWriter as a constructor parameter.

Code Block
java
java
public class TableMetricProducer implements MetricProducer
{
  private final QueueWriter queueWriter;

  /**
   * The normal constructor.

...


   *
   */
  public TableMetricProducer(. . .)
  {
    this(QueueWriterImpl.getInstance(), . . .);
  }

  /**
   * Constructor used for testing.
   *
   */
  TableMetricProducer(QueueWriter queueWriter, . . .)
  {
    queueWriter = queueWriter;
    . . . 
  }

  public void execute() 
  {
    int rowCount = . . .;
    Metric metric = new Metric("app/clients", System.currentTimeMillis(), rowCount);

   queueWriter.sendMetric(metric);
  }
}

This still isn't ideal, as we still have an explicit linkage between TableMetricProducer and QueueWriterImpl.

...

For comparison, lets see what the Tapestry IoC implementation would look like:

Code Block
java
java
public class MonitorModule
{
  public static void bind(ServiceBinder binder)
  {
    binder.bind(QueueWriter.class, QueueWriterImpl.class);
    binder.bind(MetricScheduler.class, MetricSchedulerImpl.class);
  }

  public void contributeMetricScheduler(Configuration<MetricProducer> configuration, QueueWriter queueWriter, . . .)
  {
    configuration.add(new TableMetricProducer(queueWriter, . . .))
  }
}

Again, we've omitted a few details related to the database the TableMetricProducer will point at (in fact, Tapestry IoC provides a lot of support for configuration of this type as well, which is yet another concern).

...

So we've come to accept that the death concern is better handled outside of our own code. The use of Inversion of Control is simply the flip side of that: the life cycle and construction concerns are also better handled by an outside authority as well: the IoC container. These concerns govern when a service is realized and how its dependencies and configuration are injected. As with the garbage collector, ceding these chores to the container results in less code and fewer bugs, and lets you concentrate on the things that should matter to you: your business logic, your application – and not a whole bunch of boilerplate plumbing!

 

Scrollbar