Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

Boost is a framework for working with Java. This page describes how to use the IoC part of Boost called Spider with Groovy.

Spider has a very interface oriented view of the world. If you are into Interface oriented design (e.g. Ken Pugh's book) and use fine-grained interfaces, you will feel right at home. So first, let's create a Java interface:

Code Block
// Java
package foo;

public interface MeTwo {
    void reportStuff();
}

and a corresponding implementation:

Code Block
// Java
package foo;

public class DefaultMeTwo implements MeTwo {
    public void reportStuff() {
        System.out.println("Reporting stuff");
    }
}

We could have just as easily used Groovy. As an example, here's another interface (this time Groovy):

Code Block
package foo

interface IGetInjected {
    void doStuff()
}

And its Groovy implementation:

Code Block
package foo

class DefaultIGetInjected implements IGetInjected {
    void doStuff() {
        println("Doing stuff")
    }
}

Now, let's create a class that uses these implementations.

Code Block
package foo

import au.net.netstorm.boost.spider.api.entry.Go

@PackageScope
class PleaseDontGo implements Go {
    IGetInjected yippee
    MeTwo rippa

    void go(String[] args) {
        yippee.doStuff()
        rippa.reportStuff()
    }
}

The go() method is the Spider's answer to Java's main() method, i.e. it is the entry point for IoC managed webs of classes. Also, @PackageScope is an AST macro. The Spider does field injection based on package scoped fields. However, Groovy normally turns packages scoped fields into properties and we don't want that here. The AST macro tells the Groovy compiler to leave the fields in the PleaseDontGo class alone and not try to turn them into properties. We could have placed the annotation individually on fields if we preferred. Placing it at the class level makes it apply to all package scoped fields.

Now, let's bootstrap the IoC container so that we can run our application:

Code Block
package foo

import au.net.netstorm.boost.spider.ioc.BoostWeb
import au.net.netstorm.boost.spider.api.entry.SpiderMain

def main = new SpiderMain(BoostWeb)
main.main(PleaseDontGo)

Here, BoostWeb provides some nice defaults for us to use. Running this gives:

No Format
Doing stuff
Reporting stuff

We can also override the Web. For instance, we can supply our own web:

Code Block
package foo 

import au.net.netstorm.boost.spider.api.config.web.Web
import au.net.netstorm.boost.spider.api.config.wire.Wire

class TangledWeb implements Web {
    @PackageScope Wire wire
    void web() {
        wire.ref({ println 'Reporting stuff2' } as MeTwo).to(MeTwo)
    }
}

Which tells the IoC framework to use the supplied closure whenever it needs to inject a MeTwo implementation. Note again the use of the PackageScope AST macro to override Groovy's default property behavior. With this in place, our main script becomes:

Code Block
def main = new SpiderMain(BoostWeb, TangledWeb)
main.main(PleaseDontGo)

And the output will be:

No Format
Doing stuff
Reporting stuff2

You can also use Boost's testing facilities. An example (Foo and Person classes not shown) to give you a flavor of these kinds of tests:

Code Block
@PackageScope
class StringJoinerTest extends GroovyLifecycleTestCase implements HasFixtures, InjectableSubject, InjectableTest, LazyFields  {
    StringJoiner subject
    private Person pa, pb
    Foo fooA, fooB
    Foo foo1Mock, foo2Mock

    void fixtures() {
        pa = new Person(first:'f1', last:'l1')
        pb = new Person(first:'f2', last:'l2')
    }

    void testLookup() {
        expect.oneCall(foo1Mock, "FOO", "toUpperCase")
        expect.oneCall(foo2Mock, "BAR", "toUpperCase")
        assert subject.join(pa.first, pb.last) == "f1l2"
        assert subject.join(fooA.bar, fooB.bar) == "barbar"
        assert subject.joinUpper(foo1Mock, foo2Mock) == "FOOBAR"
    }
}

Here, fooA, fooB and subject will be auto created with default values, foo1Mock and foo2Mock will be auto created as mocks, pa and pb are set using the fixtures() method. GroovyLifecycleTestCase is a slight variation of LifecycleTestCase which is built-in to Boost. The built-in one is a little too opinionated about the methods Groovy adds under the covers to its classes. The result is very compact test code albeit looking a little magical until you get used to this style of testing.