0

我遇到的问题是这样的:我有一系列通过注释收集的类。它们都位于同一个文件夹中,如果它们具有特定的注释,则它们将通过Reflections library进行实例化。当这些类被实例化时,有一个静态初始化器调用一个静态工厂,它构建了一些结构。 Java会在尝试获取工厂创建的对象时抛出InvocationTargetException错误。更具体地说,当我输出ITE的堆栈跟踪时,它直接指向为工厂请求对象的静态初始化程序。Java`InvocationTargetException`通过反射类实例化

下面是我用来复制问题的代码。

我具有注释:InferenceRule.java

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface funRule { 
    String ruleName(); 
    String ruleType(); 
    String analyze() default "node"; 
} 

我然后该注释适用于某些数目的类在包inference.rules

@InferenceRule(ruleName = "assign", ruleType = "term") 
public class Assign extends NodeAnalyzer { 
    public Assign() {super();} 
    public Assign(String... args) { super(args); } 
    public Rule gatherAllCOnstraints(InstructionNode node) { 
     // use the Identifier object here. 
    } 
    // rest of class here 
} 

NodeAnalyzer类中,Assign类的超级上述:

public abstract class NodeAnalyzer { 
    protected Identifier identifier; 

    protected NodeAnalyzer() { 
     // Construct things here 
    } 

    protected NodeAnalyzer(String... args) { 
     // Construct other things here 
    } 

    // Construct common things here 
    { 
     this.identifier = IdentifierFactory.getIdentifier(); 
    } 
    // rest of class here 
} 

Assign类实例化在Inference类,如下所述:

public class Inference { 
    public final String NODE_ANALYSIS = "NODE"; 
    public static final String INFERENCE_PACKAGE = "inference.rules"; 
    private final Map<String, NodeAnalyzer> nodeAnalyzer = new HashMap<>(); 
    private final Map<String, EdgeAnalyzer> edgeAnalyzer = new HashMap<>(); 
    public Inference() { 

    } 
    // other non-interesting things here 

    private void loadRules() { 
     Reflections reflection = new Reflections(INFERENCE_PACKAGE); 
     Set<Class<?>> annotated = reflection.getTypesAnnotatedWith(InferenceRule.class); 

     for(Class<?> clazz : annotated) { 
      try { 
       String name = clazz.getAnnotation(InferenceRule.class).ruleName(); 
       String type = clazz.getAnnotation(InferenceRule.class).ruleType(); 
       String analyze = clazz.getAnnotation(InferenceRule.class).analyze(); 
       if (StringUtils.equalsIgnoreCase(analyze, NODE_ANALYSIS)) { 
        final NodeAnalyzer newInstance = (NodeAnalyzer) clazz.getConstructor(InferenceType.class).newInstance(InferenceType.valueOf(type)); 
        this.nodeAnalyzer.put(name, newInstance); 
       } 
       // handle other cases... 
      } catch(InvocationTargetException ite) { 
       // For debugging, only 
       ite.printStackTrace(); 
       logger.error(ite.getCause.getMessage()); 
       logger.error(ite.getTargetException.getMessage()); 
      } 
     } 
    } 
} 

正如你可以看到,从AssignNodeAnalyzer实例化路径,它必须调用IdentifierFactory类:

public class IdentifierFactory { 
    private static final Identifier identifier; 
    static { 
     if (ConfigFactory.getConfig().isDebEnabled()) { 
      identifier = new DBIdentifier(); 
     } else { 
      identifier = new NaiveIdentifier(); 
     } 
    } 

    public static Identifier getIdentifier() { 
     return identifier; 
    } 
} 

NaiveIdentifier类别:

public class NaiveIdentifier { 
    private Set<Integer> unknowns = new HashSet<Integer>() {{ 
     unknowns.add(0); 
     // add more here. 
    }; 

    public NaiveIdentifier() {} // empty default constructor 
} 

ConfigFactory类遵循类似于IdentifierFactory类的模式。它根据一定的输入建立一个配置。

抛出确切异常的样子:

java.lang.reflect.InvocationTargetException 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) 
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) 
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423) 
    at phases.inference.Inference.loadRules(Inference.java:197) 
    at phases.inference.Inference.<init>(Inference.java:76) 
    at phases.PhaseFacade$PHASES.getPhase(PhaseFacade.java:27) 
    at phases.PhaseFacade.<init>(PhaseFacade.java:42) 
    at compilation.Compiler.runPhases(Compiler.java:126) 
    at compilation.Compiler.runAllOps(Compiler.java:118) 
    at Main.main(Main.java:45) 
Caused by: java.lang.ExceptionInInitializerError 
    at phases.inference.rules.NodeAnalyzer.<init>(NodeAnalyzer.java:35) 
    at phases.inference.rules.Assign.<init>(Assign.java:22) 
    ... 11 more 
