Virtual Business Systems

Spring versioning component

Contents

Introduction

Dependencies

Spring configuration

Defining versions

Processing

Performance

Links

Download Spring

Download component

Download source

Javadoc

Performance Test Source

Home

Contact Me

Introduction

This simple annotation based Spring component enables different versions of any spring injected bean to co-exist for a set of application defined version identifiers.

The component delegates calls on any versioned bean to the implementation appropriate to the active version within a processing thread.

This component is useful when an application should exhibit different behaviours with different types of user, or when multiple sequential system releases must co-exist.

Java Annotations are used to flag relevant bean classes as either versioned or version dependent. The annotation for a versioned class defines the list of classes that implement each version of the bean:

@VersionedClass({HelloV1and2.class, HelloV3.class})
public class Hello { ...

and the annotation on each version dependent class defines the version or versions that the class implements:

@VersionDependentClass({"1.0", "2.0"})
public class HelloV1and2 { ...

@VersionDependentClass({"3.0"})
public class HelloV3 { ...

Within application code, the active version is set and removed as required:

VersionHolder.getInstance().setActiveVersion("1.0");

VersionHolder.getInstance().removeActiveVersion();

While an active version is present, any methods executed on beans of a type annotated by @VersionedClass will be delegated to the appropriate implementation. In the example above, a version of "1.0" or "2.0" will result in methods being executed on the bean represented by HelloV1and2 and a version of "3.0" will cause methods to be executed on HelloV3. If no active version had been set, or it was set to some other value, no delegation would occur and the method would be executed on Hello.

A version dependent class can be associated with one or more versioned classes, but must be consistent with each of it's associated versioned classes - see Method matching below.

Only methods on beans of a type annotated by VersionedClass will be intercepted and methods will only be delegated while an active version is present.

A list of valid versions can be specified (see Defining and validating versions below). If a list is specified, active versions will be validated against it and delegation will only occur for those versions specified in the list.

Limitations

Versioning does not support prototype beans and hence inner beans. See Processing for more details.

Component Dependenciesreturn to top

This component was developed under Java 1.6, Spring Framework 2.5.6. In addition to dist/spring.jar, the component requires the following jars to be on the classpath:
  • lib/asm/asm-util-2.2.3.jar
  • lib/asm/asm-2.2.3.jar
  • lib/asm/asm-commons-2.2.3.jar
  • lib/aspectj/aspectjweaver.jar
  • lib/aspectj/aspectjrt.jar
  • liblib/cglib-nodep-2.1_3.jar
  • lib/jakarta-commons/commons-logging.jar
  • lib/log4j/log4j-1.2.15.jar (or some other logging implementation)

Spring configurationreturn to top

A skeletal application context that supports versioning can be found here.

As the component relies on AspectJ annotations, ensure that the following declaration appears in the context:

<aop:aspectj-autoproxy/>

The versioning component is activated by declaring a bean of type VersioningAspect within an application context:

<bean class="com.vbs.spring.versioning.VersioningAspect"/>

Only one instance of a VersioningAspect should be declared (a warning will be logged if multiple instances are found).

Version dependent beans are declared within the application context like any other bean:

<!-- Versioned bean -->
<bean id="hello" class="com.vbs.spring.versioning.test.Hello" />

<!-- Version dependent implementations -->
<bean class="com.vbs.spring.versioning.test.HelloVland2" />
<bean class="com.vbs.spring.versioning.test.HelloV3" />

The version dependent beans need only be named if multiple versioned beans exist of the same type. If this is the case, then the name of the matching dependent bean must be the same as that of it's versioned bean, suffixed by the version or versions that it implements:

<bean id="multiA" class="com.vbs.spring.versioning.test.Hello" />
<bean id="multiA1.0_2.0" class="com.vbs.spring.versioning.test.HelloV1and2" />
<bean id="multiA3.0" class="com.vbs.spring.versioning.test.HelloV3" />

<bean id="multiB" class="com.vbs.spring.versioning.test.Hello" />
<bean id="multiB1.0_2.0" class="com.vbs.spring.versioning.test.HelloV1and2" />
<bean id="multiB3.0" class="com.vbs.spring.versioning.test.HelloV3" />

Note that multiple versions are separated by an underscore.

Defining and validating versionsreturn to top

A version can be any string containing digits, letters plus optional periods. By default, no other validation is performed on the versions defined by the VersionDependentClass annotation or when VersionHolder.getInstance().setActiveVersion is invoked.

To specify a list of "active" versions, add the following to the application context:

<bean class="com.vbs.spring.versioning.VersionHolder"
 factory-method="getInstance">
 <property name="activeVersions">
  <list>
   <value>1.0</value>
   <value>3.0</value>
   ...
  </list>
 </property>
</bean>

Once configured, if VersionHolder.getInstance().setActiveVersion is invoked with an unknown version, an exception will be thrown.

Processing return to top

Any bean of a type that is annotated as a @VersionedClass must satisfy the following conditions for versioning to be implemented on it:
  1. the bean must not be a prototype or an inner bean
  2. the list of classes defined by the annotation must not be empty
  3. there must be a bean present for at least one of the version dependent classes defined by the annotation
  4. if active versions have been configured, there must be at least one "active" version dependent bean
  5. a given version can be implemented by one and only one version dependent class defined by the annotation
An info level log will be output for any bean that fails conditions 1 to 4 and the bean will be ignored for versioning purposes. Failure of condition 5 results in an exception being thrown at context load time.
Method matching
A version dependent class can be a super set or sub set of it's associated versioned classes, but must be consistent with them. I.E, any matching methods (non-static, non-private methods with the same name and argument types) must have the same return type and modifiers.

By default, each version dependent class defined by the @VersionedClass annotation must implement all the non-static, non-private methods on the versioned class. However if a specific version dependent class is a super set, this can be overriden by specifying a value for the optional fullyImplemented annotation:

@VersionDependentClass(value={"1.0", "2.0"},fullyImplemented=false)
public class HelloV1and2 { ...

fullyImplemented defaults to true and should be overriden with care as some method calls on a given bean may be delegated while others on the same bean may not be.

Regardless of the setting of this attribute, methods found on version dependent implementations must match exactly in name, argument types, return type and modifiers. Any found method that does not have an exact match will result in an exception being thrown at context load time.

The component will intercept beans defined by their interface or interfaces (these will be proxied) or beans with no interfaces (enhanced via CGLIb).

Performancereturn to top

Download sources for performance test classes from here.

One third of the threads within the tests ran non-intercepted method calls on an injected bean; one third ran intercepted but non-delegated calls and one third delegated the method calls.
The tests were run on both proxied and CGLib enhanced beans.

6 concurrent threads over 100,000 iterations
Non intercepted: Average call time < 1 millisecond

Proxies:
Interception overhead 15 milliseconds
Delegation overhead 15 milliseconds, total overhead 31 milliseconds

Enhanced:
Interception overhead 12 milliseconds
Delegation overhead 15 milliseconds, total overhead 28 milliseconds

30 concurrent threads over 100,000 iterations
Non intercepted: Average call time 4 milliseconds

Proxies:
Interception overhead 388 milliseconds
Delegation overhead 378 milliseconds, total overhead 767 milliseconds

Enhanced:
Interception overhead 348 milliseconds
Delegation overhead 375 milliseconds, total overhead 723 milliseconds

  Top | Home | Contact Me | Download CV