Tulis di depan
Demo ini menjelaskan cara memecahkan masalah nullpointerException yang disebabkan oleh @transactional.
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-transactional-nullpointerexception
Kode untuk menemukan nullpointerexception
Demo adalah contoh transaksi musim semi sederhana, yang menyediakan siswa berikut dan menggunakan @transactional untuk menyatakan transaksi:
@Component @transactionalpublic class studentdao {@Autowired sqlsession sqlsession sqlsession; Siswa Publik SelectStudentById (Long ID) {return sqlSession.selectone ("SelectStudentById", ID); } public final student finalSelectStudentById (long id) {return sqlsession.selectone ("selectstudentById", id); }}Setelah aplikasi dimulai, SelectStudentByID dan FinalSelectStudentById akan dipanggil pada gilirannya:
@PostConstruct public void init () {studentdao.selectStudentById (1); studentdao.finalselectStudentById (1); }Gunakan MVN Spring-Boot: Jalankan atau impor proyek ke IDE untuk memulai. Informasi pengecualian yang dilemparkan adalah:
Disebabkan oleh: java.lang.nullpointerexception di sample.mybatis.dao.studentdao.finalselectStudentById (studentdao.java:27) di com.example.demo.transactional.nullpointerexception.demonulcepticepcepcepcepcepception.epomplexcepcepticed.epomplePionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpionpion.insepion.insepion sun.reflect.nativeMethodaccessorImpl.invoke0 (metode asli) di sun.reflect.nativeMethodaCessorImpl.invoke (nativeMethoDacCessorMpl.java:62) di sun.reflect.delegatingmethodaCessImpl.invoke (delegatingmethlect. java.lang.reflect.method.invoke (Method.java:498) di org.springframework.beans.factory.annotation.initdestroyannotationBeanPostProcessor $ lifecycleelement.invoke (initdestroyannotationbeanpostprocessorer.invoke (initdestroyannotationbeanpostprocessororer.invoke (InitDestroyAnnotationBeanPostProcessorer.Invoke org.springframework.beans.factory.annotation.initdestroynotationBeanPostProcessor $ lifecyclemetadata.invokeInitmethods (initdestroyannotationBeanPostProcessor.java:311)
Mengapa tidak ada masalah dalam mengeksekusi SelectStudentById dalam kode aplikasi, dan mengeksekusi finalSelectStudentById melempar nullpointerException?
Pada kacang yang sama, sqlsession sqlsession telah disuntikkan, dan di selectstudentbyid itu bukan nol. Mengapa null dalam fungsi finalSelectStudentById?
Dapatkan nama kelas runtime yang sebenarnya
Tentu saja, ketika kita membandingkan dua fungsi, kita dapat mengetahui bahwa itu karena pengubah finalSelectStudentById adalah final. Tapi apa alasan spesifiknya?
Kami pertama -tama menetapkan breakpoint di tempat di mana pengecualian dilemparkan, men -debug kode, dan mendapatkan kelas spesifik saat runtime:
System.err.println (studentdao.getClass ());
Hasil cetak adalah:
class sample.mybatis.dao.studentdao $$ Enhancerbyspringcglib $$ 210B005D
Dapat dilihat bahwa itu adalah kelas yang diproses oleh Spring AOP, tetapi apa konten bytecode spesifiknya?
Analisis DumpClass
Kami menggunakan alat dumpclass untuk membuang kelas di 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 dumpclass.jar
Temukan PID Proses Java:
$ JPS5907 DemonullpointerExceptionApplication
Buang semua kelas yang relevan:
sudo java -jar dumpclass.jar 5907 'sampel.mybatis.dao.studentdao*' /tmp /dumpresult
Analisis pembongkaran
Gunakan JAVAP atau alat grafis JD-GUI untuk mendekomposisi sampel.mybatis.dao.studentdao $$ Enhancerbyspringcglib $$ 210B005D.
Hasilnya setelah dekomposisi adalah:
class studentdao $$ Enhancerbyspringcglib $$ 210B005D Memperluas Studentdao
StudentDao$$EnhancerBySpringCGLIB$$210b005d tidak memiliki konten terkait finalSelectStudentById
Panggilan aktual untuk SelectStudentById adalah this.cglib $ callback_0, yaitu, MethodInterceptor TMP4_1. Mari kita debug nanti dan lihat jenis tertentu
Public Final Student SelectStudentById (Long ParamLong) {coba {MethodInterceptor tmp4_1 = this.cglib $ callback_0; if (tmp4_1 == null) {tmp4_1; Cglib $ bind_callbacks (ini); } MethodInterceptor tmp17_14 = this.cglib $ callback_0; if (tmp17_14! = null) {objek [] tmp29_26 = objek baru [1]; Long tmp35_32 = Java/lang/long baru; Long tmp36_35 = tmp35_32; TMP36_35; TMP36_35. <Ilin> (paramlong); tmp29_26 [0] = tmp35_32; Return (Student) TMP17_14.Intercept (ini, cglib $ selectStudentById $ 0 $ Metode, TMP29_26, CGLIB $ selectStudentById $ 0 $ proxy); } return super.selectStudentById (paramlong); } catch (runtimeException | error localruntiMeException) {lempar localruntimeException; } Catch (Throwable LocalThrowable) {lempar New UndeclaredthrowableException (Localthrowable); }}Mari kita ambil debugging yang sebenarnya. Meskipun kode Studentdao $$ EnhancerbySpringcglib $$ 210B005D tidak dapat dilihat secara langsung, itu masih dapat dieksekusi dalam satu langkah.
Saat debugging, Anda bisa melihat
1. Studentdao $$ Enhancerbyspringcglib $$ 210B005D adalah nol
2. Jenis aktual ini.
3. Setelah CGlibaopproxy $ DynamicAdvisedInterceptor diproses oleh TransactionInterceptor, pada akhirnya akan memanggil objek target asli yang disimpan dengan sendirinya dengan refleksi.
Penyebab Pengecualian
Jadi urutkan seluruh analisis:
1. Setelah menggunakan @transactional, Spring AOP akan menghasilkan kelas proxy CGLIB. Studentdao disuntikkan oleh @Autowired dalam kode pengguna yang sebenarnya juga merupakan instance dari kelas proxy ini.
2. Kelas proxy Studentdao $$ Enhancerbyspringcglib $$ 210B005D yang dihasilkan oleh CGLIB diwarisi dari Studentdao
3.Studentdao $$ Enhancerbyspringcglib $$ 210B005D adalah nol
4. Studentdao $$ Enhancerbyspringcglib $$ 210B005D menelepon selectStudentById, dan pada kenyataannya, melalui cglibaopproxy $ DynamicAdvisedInterceptor, pada akhirnya akan memanggil objek target asli yang disimpan dengan sendirinya dengan refleksi.
5. Jadi tidak ada masalah dalam memanggil fungsi SelectStudentById
Jadi mengapa fungsi SQLSESSI pada finalSelectStudentById null dan kemudian melempar NullpointerException?
1.Studentdao $$ Enhancerbyspringcglib $$ 210B005D adalah nol
2. Pengubah fungsi finalSelectStudentById adalah final, CGLIB tidak memiliki cara untuk menulis ulang fungsi ini
3. Saat dieksekusi di finalSelectStudentById, eksekusi kode yang sebenarnya di Studentdao asli
4. Tetapi objek adalah contoh dari Studentdao $$ Enhancerbyspringcglib $$ 210B005D. Semua bidang di dalamnya adalah nol, jadi nullpointerException akan dilemparkan.
Solusi untuk Masalahnya
1. Hal termudah tentu saja untuk menghapus pengubah akhir dari fungsi finalSelectStudentById
2. Ada cara lain. Jangan gunakan SQLSession secara langsung di Studentdao, tetapi gunakan fungsi getSQLSession (). Dengan cara ini, CGLIB juga akan memproses getSQLSession () dan mengembalikan objek target asli
Meringkaskan
1. Memecahkan masalah beberapa masalah debugging dan melihat informasi objek dalam runtime yang sebenarnya
2. Untuk bytecode kelas yang dihasilkan CGLIB, Anda dapat menggunakan alat dumpclass untuk dibuang dan kemudian terurai dan menganalisis
Meringkaskan
Di atas adalah studi mendalam tentang pemecahan masalah boot musim semi @transactional. Saya harap ini akan membantu semua orang. Jika Anda memiliki pertanyaan, silakan tinggalkan saya pesan dan editor akan membalas semua orang tepat waktu. Terima kasih banyak atas dukungan Anda ke situs web Wulin.com!