I am running into a strange problem. I have an interface, whose implementations tend to be stateless. So, I want them to be singletons.
I get the implementation class names as strings. For example
String clazz = "com.foo.Bar";
I have a rules factory to obtain instances of IRule
implementations.
public class RulesFactory {
private static final Logger logger = LoggerFactory.getLogger(RulesFactory.class);
@SuppressWarnings("unchecked")
public static <T extends IRule> T getRuleInstance(String clazz) {
try {
Class<?> ruleObject = Class.forName(clazz);
Method factoryMethod = ruleObject.getMethod("getInstance");
return (T) factoryMethod.invoke(null);
} catch (ClassNotFoundException e) {
logger.error("ClassNotFoundException", e);
} catch (IllegalAccessException e) {
logger.error("IllegalAccessException", e);
} catch (SecurityException e) {
logger.error("SecurityException", e);
} catch (NoSuchMethodException e) {
logger.error("NoSuchMethodException", e);
} catch (IllegalArgumentException e) {
logger.error("IllegalArgumentException", e);
} catch (InvocationTargetException e) {
logger.error("InvocationTargetException", e);
}
return null;
}
}
The above code throws NullPointerException
if the class doesn't have a static getInstance()
method. In Java 6 i can't use static methods in interfaces. I don't want to create multiple instances of IRule
implementations. If I can enforce a static method and invoke that static method I will get the cashed instance. But I am unable to do this. How to solve this problem?
There are several solutions with different pros and cons:
- Don't use
static
methods. If the method isn't static, you can add it to IRule
and therefore enforce that the method exists.
- Check the qualifiers of
factoryMethod
and throw a descriptive exception when they aren't static
For solution #1, you need a Map<String,IRule>
. When getRuleInstance()
is called, check the map for an instance. If there is none, use the method from the interface to create one and put it into the map. This way, you can make the instances singletons.
At the same time, you can get all fields of the instance and make sure all of them are final
to enforce the statelessness.
If your application is multi-threaded, make sure you use a concurrent map and synchronize properly.
Example code:
private Map<String, IRule> rules = Maps.newHashMap();
public static <T extends IRule> T getRuleInstance(String clazz) {
try {
synchronized( rules ) {
IRule result = rules.get(clazz);
if(null == result) {
result = clazz.newInstance();
rules.put(clazz, result);
}
@SuppressWarnings("unchecked")
T tmp = (T) result;
return tmp;
}
} catch (Exception e) {
log( "Unable to create IRule for {}", clazz );
}
}
You are making your life unnecessary hard.
If you remember the great “enum
singleton pattern” and require that all implementors use it, e.g.
public enum Foo implements IRule
{
INSTANCE;
// IRule implementation methods here
public String toString() { return "the sole Foo instance"; }
}
the entire RulesFactory
becomes as simple as:
private static final ConcurrentMap<String, IRule> instancesMap
= new ConcurrentHashMap<String, IRule>();
public static IRule getRuleInstance(String clazz) {
try {
IRule iRuleInstance=instancesMap.get(clazz);
if(iRuleInstance!=null) return iRuleInstance;
Class<? extends IRule> ruleObject=Class.forName(clazz).asSubclass(IRule.class);
IRule[] enumConstants=ruleObject.getEnumConstants();
if(enumConstants==null || enumConstants.length!=1) {
logger.error("InvalidClassException",
new InvalidClassException(clazz, "not a singleton enum"));
return null;
}
iRuleInstance=enumConstants[0];
instancesMap.put(clazz, iRuleInstance);
return iRuleInstance;
} catch (ClassNotFoundException e) {
logger.error("ClassNotFoundException", e);
}
return null;
}
The great thing about the “enum
singleton pattern” is that it already guarantees the singleton property, thus the code above doesn’t make any attempt to detect concurrent lookups as they are guaranteed to return the same single instance.