'Groovy application utilizing Spring IoC example' post illustration

Groovy application utilizing Spring IoC example

avatar

With the latest performance enhancements and great dynamic, functional and meta-programming features Groovy becomes a very good choice not only for scripting, but also for big and complex application building. Long term complex application development requires extensive unit testing utilization to allow moving forward without breaking existing features. Groovy plays nicely with powerful Spring framework, which can be used to make application easily extensible and unit testing friendly.

In this article we will use all the latest approaches to inversion of control using powerful Spring Framework, unit testing and build tools utilization to show how to make good architecture of non-trivial Groovy application.

Let's take a look at the Main class of the application:

Main.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Main class of the application that uses Spring framework
 * and JSR 330 annotations for dependency injection.
 */
class Main
{
    /**
     * Launches the application.
     * 
     * @param args command line arguments
     */
    public static void main(final String[] args) {

        // Use Spring annotation-based dependency injection
        def ctx = new AnnotationConfigApplicationContext()
       
        // Parse command line parameters and push
        // all the external configuration to application environment 
        def appProps = new MapPropertySource("appProps", [
                greetInfo: new GreetInfo(text: "Hello",
                        name: args.length > 0 ? args[0] : "world")
        ] as Map<String, Object>)

        // Push environment properties to Spring application context 
        ctx.getEnvironment().getPropertySources().addFirst(appProps);

        // Point Spring to IoC configuration of the application
        ctx.register(AppConfig.class);
        
        // Wire dependencies 
        ctx.refresh();

        // Register hook to close application context on JVM shutdown
        ctx.registerShutdownHook()

        // Launch the application
        def app = ctx.getBean(Application.class)

        app.run()
    }

}

Here we configure Spring to use XML-less annotation based dependency injection. The IoC configuration is located in the AppConfig class. After that we instantiate Application bean using Spring and pass control to it by calling run method.

The most interesting place in the code is environment initialization. The Environment is a relatively new feature, that was added to Spring 3.1. Environment allows configuring Spring application context using values from external sources. Here we push command line arguments into Environment so that they will be available in different parts of our application later. We use MapPropertySource for this purpose. It lets pass a dictionary of arbitrary Objects into Environment.

Lets take a look at our AppConfig now:

AppConfig.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * IoC configuration of application.
 */
@Configuration
@ComponentScan(basePackages = "com.sysgears.example",
        scopeResolver = Jsr330ScopeMetadataResolver.class)
class AppConfig {

    private @Inject Environment env
    
    public @Bean GreetInfo createGreetInfo() {
        env.getProperty("greetInfo", GreetInfo.class)
    }
}

Here we instruct Spring to scan com.sysgears.example to search for components and also to use JSR 330 annotations. JSR 330 is the standard of dependency injection for Java applications.

Notice, how we inject Environment into configuration using JSR330 @Inject annotation and retrieve objects from it. Note also, that Environment could be injected only to Spring @Configuration classes.

The source code for GreetInfo is trivial:

GreetInfo.groovy
1
2
3
4
5
6
7
8
9
10
11
/**
 * Container for the greeting text and person name.
 */
class GreetInfo {

    /** Greeting text */
    def text

    /** Name of the person to greet */
    def name
}

By default JSR 330 components have prototype scope, which means that new instance of the component will be created each time it will be wired. To mark the component as having prototype-scope one should add @Named-annotation. Below is the example of trivial Greeter prototype-scoped component that constructs greeting string. This component has dependency on the GreeterCounter that holds global counter of the number of times the Greeter was instantiated.

Greeter.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Greeter that greets a person with greeting text and output information
 * about instance no of the greeter class.
 */
@Named
class Greeter {

    /** Current greeter instance no */
    private final int instanceNo

    /**
     * Creates an instance of this greater and remembers greeter instance no
     */
    @Inject
    public Greeter(final GreetInstanceCounter greeterCounter) {
        instanceNo = greeterCounter.incrementCounter()
    }

    /**
     * Generates string for greeting {@code subject} with the given {@code text} 
     * 
     * @param text greet text
     * @param name whom to greet
     * 
     * @return greeting string 
     */
    public String greet(String text, String name) {
        "${text}, ${name}! (from greeter ${instanceNo})"
    }
}

The GreeterCounter should be instantiated one time for a whole application. For this to happen we should mark it with @javax.inject.Singleton annotation.

GreeterCounter.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * Counter of greeter instances
 */
@Named
@javax.inject.Singleton
class GreeterCounter {
    
    /** Greeter instance current count */
    private int instanceCount = 1

    /**
     * Returns Greeter current instance count and increments counter 
     * 
     * @return Greeter current instance count 
     */
    public int incrementCounter() {
        instanceCount++
    } 
}

So, @Named and @javax.inject.Singleton are the primary mechanisms to control how many instances of your classes will be created. However these mechanisms only work during wiring phase, but what if you want create class instances at runtime?

JSR 330 provides @javax.inject.Provider - a separate mechanism to create instances at runtime. Here is an example of using this mechanism:

MultiGreeter.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Class that uses multiple greeter instances to output several greetings.
 */
@Named
@javax.inject.Singleton
class MultiGreeter {

