简体   繁体   English

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

[英]Best practice to prevent further instantiation of java classes

I have some class storing keys with important information. 我有一些类存储密钥和重要信息。 No one else is allowed to create a key, since a key relys on static information (like certain directory structures etc.). 没有其他人被允许创建密钥,因为密钥依赖于静态信息(如某些目录结构等)。

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;
        }
    }

}

This example is simplyfied. 这个例子很简单。

I wanted to test the consequences of a wrong key (exception handling, etc.) and instantiated the class via reflection in a JUnit test case. 我想测试错误键(异常处理等)的后果,并通过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);

Then I asked myself how could I prevent further instantiation of the key class (ie preventing further object creating via reflection)? 然后我问自己如何防止进一步实例化密钥类(即防止通过反射进一步创建对象)?

enums came to my mind, but they don't work with generics. 我想到了enums ,但它们不适用于泛型。

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

So how can I keep a set of n instances of a generic class and prevent further instantiation? 那么如何保留一组泛型类的n实例并阻止进一步的实例化呢?

So how can I keep a set of n instances of a generic class and prevent further instantiation? 那么如何保留一组泛型类的n个实例并阻止进一步的实例化呢?

If you truly want to use this pattern, then no one (including you) should be able to instantiate a Key object. 如果您真的想要使用此模式,那么任何人(包括您)都不应该能够实例化Key对象。 In order to keep a set of n instances in a class with this pattern, you could have a private constructor, a static method for access and a SecurityManager to prevent reflection. 为了在具有此模式的类中保留一组n个实例,您可以使用私有构造函数,访问静态方法和防止反射的SecurityManager And since you want to be able to access the keys as pubic constants, I would try something like this.. 既然你希望能够作为公共常量访问密钥,我会尝试这样的东西..

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;
        }
    }
}

Use SecurityManager to prevent reflection access. 使用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");
    }

}

This will throw a SecurityException anytime someone attempts to access a private variable or field in your class (including access attempts via reflection). 只要有人试图访问您的类中的私有变量或字段(包括通过反射访问尝试),这将抛出SecurityException

I'm not sure I fully understand your question, but if a private constructor is not sufficient, can you use a more dynamic approach and throw an exception in the constructor after a signal is given? 我不确定我是否完全理解你的问题,但如果私有构造函数不够,你可以使用更动态的方法并在给出信号后在构造函数中抛出异常吗? For example: 例如:

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;
      }
}

Then - and this is the disadvantage - you will have to call Key.lock() once you want to prevent more instances being created. 然后 - 这就是缺点 - 一旦你想要阻止创建更多实例,就必须调用Key.lock()

As you showed in your code to prevent instantiating KeyConstants you can throw some Exception inside private-non-argument constructor. 正如您在代码中展示的那样,为了防止实例化KeyConstants您可以在private-non-argument构造函数中抛出一些Exception。

Harder part is way to block creating KeyConstants.Key constructor from outside of KeyConstants class. 更难的部分是阻止从KeyConstants类外部创建KeyConstants.Key构造函数的KeyConstants

Some wild idea 一些疯狂的想法

Maybe create Exception in your constructor and check how its stack trace looks like. 也许在构造函数中创建Exception并检查其堆栈跟踪的样子。 When I add this code to constructor 当我将此代码添加到构造函数时

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;
}

and create instance of Key with reflection like 并使用反射创建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);

I get 我明白了

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)

so maybe just if 4th element of stack is java.lang.reflect.Constructor.newInstance throw Exception to prevent executing rest of constructors code like: 所以也许只是堆栈的第四个元素是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");
}

I came across some Multiton patterns recently, where I tried to handle problems with unique enum keys, that gave me the idea of another approach. 我最近遇到了一些Multiton模式,我试图用独特的枚举键处理问题,这让我想到了另一种方法。

The keys can be used for information flow as I intended, or even as keys for typesafe heterogeneous container, where they can perform compile-time casting. 密钥可以按照我的意图用于信息流,甚至可以用作类型安全异构容器的密钥,它们可以执行编译时转换。

Key-defining class 键定义类

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;
    }
  }
}

Example key-using 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
  }
}

I have another approach, you can bound an interface in a way to only be implemented by enum . 我有另一种方法,你可以以一种只能通过enum实现的方式绑定接口。 With that approach you have a fixed set of instances at compile time. 使用该方法,您可以在编译时获得一组固定的实例。

If you want to add lazy loading, the enums implementing it should be proxies that load the desired object if it is requested. 如果要添加延迟加载,则实现它的枚举应该是在请求时加载所需对象的代理。 The class or classes that are hidden behind the proxies should only be visible to them, so that they have exclusive access to the constructor. 隐藏在代理后面的一个或多个类应该只对它们可见,以便它们具有对构造函数的独占访问权。

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; }
//}

If I understand you correctly, you don't want your class to be instantiated. 如果我理解正确,你不希望你的类被实例化。 You can set the default constructor to private 您可以将默认构造函数设置为private

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

This will prevent its improper instantiation. 这将防止其不正确的实例化。

Update: added throw IllegalStateException 更新:添加throw IllegalStateException

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM