Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Fix cwiki-test attachment urls

...

Scrollbar

...

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

...

Inversion of Control builds upon those ideas. The goal is to make coding code more robust (that is, with fewer errors), more reusable and to make code much easier to test.

Most developers are Prior to IoC approaches, most developers were used to a more monolithic design, they have with a few core objects and a main() method somewhere that starts the ball rolling. main() instantiates the first couple of classes, and those classes end up instantiating and using all the other classes in the system.

...

Inversion of Control is just a more general application of this approach. The container is ultimately responsible for instantiating and configuring the objects you tell it about, and running their entire life cycle of those objects.

Building web Web applications are more complicated to write than monolithic applications, largely because of multithreading. Your code will be servicing many different users simultaneously across many different threads. This tends to complicate the code you write, since some fundamental aspects of object oriented development get called into question: in particular, the use of internal state, (values stored inside instance variables), since in a multi-threaded multithreaded environment, that's no longer the safe place it is in traditional development. Shared objects plus internal state plus multiple threads equals an broken, unpredictable application.

...

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.

...

Tapestry IoC also has support for other configuration that may be provided to services when they are realized.

Dependency InjectionInjection

Main Article: Injection

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

Inversion of Control refers to 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.

...

In any case, injection "just happens". Tapestry finds the constructor of your class and analyzes the parameters to determine what to pass in. In some cases, it uses just the parameter type to find a match, in other cases, annotations on the parameters may also be used. It also scans through the fields of your service implementation class to identify which should have injected values written into them.

Why can't I just use new?

I've had this question asked me many a timeThat's a common question. All these new concepts seem alien . All that XML (in the Spring or HiveMind IoC containers; Tapestry IoC uses no XML) is a burdenat first. What's wrong with new?

The problem with new is that it rigidly connects one implementation to another implementation. Let's follow a progression that reflects how a lot of projects get written. It will show that in the real world, new is not as simple as it first seems.

This example is built around some work I've done recently involving real-world work that involves a Java Messaging Service queue, part of an application performance monitoring subsystem for a large application. Code inside each server collects performance data of various types and sends it, via a shared JMS queue, to a central server for collection and reporting.

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
languagejava

public class TableMetricProducer implements MetricProducer
{
  . . . 

  public void execute() 
  {

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

    new QueueWriter().sendMetric(metric);
  }
}

IWe've elided 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
languagejava

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);
  }

...

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
languagejava

  private final QueueWriter queueWriter = QueueWriter.getInstance();

... and inside class QueueWriter:

Code Block
java
languagejava

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
languagejava

  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
languagejava

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
languagejava

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, Iwe've elided out 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).

...

What we are saying is that IoC techniques and discipline will lead to applications that are:

  • More testable – smaller, simpler classes; coding to interfaces allows use of mock implementations
  • More robust – smaller, simpler classes; use of final variables; thread safety baked in
  • More scalable – thread safety baked in
  • Easier to maintain – less code, simpler classes
  • Easier to extend – new features are often additions (new services, new contributions) rather than changes to existing classes

What we're saying is that an IoC container allows you to work faster and smarter.

Many of these traits work together; for example, a more testable application is inherently more robust. Having a test suite makes it easier to maintain and extend your code, because its much easier to see if new features break existing ones. Simpler code plus tests also lowers the cost of entry for new developers coming on board, which allows for more developers to work efficiently on the same code base. The clean separation between interface and implementation also allows multiple developers to work on different aspects of the same code base with a lowered risk of interference and conflict.

...

Over a decade ago, when Java first came on the scene, it was the first mainstream language to support garbage collection. This was very controversial: the garbage collector was seen as unnecessary, and a waste of resources. Among C and C++ developers, the attitude was "Why do I need a garbage collector? If I call malloc() I can call free()."

I don't know about you, but I don't think I could ever But now, most developers would never want to go back to a non-garbage collected environment. Having the GC around makes it much easier to code in a way I we find natural: many small related objects working together. It turns out that knowing when to call free() is more difficult than it sounds. The Objective-C language tried to solve this with retain counts on objects and that still lead to memory leaks when it was applied to object graphs rather than object trees.

...

The point is, the life cycle of objects turns out to be far more complicated than it looks at first glance. We've come to accept that our own applications lack the ability to police their objects as they are no longer needed (they literally lack the ability to determine when an object is no longer needed) and the garbage collector, a kind of higher authority, takes over that job very effectively. The end result? Less code and fewer bugs. And a careful study shows that the Java memory allocator and garbage collector (the two are quite intimately tied together) is actually more efficient that than malloc() and free().

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

...