防止进一步实例化java类的最佳实践

时间:2013-08-01 13:18:54

标签: java reflection constructor

我有一些类存储包含重要信息的键。没有其他人允许来创建密钥,因为密钥依赖于静态信息(如某些目录结构等)。

public final class KeyConstants
{

    private KeyConstants()
    {
        // could throw an exception to prevent instantiation
    }

    public static final Key<MyClass> MY_CLASS_DATA = new Key<MyClass>("someId", MyClass.class);

    public static class Key<T>
    {
        public final String ID;
        public final Class<T> CLAZZ;

        private Key(String id, Class<T> clazz)
        {
            this.ID = id;
            this.CLAZZ = clazz;
        }
    }

}

这个例子很简单。

我想测试错误的键(异常处理等)的后果,并通过JUnit测试用例中的反射实例化该类。

Constructor<?> c = KeyConstants.Key.class.getDeclaredConstructor(String.class, Class.class);
c.setAccessible(true);
@SuppressWarnings ("unchecked")
KeyConstants.Key<MyClass> r = (KeyConstants.Key<MyClass>) c.newInstance("wrongId", MyClass.class);

然后我问自己如何防止进一步实例化密钥类(即阻止通过反射进一步创建对象)?

enums出现在我的脑海中,但它们不适用于泛型。

public enum Key<T>
{
    //... Syntax error, enum declaration cannot have type parameters
}

那么如何保留一组泛型类的n个实例并防止进一步实例化?

6 个答案:

答案 0 :(得分:3)

  

那么如何保留一组泛型类的n个实例并防止   进一步实例化?

如果您真的想要使用此模式,那么任何人(包括您)都不应该能够实例化Key对象。为了在具有此模式的类中保留一组n个实例,您可以使用私有构造函数,访问静态方法和SecurityManager来防止反射。既然你希望能够作为公共常量访问密钥,我会尝试这样的东西..

public class KeyConstants{

    // Here are your n instances for public access
    public static final int KEY_1 = 1;
    public static final int KEY_2 = 2;
    .
    .
    .
    public static final int KEY_N = 'n';


    // now you can call this method like this..
    // Key mKey = KeyConstants.getKey(KeyConstants.KEY_1);
    public static Key getKey(int key){

         List keys = Key.getInstances();

         switch(key){

         case KEY_1:
                     return keys.get(0);
         case KEY_2:
                     return keys.get(1);
         .
         .
         .
         case KEY_N:
                     return keys.get(n);
         default:
                     // not index out of bounds.. this means
                     // they didn't use a constant
                     throw new IllegalArgumentException();
         }

    }

    static class Key<T>{
        private static List<Key> instances;
        private String ID;
        private Class<T> CLAZZ;

        private Key(String id, Class<T> clazz){
                      this.ID = id;
                      this.CLAZZ = clazz;
         }

        public static List<Key> getInstances(){
            if(instances == null){

                            instances = new ArrayList<Key>();
                //populate instances list
            }

                    return instances;
        }
    }
}

使用SecurityManager阻止反射访问。

//attempt to set your own security manager to prevent reflection
    try {
        System.setSecurityManager(new MySecurityManager());
    } catch (SecurityException se) { 
    }

class MySecurityManager extends SecurityManager {

    public void checkPermission(Permission perm) {
        if (perm.getName().equals("suppressAccessChecks"))
            throw new SecurityException("Invalid Access");
    }

}

只要有人试图访问您班级中的私有变量或字段(包括通过反射进行访问尝试),就会抛出SecurityException

答案 1 :(得分:1)

我不确定我是否完全理解你的问题,但如果私有构造函数不够,你可以使用更动态的方法并在给出信号后在构造函数中抛出异常吗?例如:

public static class Key<T>
{
  private static boolean isLocked = false;

  // Call this method when you want no more keys to be created
  public static void lock() { isLocked = true; }

  ...

      private Key(String id, Class<T> clazz)
      {
          if (isLocked) throw new IllegalStateException("Cannot create instances of Key");
          this.ID = id;
          this.CLAZZ = clazz;
      }
}

然后 - 这就是缺点 - 一旦你想阻止创建更多实例,就必须调用Key.lock()

答案 2 :(得分:1)

正如您在代码中展示的那样,为了防止实例化KeyConstants,您可以在private-non-argument构造函数中抛出一些Exception。

更难的部分是阻止从KeyConstants类外部创建KeyConstants.Key构造函数的方法。

一些疯狂的想法

也许在构造函数中创建Exception并检查其堆栈跟踪的样子。当我将此代码添加到构造函数

private Key(String id, Class<T> clazz) {

    StackTraceElement[] stack = new Exception().getStackTrace();
    for (int i=0; i<stack.length; i++){
        System.out.println(i+") "+stack[i]);
    }

    this.ID = id;
    this.CLAZZ = clazz;
}

并使用反射创建Key的实例,如

