2010-09-01 16 views
13

J'utilise les transactions déclaratives de Spring (l'annotation @Transactional) en mode "aspectj". Cela fonctionne dans la plupart des cas exactement comme il se doit, mais pour un, ce n'est pas le cas. Nous pouvons l'appeler Lang (parce que c'est ce qu'on appelle en fait).AspectJ Load Weaver ne détecte pas toutes les classes

J'ai pu identifier le problème au tisserand de chargement. En activant le débogage et la journalisation détaillée dans aop.xml, il répertorie toutes les classes en cours de tissage. La classe problématique Lang n'est en effet pas mentionnée dans les journaux.

Ensuite, je mets un point d'arrêt en haut de Lang, provoquant Eclipse pour suspendre le thread lorsque la classe Lang est chargée. Ce point d'arrêt est atteint pendant que le LTW tisse d'autres classes! Donc, je devine qu'il essaye ou non de tisser Lang et échoue et ne produit pas cela, ou une autre classe a une référence qui l'oblige à charger Lang avant qu'il ne puisse réellement le tisser.

Je ne suis pas sûr cependant comment continuer à déboguer cela, puisque je ne suis pas capable de le reproduire à plus petite échelle. Des suggestions sur comment continuer?


Mise à jour: D'autres indices sont également les bienvenus. Par exemple, comment fonctionne le LTW? Il semble y avoir beaucoup de magie qui se passe. Y at-il des options pour obtenir encore plus de sortie de débogage de la LTW? J'ai actuellement:

<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo"> 

J'ai oublié tom mentionner avant: printemps agent est utilisé pour permettre LTW, à savoir le InstrumentationLoadTimeWeaver. Sur la base des suggestions d'Andy Clément, j'ai décidé d'inspecter si le transformateur AspectJ est même passé la classe. J'ai mis un point d'arrêt dans ClassPreProcessorAgent.transform(..), et il semble que la classe Lang n'atteint jamais cette méthode, bien qu'elle soit chargée par le même chargeur de classe que les autres classes (une instance de WebAppClassLoader de Jetty). J'ai ensuite ajouté un point d'arrêt au InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..). Même pas celui-là est frappé pour Lang. Et je crois que cette méthode devrait être appelée pour toutes les classes chargées, quel que soit le chargeur de classe qu'ils utilisent. Cela commence à ressembler à:

  1. Un problème avec mon débogage. Peut-être Lang n'est pas chargé au moment où Eclipse signale il est
  2. Java bug? Farfelu, mais je suppose que ça arrive.

idée suivante: je me suis tourné sur -verbose:class et il semble que Langest en cours de chargement prématurément - probablement avant que le transformateur est ajouté à Instrumentation. Bizarrement, mon point d'arrêt Eclipse n'attrape pas ce chargement.

Cela signifie que Spring est un nouveau suspect. il semble y avoir un traitement dans ConfigurationClassPostProcessor qui charge des classes pour les inspecter. Cela pourrait être lié à mon problème.


Ces lignes ConfigurationClassBeanDefinitionReader cause la classe à lire Lang:

else if (metadata.isAnnotated(Component.class.getName()) || 
     metadata.hasAnnotatedMethods(Bean.class.getName())) { 
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); 
    return true; 
} 

metadata.hasAnnotatedMethods() En particulier, les appels getDeclaredMethods() la classe, qui charge toutes les classes de paramètres de toutes les méthodes de cette classe. Je suppose que ce n'est peut-être pas la fin du problème, car je pense que les classes sont censées être déchargées. La JVM peut-elle mettre en cache l'instance de la classe pour des raisons inconnaissables?

Répondre

7

OK, j'ai résolu le problème. Essentiellement, il s'agit d'un problème Spring associé à certaines extensions personnalisées. Si quelqu'un rencontre quelque chose de similaire, j'essaierai d'expliquer étape par étape ce qui se passe. En premier lieu, nous avons un BeanDefintionParser personnalisé dans notre projet. Cette classe a la définition suivante:.

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 

    protected Class<?> getBeanClass(Element element) { 
     try { 
      return Class.forName(element.getAttribute("class")); 
     } catch (ClassNotFoundException e) { 
      throw new RuntimeException("Class " + element.getAttribute("class") + "not found.", e); 
     } 
    } 

// code to parse XML omitted for brevity 

} 

Maintenant, le problème se produit après toute définition de haricots ont été lus et BeanDefinitionRegistryPostProcessor commence à lancer à ce stade, une classe appelée ConfigurationClassPostProcessor commence à chercher à travers toutes les définitions de haricots, pour la recherche pour les classes de haricots annotées avec @Configuration ou ayant des méthodes avec @Bean.

