Monday, March 29, 2010

ColdSpring Lite

Most of the projects I work on, including my recently released framework ColdMVC, use ColdSpring to manage their components. I honestly can't imagine starting a new project without ColdSpring -- it's that important. However, the development process can be a drag at times having to constantly reload the bean factory in order to see your changes.

While the typical response to the slow start-up time is "your application only loads once so it's not a big deal", that's really only applicable in a production environment. As a developer, my application is loaded constantly throughout the day, so it is a big deal to me. Rather than just complain about it, I decided to see if I could speed things up. Which is why I wrote my own ColdSpring Lite bean factory.

I took an approach similar to what Facebook did when they wrote HipHop: sacrifice some rarely used features in exchange for improved performance. With that in mind, I had to decide which features were necessary and which features I could live without.

Here's what it supports:

  • property injection

  • value, list, and map definitions

  • dynamic properties (i.e. ${property})

  • factory and bean post processors

  • autowiring



Here's what it doesn't support:

  • constructor arguments

  • complex nested bean injection (arrays of bean ref's)

  • bean aliases

  • parent beans

  • non-singleton beans

  • aop proxies

  • remote proxies

  • factory beans

  • xml imports



Will this limited feature set work for every project? Of course not. Will it be sufficient enough for most projects? Maybe.

Compared to ColdSpring's DefaultXmlBeanFactory, I cut the bean factory load time almost in half, which is pretty good if you ask me. Granted the load time is pretty dependent on the size of your application, so the performance improvements could vary between projects.

Here's the code if anyone is interested. I've also included a slightly modified version inside ColdMVC as well.


component {

public any function init(required string filePath, struct config) {

beanDefinitions = {};
beanInstances = {};
factoryPostProcessors = [];
beanPostProcessors = [];

var xml = fileRead(filePath);

if (structKeyExists(arguments, "config")) {
var setting = "";
for (setting in config) {
xml = replaceNoCase(xml, "${#setting#}", config[setting], "all");
}
}

loadBeans(xmlParse(xml));

return this;

}

private void function loadBeans(required xml xml) {

var i = "";

for (i=1; i <= arrayLen(xml.beans.xmlChildren); i++) {

var xmlBean = xml.beans.xmlChildren[i];

var bean = {
id = xmlBean.xmlAttributes.id,
class = xmlBean.xmlAttributes.class,
constructed = false,
autowired = false,
properties = {}
};

for (j=1; j <= arrayLen(xmlBean.xmlChildren); j++) {
bean.properties[xmlBean.xmlChildren[j].xmlAttributes.name] = xmlBean.xmlChildren[j].xmlChildren[1];
}

if (getXMLAttribute(xmlBean, "factory-post-processor", false)) {
arrayAppend(factoryPostProcessors, bean.id);
}

if (getXMLAttribute(xmlBean, "bean-post-processor", false)) {
arrayAppend(beanPostProcessors, bean.id);
}

beanDefinitions[bean.id] = bean;

}

processFactoryPostProcessors();

}

private string function getXMLAttribute(required xml xml, required string key, string def="") {

if (structKeyExists(xml.xmlAttributes, key)) {
return xml.xmlAttributes[key];
}
else {
return def;
}

}

private void function processBeanPostProcessors(required any bean, required string beanName) {

var i = "";

for (i=1; i <= arrayLen(beanPostProcessors); i++) {
var postProcessor = getBean(beanPostProcessors[i]);
postProcessor.postProcessAfterInitialization(bean, beanName);
}

}

private void function processFactoryPostProcessors() {

var i = "";

for (i=1; i <= arrayLen(factoryPostProcessors); i++) {
var postProcessor = getBean(factoryPostProcessors[i]);
postProcessor.postProcessBeanFactory(this);
}

}

public boolean function containsBean(required string beanName) {
return structKeyExists(beanDefinitions, beanName);
}

public any function getBean(required string beanName) {

var beanDef = beanDefinitions[beanName];

if (!beanDef.constructed) {
constructBean(beanName);
}

return beanInstances[beanName];

}

private void function constructBean(required string beanName) {

var property = "";
var i = "";

var dependencies = findDependencies(beanName, beanName);

for (i=1; i <= listLen(dependencies); i++) {

var beanDef = beanDefinitions[listGetAt(dependencies, i)];

lock name="BeanFactory.constructBean.#beanDef.id#" type="exclusive" timeout="5" throwontimeout="true" {

if (!beanDef.constructed) {

var beanInstance = getBeanInstance(beanDef.id);
var functions = findFunctions(beanDef.id);

if (structKeyExists(functions, "setBeanFactory")) {
beanInstance.setBeanFactory(this);
}

if (structKeyExists(functions, "setBeanName")) {
beanInstance.setBeanName(beanDef.id);
}

for (property in beanDef.properties) {
var value = parseProperty(beanDef.properties[property]);
evaluate("beanInstance.set#property#(value)");
}

beanDef.constructed = true;

processBeanPostProcessors(beanInstance, beanDef.id);

}

}

}

}

private void function addAutowiredProperties(required string beanName) {

var beanDef = beanDefinitions[beanName];

if (!beanDef.autowired) {

var functions = findFunctions(beanName);
var func = "";

for (func in functions) {

if (left(func, 3) == "set") {

var property = replaceNoCase(func, "set", "");

if (!structKeyExists(beanDef.properties, property) && containsBean(property)) {

var xml = xmlNew();
xml.xmlRoot = xmlElemNew(xml, "ref");
xml.xmlRoot.xmlAttributes["bean"] = property;

beanDef.properties[property] = xml.xmlRoot;

}

}

}

beanDef.autowired = true;

}

}

private any function getBeanInstance(required string beanName) {

lock name="BeanFactory.getBeanInstance.#beanName#" type="exclusive" timeout="5" throwontimeout="true" {

if (!structKeyExists(beanInstances, beanName)) {

beanInstances[beanName] = createObject("component", beanDefinitions[beanName].class);

if (structKeyExists(beanInstances[beanName], "init")) {
beanInstances[beanName].init();
}

}

}

return beanInstances[beanName];

}

private struct function findFunctions(required string beanName) {

var beanDef = beanDefinitions[beanName];

if (!structKeyExists(beanDef, "functions")) {

var metaData = getComponentMetaData(beanDef.class);
var functions = {};
var access = "";
var i = "";

while (structKeyExists(metaData, "extends")) {

if (structKeyExists(metaData, "functions")) {

for (i=1; i <= arrayLen(metaData.functions); i++) {

if (structKeyExists(metaData.functions[i], "access")) {
access = metaData.functions[i].access;
}
else {
access = "public";
}

if (!structKeyExists(functions, metaData.functions[i].name)) {
if (access != "private") {
functions[metaData.functions[i].name] = access;
}
}

}

}

metaData = metaData.extends;

}

beanDef.functions = functions;

}

return beanDef.functions;

}

private string function findDependencies(required string beanName, required string dependencies) {

addAutowiredProperties(beanName);

var beanDef = beanDefinitions[beanName];
var property = "";

for (property in beanDef.properties) {

var xml = beanDef.properties[property];

if (xml.xmlName == "ref") {

var dependency = xml.xmlAttributes.bean;

if (!listFindNoCase(dependencies, dependency)) {
dependencies = listAppend(dependencies, dependency);
dependencies = findDependencies(dependency, dependencies);
}

}

}

return dependencies;

}

private any function parseProperty(required xml xml, struct result) {

var i = "";

if (!structKeyExists(arguments, "result")) {
arguments.result = {};
}

switch(xml.xmlName) {

case "property": {
result[xml.xmlAttributes.name] = parseProperty(xml.xmlChildren[1], result);
break;
}

case "value": {
return xml.xmlText;
}

case "list": {

var array = [];

for (i=1; i <= arrayLen(xml.xmlChildren); i++) {
var value = parseProperty(xml.xmlChildren[i], result);
arrayAppend(array, value);
}

return array;

}

case "map": {

var struct = {};

for (i=1; i <= arrayLen(xml.xmlChildren); i++) {
var value = parseProperty(xml.xmlChildren[i].xmlChildren[1], result);
struct[xml.xmlChildren[i].xmlAttributes.key] = value;
}

return struct;

}

case "ref": {
return getBeanInstance(xml.xmlAttributes.bean);
}

default: {

for (i=1; i <= arrayLen(xml.xmlRoot.xmlChildren); i++) {
parseProperty(xml.xmlRoot.xmlChildren[i], result);
}

}

}

return result;

}

}

No comments:

Post a Comment