JBoss.orgCommunity Documentation

Chapter 7. Architecture

7.1. Data Structures Within The Cache
7.2. SPI Interfaces
7.3. Method Invocations On Nodes
7.3.1. Interceptors
7.3.2. Commands and Visitors
7.3.3. InvocationContexts
7.4. Managers For Subsystems
7.4.1. RpcManager
7.4.2. BuddyManager
7.4.3. CacheLoaderManager
7.5. Marshalling And Wire Formats
7.5.1. The Marshaller Interface
7.5.2. VersionAwareMarshaller
7.6. Class Loading and Regions

A Cache consists of a collection of Node instances, organised in a tree structure. Each Node contains a Map which holds the data objects to be cached. It is important to note that the structure is a mathematical tree, and not a graph; each Node has one and only one parent, and the root node is denoted by the constant fully qualified name, Fqn.ROOT.

In the diagram above, each box represents a JVM. You see 2 caches in separate JVMs, replicating data to each other.

Any modifications (see API chapter) in one cache instance will be replicated to the other cache. Naturally, you can have more than 2 caches in a cluster. Depending on the transactional settings, this replication will occur either after each modification or at the end of a transaction, at commit time. When a new cache is created, it can optionally acquire the contents from one of the existing caches on startup.

In addition to Cache and Node interfaces, JBoss Cache exposes more powerful CacheSPI and NodeSPI interfaces, which offer more control over the internals of JBoss Cache. These interfaces are not intended for general use, but are designed for people who wish to extend and enhance JBoss Cache, or write custom Interceptor or CacheLoader instances.

The CacheSPI interface cannot be created, but is injected into Interceptor and CacheLoader implementations by the setCache(CacheSPI cache) methods on these interfaces. CacheSPI extends Cache so all the functionality of the basic API is also available.

Similarly, a NodeSPI interface cannot be created. Instead, one is obtained by performing operations on CacheSPI, obtained as above. For example, Cache.getRoot() : Node is overridden as CacheSPI.getRoot() : NodeSPI.

It is important to note that directly casting a Cache or Node to its SPI counterpart is not recommended and is bad practice, since the inheritace of interfaces it is not a contract that is guaranteed to be upheld moving forward. The exposed public APIs, on the other hand, is guaranteed to be upheld.

Since the cache is essentially a collection of nodes, aspects such as clustering, persistence, eviction, etc. need to be applied to these nodes when operations are invoked on the cache as a whole or on individual nodes. To achieve this in a clean, modular and extensible manner, an interceptor chain is used. The chain is built up of a series of interceptors, each one adding an aspect or particular functionality. The chain is built when the cache is created, based on the configuration used.

It is important to note that the NodeSPI offers some methods (such as the xxxDirect() method family) that operate on a node directly without passing through the interceptor stack. Plugin authors should note that using such methods will affect the aspects of the cache that may need to be applied, such as locking, replication, etc. To put it simply, don't use such methods unless you really know what you're doing!

JBoss Cache essentially is a core data structure - an implementation of DataContainer - and aspects and features are implemented using interceptors in front of this data structure. A CommandInterceptor is an abstract class, interceptor implementations extend this.

CommandInterceptor implements the Visitor interface so it is able to alter commands in a strongly typed manner as the command makes its way to the data structure. More on visitors and commands in the next section.

Interceptor implementations are chained together in the InterceptorChain class, which dispatches a command across the chain of interceptors. A special interceptor, the CallInterceptor, always sits at the end of this chain to invoke the command being passed up the chain by calling the command's process() method.

JBoss Cache ships with several interceptors, representing different behavioral aspects, some of which are:

The interceptor chain configured for your cache instance can be obtained and inspected by calling CacheSPI.getInterceptorChain(), which returns an ordered List of interceptors in the order in which they would be encountered by a command.

Some aspects and functionality is shared by more than a single interceptor. Some of these have been encapsulated into managers, for use by various interceptors, and are made available by the CacheSPI interface.

Early versions of JBoss Cache simply wrote cached data to the network by writing to an ObjectOutputStream during replication. Over various releases in the JBoss Cache 1.x.x series this approach was gradually deprecated in favor of a more mature marshalling framework. In the JBoss Cache 2.x.x series, this is the only officially supported and recommended mechanism for writing objects to datastreams.

When used to cluster state of application servers, applications deployed in the application tend to put instances of objects specific to their application in the cache (or in an HttpSession object) which would require replication. It is common for application servers to assign separate ClassLoader instances to each application deployed, but have JBoss Cache libraries referenced by the application server's ClassLoader.

To enable us to successfully marshall and unmarshall objects from such class loaders, we use a concept called regions. A region is a portion of the cache which share a common class loader (a region also has other uses - see eviction policies).

A region is created by using the Cache.getRegion(Fqn fqn, boolean createIfNotExists) method, and returns an implementation of the Region interface. Once a region is obtained, a class loader for the region can be set or unset, and the region can be activated/deactivated. By default, regions are active unless the InactiveOnStartup configuration attribute is set to true.