Caused by: java.lang.NullPointerException 
    at typesystem.identification.NaiveIdentifier$1.<init>(NaiveIdentifier.java:23) 
    at typesystem.identification.NaiveIdentifier.<init>(NaiveIdentifier.java:22) 
    at typesystem.identification.IdentifierFactory.<clinit>(IdentifierFactory.java:25) 
    ... 13 more 

和:

java.lang.reflect.InvocationTargetException 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) 
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) 
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423) 
    at phases.inference.Inference.loadRules(Inference.java:197) 
    at phases.inference.Inference.<init>(Inference.java:76) 
    at phases.PhaseFacade$PHASES.getPhase(PhaseFacade.java:27) 
    at phases.PhaseFacade.<init>(PhaseFacade.java:42) 
    at compilation.Compiler.runPhases(Compiler.java:126) 
    at compilation.Compiler.runAllOps(Compiler.java:118) 
    at Main.main(Main.java:45) 
Caused by: java.lang.NoClassDefFoundError: Could not initialize class typesystem.identification.IdentifierFactory 
    at phases.inference.rules.NodeAnalyzer.<init>(NodeAnalyzer.java:35) 
    at phases.inference.rules.Assign.<init>(Assign.java:18) 
    ... 11 more 

从这些,我无法充分地看清楚的根本原因是什么。为了进一步复杂化,我试图用其他输入文件来运行它,它对这些工作很好。

+0

根本原因似乎是一个NPE在构造函数中抛出了'NaiveIdentifier'类(也可能是一个匿名内部类)。你能否编辑这个问题来包含这个类的代码?第二个错误消息'Could not initialize class ...'是当你尝试加载一个已经失败的静态初始化的类时会发生什么。 Java不打算再次尝试初始化这些。 –

+0

我加了相关的代码。在遍历*每行*代码之后,似乎问题出现在'NaiveIdentifier'中的'HashSet'的实例化中。我不明白为什么会抛出NPE。 – lilott8

+0

双花括号初始化是一种反模式,隐藏幕后实际发生的事情(创建一个内部类的实例,继承你实际想要实例化的类型),所有这些都是为了能够编写'add (element);'而不是'fieldName.add(element);'。具有讽刺意味的是,你甚至没有用'unknowns.add(0);'这样的优点,而不是保存任何字符,而是通过在内部类的构造函数中访问'unknowns'字段来破坏整个代码。已构建的对象已分配给该字段。 – Holger

回答

2

此代码

public class NaiveIdentifier { 
    private Set<Integer> unknowns = new HashSet<Integer>() {{ 
     unknowns.add(0); 
     // add more here. 
    }}; // (<- added missing brace here) 

    public NaiveIdentifier() {} // empty default constructor 
} 

使用“双大括号初始化”反模式。通常情况下,这种反模式是用来保存在源代码中的一些打字:

public class NaiveIdentifier { 
    private Set<Integer> unknowns = new HashSet<Integer>() {{ 
     // yeah, we saved writing the nine characters "unknowns." 
     add(0); 
     // add more here. 
    }}; 

    public NaiveIdentifier() {} // empty default constructor 
} 

在创建集合类的一个新的子类的费用,并有可能造成内存泄漏的内部类持有引用到其外部类的实例如this Q&A中所讨论的。

作为一个具有讽刺意味的转折,你没有遗漏字符unknowns.,因此不仅没有采取这种反模式的优势,而且创建了这个错误,因为你正在访问应该用构造集实例初始化的字段来自该集合的构造函数。换句话说,你的代码就相当于下面的代码:

public class NaiveIdentifier { 
    private Set<Integer> unknowns; 
    { 
     Set<Integer> temp = new HashSet<Integer>() {{ 
     unknowns.add(0); 
     // add more here. 
     }}; 
     unknowns = temp; 
    } 

    public NaiveIdentifier() {} // empty default constructor 
} 

这使得它清楚,为什么这个代码失败,出现NullPointerException

您可以通过一致地使用反模式来解决此问题,即删除unknowns.字符以将外部实例字段访问更改为超类调用(如上面的第二个代码示例中所示),但是,现在字符已存在,你可以很容易地修改代码,用干净的初始化没有反模式:

public class NaiveIdentifier { 
    private Set<Integer> unknowns = new HashSet<Integer>(); 
    { 
     unknowns.add(0); 
     // add more here. 
    } 

    public NaiveIdentifier() {} // empty default constructor 
} 

当使用单大括号,你是不是创造HashSet一个内部类的子类,只是定义一个初始化,这将是添加到NaiveIdentifier的构造函数中,以预期的程序文本顺序执行,首先,初始化程序unknowns = new HashSet<Integer>(),然后是unknowns.add(…);声明。

对于简单的初始化语句,你可以考虑替代

public class NaiveIdentifier { 
    private Set<Integer> unknowns = new HashSet<>(Arrays.asList(0, 1, 2, 3 …)); 

    public NaiveIdentifier() {} // empty default constructor 
}