[英]Java `InvocationTargetException` with class instantiation by reflection
因此,我遇到的問題是:我有一系列通過注釋收集的類。 它們都位於同一個文件夾中,並且如果具有特定的注釋,則可以通過Reflections
庫實例化它們。 在實例化這些類時,有一個靜態初始化程序調用一個靜態工廠,該工廠建立一些結構。 Java將在嘗試獲取工廠創建的對象時拋出InvocationTargetException
錯誤。 更具體地說,當我輸出ITE
的stacktrace時,它直接指向向工廠詢問對象的靜態初始化器。
以下是我用來復制問題的代碼。
我有一個注釋: 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());
}
}
}
}
如您所見,從Assign
和NodeAnalyzer
的實例化路徑,它必須調用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
從這些內容中,我無法充分分辨出根本原因是什么。 為了使這一點更加復雜,我嘗試使用其他輸入文件來運行它,並且在這些文件上工作得很好。
這段代碼
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
}
正在使用“ Double Curly Brace Initialization”反模式。 通常,此反模式用於保存源代碼中的某些鍵入內容:
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
}
如本問答中所討論的那樣,以創建集合類的新子類為代價,並且由於內部類持有對其外部類實例的引用,因此有可能造成內存泄漏。
具有諷刺意味的是,您沒有忽略unknowns.
字符unknowns.
,因此,不僅沒有利用此反模式的任何優勢,而且還創建了此bug,因為您正在從集合的構造函數中訪問應該使用構造的集合實例初始化的字段。 換句話說,您的代碼等效於以下代碼:
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
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.