It was not long ago that the programming landscape was dominated with languages such as C, with its public structures and global variables. Before we fully understood separation of concerns, the idea that an application is more maintainable when divided into independent parts with exclusive purposes, it was not uncommon to see "settings in the sky" that could be accessed by all and modified on a whim.
Object-oriented programming techniques, with their encapsulation and information hiding, were supposed to change all that. The Java language went so far as to prevent "bare" variables and methods, requiring every piece of data to be part of some class, thinking that this is all it took to prevent a bad practice. But it wasn't enough. Csar (pronounced /zɑːr/), the Concern Separation Aspect Registrar, is meant to help truly separate concerns in a program by providing a concern registration that compartmentalizes "global" variables by thread group while still providing transparent lookup. Csar can be found at
The "Static Problem"
We supposedly learned long ago the drawbacks of publicly mutable global data. Why then in Java do we have the static methods
Locale.setDefault(Locale newLocale), with which any piece of code can change the locale for the entire JVM? (What if one wanted to simultaneously serve two users, one in France and the other in China?) If one wants to install a network authentication strategy, why must one call
Authenticator.setDefault(Authenticator a) which specifies how authentication will be handled for all Internet connections—even those created from other threads or inside third party libraries? (Why should a thread getting the latest currency rates use the same authenticator as a thread transferring money between bank accounts?)
Why when creating a logger in SLF4J must one use the global
LoggerFactory.getLogger(String name) mechanism? (What if each bank account access should be logged in a file specific to the logged-in user, while the currency update code should be logged to a general application log file?) The difficulty is described in Should Logger members of a class be declared as static? and When Static References to Log objects can be used, the latter of which describes it as the "static problem":
The real root cause of this problem is that SHARED data (static members on a class) is being shared across supposedly independent "applications". If no classes are "shared" between applications then the problem does not exist. However it appears that (to my personal frustration) container vendors continue to encourage the use of shared classes, and application developers continue to use it.
The "static problem" is no other than the "global variable" problem resurfacing in an object-oriented age. It seems like every new framework or subsystem has its global registry in which the developer performs some configuration that affects the entire JVM. If there is only one "application" running on the JVM, all is fine; but in today's world in many applications or context could be running on the same JVM, it become difficult to compartmentalize these global registries for contexts that need independent configurations.
The current vogue for addressing this problem is a container, an API that provides another layer of indirection when accessing system resources. Because each application accesses its configuration exclusively via the container API, a container thus isolates the various embedded applications, thereby allowing potentially a different configuration for each. There are IoC containers, servlet containers, and EJB containers, just to name a few. Containers play an important role, but their use in solving the "static problem" is diminished for two reasons:
- Not every piece of code has access to or even knowledge of the container.
- Many general-purpose libraries need to function across containers, or independent of containers.
The most visible examples of general-purpose libraries needing access to compartmentalized settings are those addressing "cross-cutting concerns" or aspects of a program, such as internationalization and logging. Logback for example, an SLF4J logging implementation, attempts to address the "relatively difficult problem of providing a separate logging environment for multiple applications running on the same web or EJB container" by using what it calls a "context selector". The problem is that a Logback context selector depends on JNDI environments provided through specially configured web containers. No mention is made of how one might easily have multiple logging configurations compartmentalized in an environment independent of a web server.
Csar is named "Concern Separation Aspect Registrar" because it provides access to some concern (usually cross-cutting) that may configured either globally or locally to some section of the program. Csar acts like a global service locator that provides flexible, transparent local configuration. Like a "czar" in American politics, Csar governs configuration and access to program concerns.
Csar takes advantage of the once-maligned but now almost forgotten
ThreadGroup class. Every thread runs inside some thread group. Csar can turn a thread group into a mini-container, an ever-present registry providing access to a program concern potentially with an independent configuration of others on the JVM. At any time a program, module, or library can access its configured concern by calling
Csar.getConcern(MyConcern.class). The returned instance of
MyConcern to the caller seems like a global variable, but other callers may receive other instances configured specifically for their separate thread group.
Rincl and Clogr
An unlimited number of configuration types can be coupled with Csar. Two libraries already exist to tackle the problem of compartmentalizing logging and internationalization.
- Facilitates internationalization by providing access to localization resources via Csar.
- Simplifies SLF4J logging while providing compartmentalized logging configurations.
As an example the methods
Rincl.getLocale(Locale.Category category) and
Rincl.setLocale(Locale.Category category, Locale locale) provide access to what appears to be a global locale setting. In reality the locale is restricted to the
ResourceI81nConcern for that thread group (which may or may not be shared with other threads and thread groups). This
ResourceI81nConcern also allows lookup of a hierarchy of resource definitions, potentially using independent locales for different callers. Without special Csar configuration, these methods will fall back to using the JVM default locale transparently.
You can configure Csar concerns manually, as explained below. Csar also provides a
ConcernProvider mechanism whereby any library that uses Csar can provide a list of concerns to be automatically available by default without any special configuration required by the program. Simply by including the
io.rincl:rincle-resourcebundle dependency, for example, will cause any call to
Csar.getConcern(ResourceI18nConcern.class) to return an implementation that looks up resources stored in resource bundles.
Adding Csar to an Application
To provide a concrete example of how you can add "local globals" to your own application using Csar, consider that you want to provide some sort of "environment" class to consumers. If you were to follow the lead of the frameworks mentioned above, you might create an
Environment class with static access methods, or even provide a singleton static
Environment.INSTANCE which any class on the JVM could access. Csar takes a less invasive approach, orchestrated by the
1. Include Csar Dependency
The first step is to include the appropriate Csar dependency. To support resource bundles, simply add
io.csar:csar:x.x.x to your Maven POM. Check the Maven Central Repository for the latest
2. Make a
Creating such an environment in Csar requires simply that your class implement
Environment class is acting as one of your program's concerns, which Csar is helping to keep separate from the others—and from other instances of the same concern. In this example we'll leverage the existing
java.util.Properties class to hold our environment properties.
3. Set the Default Concern
Setting one or more default concerns via
Csar.setDefaultConcerns(Concern... concerns) is a fallback mechanism; it is equivalent to setting a globally accessible instance of a concern. If your application does not use local concerns, any thread asking for a concern will receive the default registered concern of the requested type. Setting the default concern is optional if you configured local concerns for all threads.On the other hand, you application may only use a single default concern if that type of concern does not need to be configured locally for any threads.
4. Request a Concern
Any code at any time may ask Csar for a concern using
Csar.getConcern(Class<C> concernType), indicating the desired type of concern. Csar will automatically determine if a local concern has been provided to that thread group. If not, the default registered concern will be retrieved.
5. Use a Local Concern
If you wish to provide a specially configured concern locally to a thread, you can simply use
Csar.run(Concern concern, Runnable runnable) and provide the local concern along with a
java.lang.Runnable to execute. When any code in the returned
java.lang.Thread instance calls
Csar.getConcern(Class<C> concernType), it will be given the local concern instance, not the global default concern.
Csar is a small library with no other dependencies. Include it as a dependency and use
Csar.getConcern(Class<C> concernType) wherever you need configuration information, even without libraries that can be used within any container. You won't need to create a global singleton concern for the JVM. Rather your application can simply register a default concern instance or provide default concerns via Csar's concern provider mechanism. Either way your entire application will have access to the default concern you specified.
The moment your application needs various compartmentalized concern instances, ask Csar to run the relevant code in a different thread group, providing the concern instance you have specially configured for that local context. Code in that thread group, that before retrieved a "global" concern will now have access to the locally configured instance. The requesting code will be none the wiser—after all, when concerns are properly separated, exactly how concerns are configured should be of no concern to the calling code.