Previous Up Next

Chapter 8  The Spring Framework

8.1  Introduction

The Spring framework (www.springsource.org) is a popular Java library for configuration-driven object creation. In particular, Spring-based applications can create Java objects of arbitrary types from configuration information in an XML file. In this chapter, I explore how Spring might be different if it obtained configuration information from a Config4* file instead of from an XML file.

The purpose of this exploration is not to advocate that Spring should be retrofitted with support for Config4*. After all, there is no need to fix something that is not broken. Rather, the purpose of this chapter is to show the suitability of Config4* for future projects that might need a Spring-like capability.

8.2  Terminology

Java is an Indonesian island that is famous for its export of coffee beans. This has resulted in many Americans using java as a slang term for coffee. This, in turn, has resulted in the Java programming language using coffee-inspired terminology for programming concepts. Of particular note, the term bean (as in a coffee bean) is often used to denote a class (or object) whose public API adheres to several conventions, including the following:

By the way, the combination of a private field and its public get and set operations is called a property.

8.3  Reducing the Verbosity of Spring Beans

Now that I have explained the terms bean and property, you might be able to understand the extract of a Spring XML file shown in Figure 8.1.

Figure 8.1: Example of Spring beans
<bean id = "employee1" class = "com.foo.bar.Employee">
    <property name = "firstName" value = "John"/>
    <property name = "lastName" value = "Smith"/>
    <property name = "age" value = "24"/>
    <property name = "manager" ref = "manager"/>
</bean>

<bean id = "owner" class = "com.foo.bar.Employee">
    <property name = "firstName" value = "Jane"/>
    <property name = "lastName" value = "Doe"/>
    <property name = "age" value = "42"/>
</bean>

Each bean element contains configuration information that can be used to create and initialise a Java object. The class attribute specifies the type of object to be created. Typically, Spring will create the object by using Java’s reflection capabilities to invoke the default constructor of the specified class.1 Then Spring processes each of the property elements nested inside the bean element. For each property, Spring uses reflection to invoke a set<Name>() operation on the newly-created bean. When doing this, Spring uses reflection to determine the type of the parameter passed to the set<Name>() operation so it can convert the stringified value obtained from the XML file to that appropriate type.

Each bean has an id attribute that is required to have a unique value. The manager property in the employee1 bean does not have a value attribute. Instead, it has a ref attribute, the value of which specifies the unique id of another bean. Thus, when Spring is creating the employee1 bean, it (recursively) creates the owner bean too.

We can transform the XML syntax in Figure 8.1 to Config4* syntax in a straightforward manner. Each XML element becomes a correspondingly named scope in the Config4* file. If there can be multiple occurrences of the XML element, then the "uid-" prefix is used on the name of the corresponding Config4* scope. Thus, the bean and property elements become uid-bean and uid-property scopes. Each XML attribute becomes a variable in the Config4* file. The result of this transformation is shown in Figure 8.2.

Figure 8.2: Simple representation of beans in Config4* syntax
uid-bean {
    id = "employee1"; class = "com.foo.bar.Employee";
    uid-property { name = firstName; value = "John"; }
    uid-property { name = lastName; value = "Smith"; }
    uid-property { name = age; value = "24"; }
    uid-property { name = manager; ref = "owner"; }
}

uid-bean {
    id = "owner"; class = "com.foo.bar.Employee";
    uid-property { name = firstName; value = "Jane"; }
    uid-property { name = lastName; value = "Doe"; }
    uid-property { name = age; value = "42"; }
}

Unfortunately, this straightforward transformation has resulted in a Config4* file that is more verbose than the original XML file. However, there is room for some improvements as I now discuss.

In Section 7.3, I turned an uid-camera scope that contained an id variable with a unique value into a scope with a name of the form camera.id. The same technique can be applied to Figure 8.2. In fact, the technique can be applied twice. First, we can replace the uid-bean scope and its id variable with a scope that has a name of the form bean.id. Second, we can replace the uid-property scope and its name variable with a scope that has a name of the form property.name. These changes result in the configuration file shown in Figure 8.3.

Figure 8.3: Enhanced representation of beans in Config4* syntax
bean.employee1 {
    class = "com.foo.bar.Employee";
    property.firstName.value = "John";
    property.lastName.value = "Smith";
    property.age.value = "24";
    property.manager.ref = "owner";
}

bean.owner {
    class = "com.foo.bar.Employee";
    property.firstName.value = "Jane";
    property.lastName.value = "Doe";
    property.age.value = "42";
}

This revised Config4* file is more concise than the original XML file. Of course, the word "property" is written repeatedly in each bean, so that invites the possibility of writing the beans with an explicitly-opened property scope to save a few more keystrokes, as shown in Figure 8.4.

Figure 8.4: Enhanced representation of beans in Config4* syntax
bean.employee1 {
    class = "com.foo.bar.Employee";
    property {
        firstName.value = "John";
        lastName.value = "Smith";
        age.value = "24";
        manager.ref = "owner";
    }
}

bean.owner {
    class = "com.foo.bar.Employee";
    property {
        firstName.value = "Jane";
        lastName.value = "Doe";
        age.value = "42";
    }
}

In summary, a straightforward translation of XML syntax into Config4* syntax can result in a more verbose file. However, with some simple tweaking, it is possible to produce a Config4* file that is more concise than its XML counterpart.

8.4  The Benefits of @include

Unfortunately, XML does not provide a mechanism for one XML file to include the contents of another. Because of this, the designers of Spring had to implement their own mechanism. The syntax is illustrated below:

<import resource="another-file.xml"/>

If Spring were to be redesigned to use Config4* instead of XML, then the ability to include another file would be obtained without any developer effort via the @include statement.

8.5  The Benefits of @copyFrom

Sometimes, a Spring configuration file contains several bean element in which most properties have identical values. In such a case, it can be useful to reuse some of the details of one bean when defining the other beans. Spring uses the term bean inheritance to refer to this form of reuse, and an example of it is shown in Figure 8.5.

Figure 8.5: Example of Spring bean inheritance
<bean id = "widget1" class = "com.foo.bar.Widget">
    <property name = "t" value = "..."/>
    <property name = "u" value = "..."/>
    <property name = "v" value = "..."/>
    <property name = "w" value = "..."/>
    <property name = "x" value = "..."/>
    <property name = "y" value = "..."/>
</bean>

<bean id = "widget2" parent="widget1">
    <property name = "v" value = "..."/>
    <property name = "z" value = "..."/>
</bean>

The widget1 bean defines properties t, u, v, w, x and y. The widget2 bean uses the parent attribute to specify that it will inherit (that is, reuse) some of the details from the widget1 bean. In this case, widget2 inherits the class attribute plus most of the properties. However, widget2 redefines the v property and also defines an additional property: z.

If Spring were to be redesigned to use Config4* instead of XML, then the semantics of bean inheritance would be obtained without any developer effort via the @copyFrom statement, as you can see in Figure 8.6.

Figure 8.6: Configt4* equivalent of Spring’s bean inheritance
bean.widget1 {
    class = "com.foo.bar.Widget";
    property.t.value = "...";
    property.u.value = "...";
    property.v.value = "...";
    property.w.value = "...";
    property.x.value = "...";
    property.y.value = "...";
}

bean.widget2 {
    @copyFrom "bean.widget1";
    property.v.value = "...";
    property.z.value = "...";
}

8.6  The Benefits of Pre-set Variables

Conceptually, the contents of a Spring XML file can be split into two types of configuration: static and runtime.

Static configuration.
The values of id and class attributes, and the values of most properties are likely to remain static for the duration of the project or change only rarely.
Runtime configuration.
A small number of properties—with names like host, port and logDir—are likely to change for each runtime environment in which you deploy the application.

In a large, Spring-based application, the Spring XML file might contain thousands of lines of static configuration and only a few lines of runtime configuration. With such an application, it is undesirable to tell administrators, “Here is a 2000-line Spring XML file; you need to be concerned with only fives lines in it: the logDir property on line 42, the host property on line 837, …” From a usability perspective, it would be better to tell administrators to modify a 5-line Java properties file containing only runtime configuration variables, and arrange for the application to (somehow) merge the contents of that properties file with the 2000-line Spring XML file.

Spring provides a mechanism to merge the contents of a properties file with a Spring XML file. However, before explaining the mechanism, I need to provide some background information on Spring.

Spring creates Java objects from bean information in an XML file in a multi-step process, a slightly simplified version of which is as follows:

  1. Spring parses the XML file, and stores the information in an internal format. This internal format is called bean configuration metadata, or metadata for short.
  2. Spring iterates over metadata to find beans whose class attribute indicate they implement the BeanFactoryPostProcessor interface (which is defined by the Spring library). For each such bean, Spring instantiates the bean and invokes two operations (defined in the BeanFactoryPostProcessor interface) on it. The invocation of these operations gives the bean the opportunity to modify metadata.
  3. Spring is now ready to instantiate the “ordinary” beans defined by the bean configuration metadata.

Included in the Spring library is the PropertyPlaceholderConfigurer class, which implements the BeanFactoryPostProcessor interface. This class defines a location property that specifies the location of a Java properties file. When a PropertyPlaceholderConfigurer bean is instantiated (in step 2 of the above algorithm), it iterates over the metadata, and replaces occurrences of "${property.name}" with the value of the named property found in the properties file. Figure 8.7 illustrates use of a PropertyPlaceholderConfigurer bean.

Figure 8.7: Example use of the PropertyPlaceholderConfigurer bean
<bean class = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name = "location" value = "/path/to/file.properties"/>
</bean>

<bean id = "widget1" class = "com.foo.bar.Widget">
    <property name = "logDir" value = "${log.dir}"/>
    <property name = "logLevel" value = "${log.level}"/>
</bean>

<bean id = "tcpServer" class = "com.foo.bar.TcpServer">
    <property name = "host" value = "${tcp.host}"/>
    <property name = "port" value = "${tcp.port}"/>
</bean>

Obviously, the Spring developers had to write code to implement the PropertyPlaceholderConfigurer class. If Spring were to be redesigned to use Config4* instead of XML, then there would be no need to implement the PropertyPlaceholderConfigurer class. This is because Config4* provides several ways to merge runtime configuration with static configuration, as you can see in Figure 8.8.

Figure 8.8: Merging runtime and static configuration with Config4*
@include getenv("FOO_CONFIG", "") if exists;

#--------
# Default values for runtime configuration
#--------
log.dir   ?= getenv("FOO_HOME") + "/log";
log.level ?= "2";
tcp.host  ?= exec("hostname");
tcp.port  ?= "8020";

bean.widget1 {
    class = "com.foo.bar.Widget";
    property.logDir.value   = .log.dir;
    property.logLevel.value = .log.level;
}

bean.tcpServer {
    class = "com.foo.bar.TcpServer";
    property.host.value = .tcp.host;
    property.port.value = .tcp.port;
}

The static configuration file can use the conditional assignment operator ("?=") to provide default values for runtime configuration variables. These variables can then be used to specify values for bean properties. The default values of runtime configuration variables can be overridden in two ways.

One way, which is illustrated in Figure 8.8, is to use an @include statement to access information in a runtime configuration file.

The other way is use preset configuration variables (which is discussed in the Overview of the Config4* API chapter of the Config4* Getting Started Guide). In essence, during initialisation, the application iterates over command-line options of the form "-set name value", and invokes cfg.insertString(name, value) for each such option. Doing this, “presets” those variables in the Configuration object. Afterwards, the application invokes cfg.parse("...") to parse a configuration file.

8.7  Summary

In this chapter, I have explored how the Spring framework might have turned out differently if Config4* had been available when Spring was first being developed. This exploration identified two main benefits:

  1. A Config4* syntax for defining Spring beans would have been more concise than the corresponding XML syntax. This conciseness would have benefited users because it would have made it easier to write and maintain bean definitions.
  2. The designers of Spring had to write code to implement several significant pieces of functionality because XML parsers did not provide such functionality. Because Config4* does provide such functionality, the developers of Spring would have had to write less code if Config4* had been available to them.

I am not advocating that the Spring framework library be retrofitted with support for Config4*. There are probably tens of millions of lines of existing XML-based Spring configuration files in use across thousands of organisations. I do not think the potential improvements that would arise from the use of Config4* would justify the effort required for those organizations to migrate their existing projects to use Config4* syntax.

Rather, the purpose of chapter has been to illustrate that Config4* is a suitable alternative to XML for future projects that have complex configuration requirements. The next time you are about to start work on a new XML-based project, it might be worthwhile to stop and explore the question, “Might it be better to use Config4* instead of XML?” You might discover that using Config4* will reduce the complexity of implementing the project and improve its user-friendliness.


1
Spring has the ability to create an object by invoking a non-default constructor or by invoking a factory method. However, a discussion of those capabilities is outside the scope of this chapter.

Previous Up Next