    /**
     * Object that contains greeting text and name passed through environment
     */
    private @Inject GreetInfo greetInfo

    /**
     * Greeter instance factory 
     */
    private @Inject Provider<Greeter> greeterProvider

    /**
     * Outputs greeting with 2 instances of greeter.
     * 
     * @return greeting text
     */
    public String greet() {
        def greeter1 = greeterProvider.get()
        def greeter2 = greeterProvider.get()
        
        greeter1.greet(greetInfo.text, greetInfo.name) + "\n" +
                greeter2.greet(greetInfo.text, greetInfo.name)
    }
}
In this code we are using our Greeter to output greeting text two times, using two greeter instances.

The instances of Greeter are created with injected @Inject Provider greeterProvider. Note, that this is enough to write only this line of code to get factory of Greeter's created for us automatically by IoC container at runtime.

This is pretty much all the logic of our pet application. Now, how to write unit tests for it? Let's look for the simple case - a unit test for the Greeter.

GreeterSpec.groovy
1
2
3
4
5
6
7
8
9
@ContextConfiguration(classes = AppConfig.class)
class GreeterSpec extends Specification {
    private @Inject Greeter greeter
    
    def 'check greeting'() {
        expect: 'should generate correct greet string'
        greeter.greet('Hello', 'Test') == 'Hello, Test! (from greeter 1)' 
    }
}

Unit tests are implemented using Spock framework. Greeter is pretty much independent and simple class. That's why the unit test for this class is simple as well.

In this code we point Spring to the IoC configuration of our application, using @ContextConfiguration(classes = AppConfig.class). Also we let Spring create the instance of Greeter class and wire its dependencies by using just @Inject annotated field.

The code is very neat and straightforward.

Let's handle more complex problem - a unit test for the class that depends on Environment:

MultiGreeterSpec.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@ContextConfiguration(classes = AppConfig.class, initializers = TestContextInitializer.class)
class MultiGreeterSpec extends Specification {
    private @Inject MultiGreeter multiGreeter

    public static class TestContextInitializer implements
            ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext ctx) {
            def appProps = new MapPropertySource("appProps", [
                    greetInfo: new GreetInfo(text: "TestHello", name: "TestWorld")
            ] as Map<String, Object>)

            ctx.getEnvironment().getPropertySources().addFirst(appProps);
        }
    }

    def 'check greeting generation'() {
        expect: 'should generate correct greeting'

        multiGreeter.greet().trim() == '''
TestHello, TestWorld! (from greeter 1)
TestHello, TestWorld! (from greeter 2)
'''.trim()
    }
}

Here in @ContextConfiguration we point Spring to context initializer - e.g. the code that will be executed right before wiring. We use context initializer to push test environment properties into application context. This is very similar to the code we had in Main.groovy to pass external properties into Spring context.

At the end of this post I would like to share the Gradle script to build and test this application and create executable JAR that can be launched on any computer that have Java VM installed.

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
apply plugin: 'maven'
apply plugin: 'groovy'
group = 'com.sysgears.example'
version = '1.0'

defaultTasks 'clean', 'build', 'test', 'uberJar'

def compatibilityVersion = 1.5
def springVersion = '3.2.2.RELEASE'
def mainClassName = 'com.sysgears.example.Main'
sourceCompatibility = compatibilityVersion
targetCompatibility = compatibilityVersion

repositories {
    mavenCentral()
}

dependencies {
     // Spring Framework Dependenices
     compile "org.springframework:spring-beans:${springVersion}"
     compile "org.springframework:spring-context:${springVersion}"
     compile "org.springframework:spring-core:${springVersion}"
     compile "org.springframework:spring-expression:${springVersion}"

     // JSR 330 annotation support
     compile "javax.inject:javax.inject:1"

     // Spring unit testing support
     testCompile "org.springframework:spring-test:${springVersion}"

     // Spock framework for unit testing
     testCompile("org.spockframework:spock-core:0.7-groovy-2.0") {
         exclude module: 'groovy-all'
     }
     testCompile("org.spockframework:spock-spring:0.7-groovy-2.0") {
         exclude module: 'groovy-all'
     }

     // Groovy
     groovy 'org.codehaus.groovy:groovy:2.1.2'
}

// Creates executable standalone JAR file of the application
task uberJar(type: Jar, dependsOn:[':compileJava',':compileGroovy']) {
    from files(sourceSets.main.output.classesDir)
    from files(sourceSets.main.output.resourcesDir)
    from(configurations.runtime.asFileTree.files.collect { zipTree(it) }) {
        exclude "META-INF/*.SF"
        exclude "META-INF/*.DSA"
        exclude "META-INF/*.RSA"
    }
    manifest {
        attributes 'Implementation-Title': 'Groovy app architecture example',
                'Implementation-Version': version,
                'Built-By': System.getProperty('user.name'),
                'Built-Date': new Date(),
                'Built-JDK': System.getProperty('java.version'),
                'Main-Class': mainClassName
    }

}

This script is pretty-much self-explaining, however you need to make some magic, in order all things to work as expected. I hope this will be improved in the future versions of Gradle and Spock framework.

The full source code of the project is available at: GitHub repository.

Thanks for reading,

If you're looking for a developer or considering starting a new project,
we are always ready to help!