0

Le problème que je rencontre est le suivant: J'ai une série de classes qui sont collectées via des annotations. Ils résident tous dans le même dossier, et s'ils ont l'annotation particulière, ils sont instanciés via le Reflections library. Pendant l'instanciation de ces classes, il existe un initialiseur statique qui appelle une fabrique statique, qui construit une structure. Java lancera l'erreur InvocationTargetException en essayant d'obtenir l'objet créé en usine. Plus précisément, lorsque je publie la stacktrace pour ITE, elle pointe directement sur l'initialiseur statique qui demande à l'usine l'objet.Java `InvocationTargetException` avec instanciation de classe par réflexion

Voici le code que j'utilise pour répliquer le problème.

J'ai une annotation: InferenceRule.java

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

Je demande alors que l'annotation à un certain nombre de classes dans le package 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 
} 

La NodeAnalyzer classe, super de la classe Assign ci-dessus :

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 
} 

La classe Assign est instancié dans la classe Inference, comme décrit ci-dessous:

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

Comme vous pouvez le voir, à partir du chemin d'instanciation dans Assign et NodeAnalyzer, il doit appeler la classe 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; 
    } 
} 

la classe NaiveIdentifier:

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

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

leLa classesuit un modèle similaire à la classe IdentifierFactory. Il construit une config basée sur certaines entrées.

L'exception exacte jeté ressemble à:

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 

et:

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 

De ceux-ci, je ne peux pas discerner correctement ce que la cause est. Pour compliquer davantage ceci, j'ai essayé d'exécuter ceci en utilisant d'autres dossiers d'entrée et cela fonctionne très bien sur ceux-ci.

+0

La cause racine semble être un NPE jeté dans le constructeur de la classe 'NaiveIdentifier' (ou peut-être une classe interne anonyme). Pourriez-vous s'il vous plaît modifier la question pour inclure le code de cette classe? La deuxième erreur, avec le message 'Impossible d'initialiser la classe ...' est ce qui se passe lorsque vous essayez de charger une classe qui a déjà échoué à l'initialisation statique. Java ne prend pas la peine de faire une seconde tentative pour les initialiser. –

+0

J'ai ajouté le code pertinent. Après avoir parcouru * chaque * ligne de code, il est apparu que le problème était avec l'instanciation de 'HashSet' dans' NaiveIdentifier'. Je ne comprends pas pourquoi cela jetterait le NPE. – lilott8

+0

L'initialisation de l'accolade double-bouclée est un anti-pattern, cachant ce qui se passe réellement dans les coulisses (créant une instance d'une classe interne sous-classant le type que vous voulez réellement instancier), le tout pour l'avantage discutable d'écrire (élément); 'au lieu de' fieldName.add (élément); '. Ironiquement, vous n'utilisez même pas cet avantage car vous avez écrit 'unknowns.add (0);', n'enregistrant aucun caractère, mais en brisant tout le code en accédant au champ 'unknowns' du constructeur de la classe interne, avant le objet construit a été affecté au champ. – Holger

Répondre

2

Ce code

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 
} 

est d'utiliser le « Double Curly Brace Initialisation » anti-modèle. Habituellement, cet anti-modèle est utilisé pour économiser en tapant le code source:

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 
} 

au détriment de la création d'une nouvelle sous-classe de la classe de collecte et de créer potentiellement des fuites de mémoire que les classes internes détiennent des références à leur instance de classe extérieure , comme discuté dans this Q&A.Comme une torsion ironique, vous n'avez pas omis les caractères unknowns., ainsi non seulement pris aucun avantage de cet anti-pattern, mais créé ce bug, comme vous accédez au champ supposé être initialisé avec l'instance de jeu construite à partir de l'intérieur du constructeur de l'ensemble. En d'autres termes, votre code est équivalent au code suivant:

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 
} 

qui montre clairement, pourquoi ce code échoue avec un NullPointerException.

Vous pouvez résoudre ce problème en utilisant l'anti-pattern de manière cohérente, en supprimant les caractères unknowns. pour changer l'accès au champ d'instance externe en invocation de superclasse (comme dans le deuxième exemple de code ci-dessus), maintenant que les caractères sont présents , vous pouvez facilement changer le code d'utiliser un initialiseur propre sans l'anti-modèle:

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

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

Lorsque vous utilisez des accolades simples, vous n'êtes pas en train de créer une sous-classe de classe interne de HashSet, mais juste définissant un initialiseur qui sera ajouté au constructeur de NaiveIdentifier, exécuté dans l'ordre du texte du programme attendu, d'abord, l'initialiseur unknowns = new HashSet<Integer>(), puis les instructions unknowns.add(…);.

Pour les instructions simples d'initialisation, vous pouvez envisager l'alternative

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

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