Écrire devant
Cette démo explique comment dépanner une nulpointerexception causée par @Transactional.
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-transactional-nullpointerexception
Code pour localiser NullPointerException
La démo est un exemple de transaction de printemps simple, qui fournit le StudentDao suivant et utilise @Transactional pour déclarer la transaction:
@ Composant @ TransactionalPublic class StudentDao {@autowired private sqlSession sqlSession; Étudiant public selectStudentById (Long ID) {return sqlSession.Selectone ("SelectStudentById", id); } public Final Student finalSelectStudentById (Long ID) {return sqlSession.Selectone ("SelectStudentById", id); }}Après le démarrage de l'application, SelectStudentById et FinalSelectStudentByid seront appelés à leur tour:
@PostConstruct public void init () {StudentDao.SelectStudentById (1); StudentDao.FinalSelectStudentById (1); }Utilisez MVN Spring-Boot: Exécutez ou importez le projet dans l'IDE pour démarrer. Les informations d'exception lancées sont:
Causé par: java.lang.nullpointerException à sample.mybatis.dao.studentdao.finalselectstudentbyid (étudiantdao.java:27) à com.example.demo.transactional.nullpointerexception.demonullpointeredApcation.init (DemonullpoInterException Sun.Reflect.NativeMethodAccessOrimpl.invoke0 (méthode native) à Sun.Reflect.NativeMethodAccessOrimp.invoke (Nativemethodaccessorimpempl.java:62) à Sun.Reflect.delegatingMethodaccessorimp.invoke (DelegatingMethodAccessorImml java.lang.reflect.method.invoke (méthode.java:498) sur org.springframework.beans.factory.annotation.initdestroyannotationBeanPostprocessor $ lifecycleelement.invoke (InitDestroyannotationBeanPostprocess.java:366) à la org.springframework.beans.factory.annotation.initdestroyannotationBeanPostprocessor $ lifecyclemetadata.invokeinitmethods (initdestroyannotationbeanpostprocessor.java:311)
Pourquoi n'y a-t-il aucun problème dans l'exécution de SelectStudentById dans le code d'application et l'exécution de FinalSelectStudentByid lance une NullPointerException?
Dans le même bean, SQLSession SQLSession a été injectée et, dans SelectStudentByid, il n'est pas nul. Pourquoi NULL est-il dans la fonction FinalSelectStudentByid?
Obtenez le nom de classe de l'exécution réelle
Bien sûr, lorsque nous comparons les deux fonctions, nous pouvons savoir que c'est parce que le modificateur de FinalSelectStudentByid est définitif. Mais quelle est la raison spécifique?
Nous définissons d'abord un point d'arrêt à l'endroit où l'exception est lancée, déboguez le code et obtenons la classe spécifique au moment de l'exécution:
System.err.println (StudentDao.getClass ());
Le résultat de l'impression est:
Class Sample.mybatis.dao.Studentdao $$ EnhancerByspringCglib $$ 210B005D
On peut voir qu'il s'agit d'une classe traitée par Spring AOP, mais quel est son contenu bytecode spécifique?
Analyse des classes de bosse
Nous utilisons l'outil DumpClass pour vider la classe dans JVM:
https://github.com/hengyunabc/dumpclass
wget http://search.maven.org/remoteContent?filepath=io/github/hengyunabc/dumpclass/0.0.1/dumpclass-0.0.1.jar -o downclass.jar
Trouvez le processus Java PID:
$ JPS5907 DemonullPointerExceptionApplication
Vider toutes les classes pertinentes:
Sudo Java -Jar Dumpclass.jar 5907 'Sample.Mybatis.dao.Studentdao *' / tmp / DumpResult
Analyse de démontage
Utilisez Javap ou Graphical Tool JD-GUI pour décomposer un échantillon.mybatis.dao.studentdao $$ Enhancer GyspringCglib $$ 210b005d.
Le résultat après la décomposition est:
classe StudentDao $$ EnhancerByspringCglib $$ 210B005D étend StudentDao
StudentDao$$EnhancerBySpringCGLIB$$210b005d
L'appel réel de SelectStudentById est ce.cglib $ callback_0, c'est-à-dire MethodInterceptor TMP4_1. Déboglons réellement plus tard et voyons le type spécifique
public Final Student SelectStudentById (Long Paramlong) {try {MethodInterceptor TMP4_1 = this.cglib $ callback_0; if (tmp4_1 == null) {tmp4_1; Cglib $ bind_callbacks (this); } MethodInterceptor tmp17_14 = this.cglib $ callback_0; if (tmp17_14! = null) {objet [] tmp29_26 = nouvel objet [1]; Long tmp35_32 = new Java / Lang / Long; Long tmp36_35 = tmp35_32; tmp36_35; tmp36_35. <init> (paramlong); tmp29_26 [0] = tmp35_32; return (étudiant) TMP17_14.Intercept (ceci, CGLIB $ SelectStudentByid 0 $ Méthode, TMP29_26, CGLIB $ SelectStudentById 0 $ proxy); } return super.selectStudentById (paramlong); } catch (runtimeException | error localRuntimeException) {lancer localRuntimeException; } catch (landable localthrowable) {lancez un nouveau UnclaredThrowableException (localthrowable); }}Prenons le débogage réel. Bien que le code de StudentDao $$ améliorant GyspringCGLIB $$ 210B005D ne puisse pas être vu directement, il peut toujours être exécuté en une seule étape.
Lors du débogage, vous pouvez voir
1. StudentDao $$ Enhancer GyspringCglib $
2.
3. Après que cglibaopproxy $ dynamicadvisiseInterceptor soit traité par Transaction Interceptor, il appellera éventuellement l'objet cible d'origine enregistré par lui-même avec la réflexion.
Causes de lancer une exception
Triez donc toute l'analyse:
1. Après avoir utilisé @Transactional, Spring AOP générera une classe proxy CGLIB. StudentDao injecté par @Autowired dans le code utilisateur réel est également une instance de cette classe de proxy.
2. La classe Proxy Studentdao $$ Enhancer GyspringCglib $$ 210B005D générée par CGLIB est héritée de StudentDao
3.StudentDao $$ Enhancer GyspringCGlib $$ 210B005D est nul
4. StudentDao $$ EnhancerByspringCglib $$ 210b005d appelle SelectStudentById, et en fait, via CGlibaopproxy $ dynamicadisvisedInterceptor, il appellera finalement l'objet cible d'origine économisé par lui-même avec réflexion.
5. Il n'y a donc pas de problème à appeler la fonction SelectStudentById
Alors, pourquoi la SQLSession dans la finalesectStudentByid fonctionne-t-elle NULL et lance-t-elle ensuite une nulpointerException?
1
2. Le modificateur de la fonction FinalSelectStudentById est final, CGlib n'a aucun moyen de réécrire cette fonction
3. Lorsqu'il est exécuté dans FinalSelectStudentById, l'exécution réelle du code dans l'étudiant d'origine
4. Mais l'objet est une instance de StudentDao $$ Enhancer GyspringCGlib $$ 210B005D. Tous les champs à l'intérieur sont nuls, donc une nulpointerexception sera lancée.
Solutions au problème
1. La chose la plus simple est bien sûr de supprimer le modificateur final de la fonction finalectstudentbyid
2. Il y a une autre façon. N'utilisez pas SQLSession directement dans StudentDao, mais utilisez la fonction getSQLSession (). De cette façon, CGLIB traitera également getSQLSession () et renverra l'objet cible d'origine
Résumer
1. Dépanner les problèmes de débogage multiples et voir les informations de l'objet dans le temps d'exécution réel
2. Pour le code bytecode de la classe générée par CGLIB, vous pouvez utiliser l'outil de caisse de décharge pour vider puis décomposer et analyser
Résumer
Ce qui précède est l'étude approfondie du dépannage de la botte de printemps @Transactional. J'espère que ce sera utile à tout le monde. Si vous avez des questions, veuillez me laisser un message et l'éditeur répondra à tout le monde à temps. Merci beaucoup pour votre soutien au site Web Wulin.com!