Lors de la lecture des annotations pour un bean, il utilise l'interface AnnotationMetadata. Pour la plupart des haricots réguliers, une sous-classe appelée AnnotationMetadataVisitor est utilisée. Cependant, lors de l'analyse des définitions de bean, si vous avez surchargé la méthode getBeanClass() pour retourner une instance de classe, comme nous l'avons fait, une instance StandardAnnotationMetadata est utilisée. Lorsque StandardAnnotationMetadata.hasAnnotatedMethods(..) est appelé, il appelle Class.getDeclaredMethods(), ce qui à son tour oblige le chargeur de classe à charger toutes les classes utilisées comme paramètres dans cette classe. Les classes chargées de cette manière ne sont pas correctement déchargées et ne sont donc jamais tissées, car cela se produit avant l'enregistrement du transformateur AspectJ.

Maintenant, mon problème est que j'avais une classe comme ceci:

public class Something { 
    private Lang lang; 
    public void setLang(Lang lang) { 
     this.lang = lang; 
    } 
} 

Ensuite, j'ai eu un haricot de classe Something qui a été analysé en utilisant notre coutume ControllerBeanDefinitionParser. Cela a déclenché la procédure de détection d'annotation incorrecte, ce qui a déclenché un chargement de classe inattendu, ce qui signifie qu'AspectJ n'a jamais eu l'occasion de tisser Lang.

La solution était de ne pas passer outre getBeanClass(..), mais au lieu de passer outre getBeanClassName(..) qui, selon la documentation est préférable:

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 

    protected String getBeanClassName(Element element) { 
     return element.getAttribute("class"); 
    } 

// code to parse XML omitted for brevity 

} 

Leçon du jour: Ne pas passer outre getBeanClass à moins que vous pensez vraiment. En fait, n'essayez pas d'écrire votre propre BeanDefinitionParser à moins de savoir ce que vous faites.

Fin.

1

Option 1) L'aspect J est open source. Crack il ouvre et voir ce qui se passe.

Option 2) Renommez votre classe à Bang, voir s'il commence à travailler

Je ne serais pas surpris s'il est difficile de sauter codage « lang » là-bas, mais je ne peux pas dire pourquoi.

Edition -

Code voyant comme celui-ci dans la source

 if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object 
      superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class); 
      superclassname = Utility.compactClassName(superclassname, false); 

} else { 
      superclassname = "java.lang.Object"; 
     } 

on dirait qu'ils essaient de sauter le tissage de java.lang.stuff .... ne vois rien pour seulement « lang » mais il peut être là (ou un bogue)

+0

Merci pour la réponse. Idée intéressante que la chaîne "Lang" pourrait poser problème. Mais j'aurais dû le mentionner, il y a d'autres classes (au moins une) qui ne sont pas non plus tissées. L'autre connu s'appelle CommServer. Donc je doute que ce soit le problème. Je devrais peut-être commencer à regarder la source AspectJ, cependant. – waxwing

4

Si votre classe n'est pas mentionnée dans la sortie -verbose/-debug, cela me suggère qu'elle n'est pas chargée par le chargeur que vous pensez être. Pouvez-vous être sûr à 100% que 'Lang' n'est pas sur le classpath d'un classloader plus haut dans la hiérarchie? Quel classloader charge Lang au moment où vous déclenchez votre point d'arrêt? De plus, vous ne mentionnez pas la version d'AspectJ - si vous êtes sur 1.6.7 qui a eu des problèmes avec ltw pour autre chose qu'un fichier aop.xml trivial. Vous devriez être sur 1.6.8 ou 1.6.9.

Comment ça fonctionne réellement?

En d'autres termes, une machine à tisser AspectJ est créée pour chaque chargeur de classe pouvant souhaiter tisser du code. On demande à AspectJ s'il veut modifier les octets d'une classe avant qu'elle ne soit définie sur la machine virtuelle. AspectJ examine tous les fichiers aop.xml qu'il peut 'voir' (en tant que ressources) via le chargeur de classe en question et les utilise pour se configurer lui-même. Une fois configuré, il tisse les aspects spécifiés, en tenant compte de toutes les clauses d'inclusion/exclusion.

Andy Clement
AspectJ Chef de projet

+0

J'ai 1.6.9 d'aspectjweaver et d'aspectjrt dans mes bibliothèques de guerre. Lang.class.getClassLoader() renvoie la même instance de classloader qu'une autre classe qui est tissée. J'ai ensuite expérimenté en mettant un point d'arrêt dans ClassPreProcessorAgent, et en fait, Lang n'est jamais passé à sa méthode transform(). Peut-être que je devrais essayer de le déployer sur Tomcat à la place. – waxwing

+0

Lorsque vous avez mentionné le problème de chargement «trop tôt», cela m'a rappelé que je l'avais déjà vu avec Spring. Une recherche rapide du printemps Jira a soulevé https://jira.springframework.org/browse/SPR-4963 qui est un genre similaire de problème. Il suggère une retransformation de classe comme une réponse (où la classe serait de nouveau conduite à travers le processus de chargement, en utilisant les octets originaux, mais la deuxième fois par AspectJ le verra), mais pour autant que je sache, nous n'avons rien fait pour cela au printemps encore - je ne pense pas que quelque chose soit prévu pour 3.1 non plus. –