Constructor<?> c = KeyConstants.Key.class.getDeclaredConstructor(
        String.class, Class.class);
c.setAccessible(true);
KeyConstants.Key<MyClass> r = (KeyConstants.Key<MyClass>) c
        .newInstance("wrongId", MyClass.class);

我得到了

0) KeyConstants$Key.<init>(Test.java:38)
1) sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
2) sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
3) sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
4) java.lang.reflect.Constructor.newInstance(Constructor.java:525)

所以也许只是堆栈的第4个元素是java.lang.reflect.Constructor.newInstance抛出异常以防止执行其余的构造函数代码,如:

if (stack.length>=4 && stack[4].toString().startsWith("java.lang.reflect.Constructor.newInstance")){
    throw new RuntimeException("cant create object with reflection");
}

答案 3 :(得分:1)

我最近遇到了一些Multiton模式,我尝试使用独特的枚举键处理问题,这让我想到了另一种方法。

密钥可用于我想要的信息流,甚至可以用作类型安全异构容器的密钥,它们可以执行编译时转换。

键定义类

public class KeyConstants
{

  public static final KeysForIntegers SOME_INT_KEY = KeysForIntegers.KEY_2;

  public static final KeysForStrings SOME_STRING_KEY = KeysForStrings.KEY_1;

  public interface Key<Type>
  {
    public Class<Type> getType();
  }

  /* Define methods that classes working with the keys expect from them */
  public interface KeyInformation
  {
    public String getInfo1();
    // and so on...
  }

  public enum KeysForStrings implements Key<String>, KeyInformation
  {
    KEY_1("someId");

    public final String ID;

    private KeysForStrings(String id)
    {
      ID = id;
    }

    @Override
    public String getInfo1()
    {
      return "Good piece of information on " + ID + ".";
    }

    @Override
    public Class<String> getType()
    {
      return String.class;
    }
  }

  public enum KeysForIntegers implements Key<Integer>, KeyInformation
  {
    KEY_2("bla");

    public final String ID;

    private KeysForIntegers(String id)
    {
      this.ID = id;
    }

    @Override
    public String getInfo1()
    {
      return "Some info on " + ID + ".";
    }

    @Override
    public Class<Integer> getType()
    {
      return Integer.class;
    }
  }
}

使用密钥的示例

public class KeyUser
{
  public static void main(String[] args)
  {
    KeysForIntegers k1 = KeyConstants.SOME_INT_KEY;
    KeysForStrings k2 = KeyConstants.SOME_STRING_KEY;

    processStringKey(k2);
    useIntKey(k1);

    Integer i = useIntKey(KeyConstants.SOME_INT_KEY);
    processStringKey(KeyConstants.SOME_STRING_KEY);
  }

  /* My methods should just work with my keys */

  @SuppressWarnings ("unchecked")
  public static <TYPE, KEY extends Enum<KeysForIntegers> & Key<TYPE> & KeyInformation> TYPE useIntKey(KEY k)
  {
    System.out.println(k.getInfo1());
    return (TYPE) new Object();
  }

  public static <KEY extends Enum<KeysForStrings> & KeyInformation> void processStringKey(KEY k)
  {
    System.out.println(k.getInfo1());
    // process stuff
  }
}

答案 4 :(得分:1)

我有另一种方法,您可以通过enum实现的方式绑定接口。 使用这种方法,您可以在编译时获得一组固定的实例。

如果要添加延迟加载,实现它的枚举应该是在请求时加载所需对象的代理。隐藏在代理服务器后面的一个或多个类应该只对它们可见,这样它们才能拥有对构造函数的独占访问权。

public class User {

  public static <S> S handleKey(FixedInstanceSet<S,?> key) {
    return key.getKey();
  }
}

interface FixedInstanceSet<S, T extends Enum<T> & FixedInstanceSet<S,T>>
{
  public S getKey();
}

enum StringKeys implements FixedInstanceSet<String, StringKeys> {
  TOP, DOWN, LEFT, RIGHT;
  @Override
  public String getKey() { return null; }
}

enum IntKeys implements FixedInstanceSet<Integer, IntKeys > {
  TOP, DOWN, LEFT, RIGHT;
  @Override
  public Integer getKey() { return null; }
}

/*
 * Bound mismatch: The type NotWorking is not a valid substitute for the bounded
 * parameter <T extends Enum<T> & FixedInstanceSet<S,T>> of the type
 * FixedInstanceSet<S,T>
 */
//class NotCompiling implements FixedInstanceSet<String, NotCompiling> {
//
//  @Override
//  public String getKey() { return null; }
//}

答案 5 :(得分:0)

如果我理解正确,你不希望你的类被实例化。 您可以将默认构造函数设置为private

private Key() throws IllegalStateException //handle default constructor
    {
        throw new IllegalStateException();
    }

这将阻止其不正确的实例化。

更新: 添加了throw IllegalStateException