How to get singleton instance from a class name as

2019-08-31 13:37发布

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?

2条回答
等我变得足够好
2楼-- · 2019-08-31 13:51

There are several solutions with different pros and cons:

  1. Don't use static methods. If the method isn't static, you can add it to IRule and therefore enforce that the method exists.
  2. 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 );
    }
}
查看更多
仙女界的扛把子
3楼-- · 2019-08-31 13:58

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.

查看更多
登录 后发表回答