In a nutshell, provides an efficient way to access bean-like properties of java objects. Unlike java bean properties, a chain of properties can be specified efficiently, allowing convenient access to properties of nested domain objects.
Runtime class-generation and caching can be used very easily to obviate the need for reflection, without losing the flexibility it provides.
Class Properties It also provides classes which manipulate objects at runtime by using strings to address conceptual variables (think bean properties) in a similar fashion to the java reflection mechanisms.
Class Property example Say we have the following classes:
public class Order {
...
public Customer getCustomer() { ... }
...
}
public class Customer {
...
public Address getAddress() { ... }
...
}
public class Address {
...
public String getLine1() { ... }
public String getLine2() { ... }
...
}
Now, we could do this to access line 1 of the address:
result = order.getCustomer().getAddress().getLine1();But what if there are nulls? What if we want to compare the first line of the address from two different orders? Whith properties this is really simple:
ClassProperty p = PropertyManager.getProperty(Order.class,"Customer:Address:Line1",true); result = p.getValue(order);The idea is simple enough - you use the string "Customer:Address:Line1" to specify a series of getXXX calls. Importantly, it the library handles nulls for you, returning null if any of the objects in the chain are null.
Note, this is not simply a wrapper around java reflection, since it includes non-reflective optimizations by making use of the cojen library to generate class files at runtime to acces these properties.
| The real benefit lies in being able to specify which properties you wish to access at runtime. You can even pass this ability on to the users of your library or application. Unlike reflection, class files are generated for the properties, and these files are cached - which means once you have specified a property once, further uses of the same property don't use reflection and are very fast. |
Property API vs Java Bean Properties. The property API models accessor/mutator method pairs as conceptual variables called properties. The approach used is similar to, but more flexible than java bean properties, and more performant than reflection if runtime class generation is used. Unlike java bean properties, a chain or path of properties can be specified (and more importantly, turned into bytecode using runtime generation) to traverse a complex tree of objects.
It is simple to create applications portable between different security environments - using runtime generation where allowed, and falling back to reflection where security is tighter.
Dynamic Class Management. The properties package also provides classes to handle the loading (and unloading/reloading) of classes at runtime. It provides a framework useful for dynamically loading runtime-generated classes, for example.
While the property API can happily ignore the dynamic loading framework, it can also make use of it to enable runtime class generation.
| Class Name | Description |
|---|---|
| ClassProperty | Conceptual variable related to a particular class. May be a static or member property. |
| SimpleClassProperty | ClassProperty implemented by a pair of (accessor/mutator) methods. |
| NestedClassProperty | ClassProperty implemented by a chain of SimpleClassPropertys. |
| Class Name | Description |
|---|---|
| DynamicClassManager | Manages the dynamic loading of classes. |
| Class Name | Description |
|---|---|
| CompiledProperty | Superclass of all dynamically generated ClassPropertys. Provides some useful methods for the generated inner classes. |
| PropertyManager | Manages ClassProperty instances. Can use DynamicClassManager, and generate ClassPropertys using cojen, or use reflection. |
| ClassPropertyKey | (class,property,isDynamicNull) tuple used to identify properties. Used in PropertyManager, and used to index the cache in DynamicClassManager. |
It is common for classes to have private variables accessable through accessor and mutator methods (usually called setXXX and getXXX), In general, these methods may not relate directly to any single member variable, and either method may be absent (in the case of read only, or write only properties). This concept is similar to the one modelled by Java Bean properties, but we wish to be a little more general and flexible. In particular, we do not restrict ourselves to a single property, but we specify a property of the returned object, and a property of that returned object, and so forth; in a chain of properties. We can also address static properties, not just instance properties.
A typical business data model is likely to consist largely of struct-like classes with property-style accessors and/or mutators. (This is after all the basis on which binding frameworks, and java beans are based). It is convenient to allow access to the properties of these objects via the conceptual "names" of such properties. Moreover, it is often desireable to be able to successively access properties of properties (of properties...and so forth) by "chaining" properties together.
The base case is handled by SimpleClassProperty which represents the first case described above; a single property relating to a single class. The second case is handled by NestedClassProperty.
Syntax
General format: P1:P2:...:Pn corresponds to target.getP1().getP2()...getPn()
Casting: (C1)P1:(C2)P2:...:Pn corresponds to ( (C2)( (C1)target.getP1() ).getP2() )...getPn()
If the dynamic null handling option is used (by passing true to the varies property-creating functions), a property's value is considered to be null if any of the objects in the chain are null - it is not required that all objects except the last are non-null.
When calling setValue() on a nested property, the default constructor is used to create any objects for null links in the chain.
DynamicClassManager is designed to help applications access classes at runtime. While at first glance, Java appears to support this, it has a few serious limitations. Most obvious is that it does not support unloading a class definition directly. This means that once you have loaded a class, you are (usually) stuck with it. An attempt to re-load it will return the already loaded class. The only way to avoid this is to discard the classloader with which the class was loaded. Obviously discarding an application classloader isn't feasable. We instead create a child classloader, specifically intended to load dynamic classes. We can then discard this classloader along with all the classes it loaded.
There are a number of points worth mentioning here.
First, if a non-dynamic class were to reference a dynamic one, this would cause havoc. The application classloader will load the "dynamic" class, which will then not be unloadable. For this reason, dynamic classes should be placed under a common path, outside of the application classpath so that the application classloader can't find them. This prevents confusing behaviour.
Second, dynamic classes can reference application classes even if they haven't previously been loaded. Since the Java classloader model delegates to the parent classloader first, the application classloader will be used to load these classes, and everything works as expected. As it happens, the dynamic classloader wouldn't be able to find the application classes anyway.
Access to the dynamically loaded classes occurs through a well-defined interface. It only really makes sense to access objects of these dynamic classes via an application-side interface which they implement. The reason for this is simple - they are a collection of probably similar, generated (runtime-defined) classes. Access must occur through some object, and so a registerObject() method is defined in DynamicClassManager. Upon class loading (ie in a static initializer), a dynamically loaded class may call this method to register an object (often, but not necessarily a singleton instance of itself) with the manager. The application can then access the object via the appropriate key (which was presumably used to create the object in the first place. Here, an illustrative exampl is useful. Say we have an often-used search string we wish to "compile". We first define a Search class/interface, with a doSearch(String searchMe) method. We then dynamically compile searchString into a class Search001 implementing/extending Search. This class should have a static initializer which instantiates a singleton and registers it by calling DynamicClassLoader.registerObject(searchString, this). The application can now call ((Search)DynamicClassLoader.getObject(searchString)).doSearch(searchMe); A somewhat contrived example perhaps, but it illustrates the point.
It may seem like added complexity to register an object for each class you load, but in practice, most applications suited to dynamic classes suit this singleton pattern. If in doubt, study the dynamic property implementation. This serves as a perfect example of (and hopefully justification for) the scheme.
This package makes use of dynamic class loading, in combination with the cojen library, to allow implementations of ClassProperty to be generated at runtime, saved to disk, and dynamically loaded on the current and subsequent executions. This way, the reflective step is only taken once. The DynamicClassManager is used to load all generated property classes from a common path, and ClassPropertyKeys are used to index them.
The properties created are both subclasses and inner classes of CompiledProperty. The common functionality, along with a helper function to register the property if factored out into CompilerProperty. Each property contains its own key, and its toString() method prints this key. This helps debugging.