เขียนด้านหน้า
การสาธิตนี้อธิบายวิธีแก้ไขปัญหา nullpointerexception ที่เกิดจาก @Transactional
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-transactional-nullpointerexception
รหัสเพื่อค้นหา nullpointerexception
การสาธิตเป็นตัวอย่างการทำธุรกรรมฤดูใบไม้ผลิอย่างง่ายซึ่งให้นักเรียนต่อไปนี้และใช้ @Transactional เพื่อประกาศธุรกรรม:
@component @transactionalpublic studentdao {@autowired ส่วนตัว sqlsession sqlsession; นักศึกษาสาธารณะ SelectSudentById (Long ID) {return sqlsession.selectone ("SelectStudentById", ID); } สาธารณะนักเรียนสุดท้าย FinalsElectStudentById (Long ID) {return sqlsession.selectone ("SelectStudentById", ID); -หลังจากแอปพลิเคชันเริ่มต้นแล้ว SelectStudentById และ FinalSelectStudentById จะถูกเรียกในทางกลับกัน:
@PostConstruct โมฆะสาธารณะ init () {StudentDao.SELECTSTUDENTBYID (1); StudentDao.FinalSelectStudentById (1); -ใช้ MVN Spring-Boot: เรียกใช้หรือนำเข้าโครงการไปยัง IDE เพื่อเริ่มต้น ข้อมูลข้อยกเว้นที่โยนคือ:
เกิดจาก: java.lang.nullpointerexception ที่ sample.mybatis.dao.studentdao.finalselectStudentbyid (studentdao.java:27) ที่ com.example.demo.transactional.nullpointerexception.demonultpoptication. sun.reflect.nativemethodaccessorimpl.invoke0 (วิธีการดั้งเดิม) ที่ sun.reflect.nativemethodaccessorimpl.invoke (nativemethodaccessorimpl.java:62) ที่ sun.reflect.delegatingmethodaccessorimpl.invoke java.lang.reflect.method.invoke (method.java:498) ที่ org.springframework.beans.factory.annotation.initdestroyannotationbeanpostprocessor $ lifecylement.invoke org.springframework.beans.factory.annotation.initDestroyannotationBeanPostProcessor $ Lifecyclemetadata.invokeInitMethods (InitDestroyannotationBeanPostProcessor.java:311)
เหตุใดจึงไม่มีปัญหาในการดำเนินการ SelectSudentById ในรหัสแอปพลิเคชันและการดำเนินการ FinalSelectStudentById โยน nullpointerexception
ในถั่วเดียวกัน sqlsession sqlsession ได้รับการฉีดและใน SelectSudentById มันไม่ใช่ Null ทำไม null ใน finalselectstudentbyid ฟังก์ชัน?
รับชื่อคลาสของรันไทม์จริง
แน่นอนเมื่อเราเปรียบเทียบฟังก์ชั่นทั้งสองเราสามารถรู้ได้ว่าเป็นเพราะตัวดัดแปลงของ FinalsElectStudentById ถือเป็นที่สิ้นสุด แต่เหตุผลเฉพาะคืออะไร?
ก่อนอื่นเราตั้งค่าเบรกพอยต์ในสถานที่ที่มีการโยนข้อยกเว้นแก้ไขข้อบกพร่องของรหัสและรับคลาสเฉพาะที่รันไทม์:
System.err.println (StudentDao.getClass ());
ผลการพิมพ์คือ:
ตัวอย่างคลาส.
จะเห็นได้ว่ามันเป็นคลาสที่ประมวลผลโดย Spring AOP แต่เนื้อหาไบต์เฉพาะเฉพาะคืออะไร?
การวิเคราะห์ dumpclass
เราใช้เครื่องมือ dumpclass ในการทิ้งคลาสใน 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
ค้นหากระบวนการ Java PID:
$ jps5907 demonullpointerexceptionapplication
ทิ้งคลาสที่เกี่ยวข้องทั้งหมด:
sudo java -jar dumpclass.jar 5907 'sample.mybatis.dao.studentdao*' /tmp /dumpresult
การวิเคราะห์การถอดประกอบ
ใช้ Javap หรือเครื่องมือกราฟิก JD-Gui เพื่อย่อยสลายตัวอย่าง MyBatis.dao.studentDao $$ EnhancerByspringCglib $$ 210B005D
ผลลัพธ์หลังจากการสลายตัวคือ:
ชั้นเรียน studentdao $$ enhancerbyspringcglib $$ 210b005d ขยาย studentdao
StudentDao$$EnhancerBySpringCGLIB$$210b005d ไม่มีเนื้อหาที่เกี่ยวข้องกับ FinalSelectStudentById
การโทรไปยัง SelectSudentById จริงคือ this.cglib $ callback_0 นั่นคือ MethodInterceptor TMP4_1 มาดีบั๊กจริง ๆ ในภายหลังและดูประเภทเฉพาะ
Public Public Student SelectDentById (Long Paramlong) {ลอง {MethodInterceptor tmp4_1 = this.cglib $ callback_0; if (tmp4_1 == null) {tmp4_1; cglib $ bind_callbacks (นี่); } MethodInterceptor tmp17_14 = this.cglib $ callback_0; if (tmp17_14! = null) {object [] tmp29_26 = วัตถุใหม่ [1]; TMP35_32 ยาว = ใหม่ Java/Lang/Long; TMP36_35 ยาว = TMP35_32; TMP36_35; tmp36_35. <init> (paramlong); TMP29_26 [0] = TMP35_32; return (นักเรียน) tmp17_14.intercept (นี่, cglib $ selectStudentById $ 0 $ วิธี, tmp29_26, cglib $ selectStudentById $ 0 $ proxy); } return super.selectstudentbyId (paramlong); } catch (runtimeException | ข้อผิดพลาด localruntimeException) {โยน localruntimeException; } catch (localthrowable localable) {โยน undeclaredthrowableexception ใหม่ (localthrowable); -มาใช้การดีบักจริง แม้ว่ารหัสของ StudentDao $$ EnhancerByspringCglib $ 210B005D ไม่สามารถมองเห็นได้โดยตรง แต่ก็ยังสามารถดำเนินการได้ในขั้นตอนเดียว
เมื่อทำการดีบักคุณจะเห็น
1. Studentdao $$ enhancerbyspringcglib $$ 210b005d เป็น null
2. ประเภทที่แท้จริงของสิ่งนี้ cglib $ callback_0 คือ cglibaopproxy $ dynamicadvisedInterceptor ซึ่งวัตถุเป้าหมายดั้งเดิมถูกบันทึกจริง
3. หลังจาก CGLIBAOPPROXY $ DynamicAdvisedInterceptor ถูกประมวลผลโดย TransactionInterceptor ในที่สุดมันจะเรียกวัตถุเป้าหมายดั้งเดิมที่บันทึกไว้ด้วยตัวเองด้วยการสะท้อน
สาเหตุของการโยนข้อยกเว้น
ดังนั้นจัดเรียงการวิเคราะห์ทั้งหมด:
1. หลังจากใช้ @Transactional สปริง AOP จะสร้างคลาสพร็อกซี CGLIB StudentDao ฉีดโดย @autowired ในรหัสผู้ใช้จริงก็เป็นอินสแตนซ์ของคลาสพร็อกซีนี้
2. นักศึกษาระดับพร็อกซีระดับพร็อกซี $$ enhancerbyspringcglib $ 210b005d สร้างโดย CGLIB นั้นสืบทอดมาจาก StudentDao
3. StudentDao $$ enhancerbyspringcglib $$ 210b005d เป็น null
4. StudentDao $$ EnhancerByspringCglib $ 210B005D กำลังเรียก SelectSudentById และในความเป็นจริงผ่าน cgLibaopproxy $ DynamicAdvisedInterceptor ในที่สุดมันจะเรียกวัตถุเป้าหมายดั้งเดิมที่บันทึกไว้ด้วยการสะท้อนกลับ
5. ดังนั้นจึงไม่มีปัญหาในการเรียกใช้ฟังก์ชัน SelectSudentById
เหตุใด SQLSession ในฟังก์ชั่น FinalSelectStudentById NULL จึงโยน nullpointerexception?
1.studentdao $$ enhancerbyspringcglib $$ 210b005d เป็น null
2. ตัวดัดแปลงของฟังก์ชัน FinalSelectStudentById เป็นที่สิ้นสุด CGLIB ไม่มีทางที่จะเขียนฟังก์ชั่นนี้ใหม่
3. เมื่อดำเนินการใน FinalSelectStudentById การดำเนินการจริงของรหัสใน studentdao ดั้งเดิม
4. แต่วัตถุนั้นเป็นตัวอย่างของ StudentDao $$ EnhancerByspringCglib $ 210B005D ฟิลด์ทั้งหมดภายในนั้นเป็นโมฆะดังนั้น Nullpointerexception จะถูกโยนทิ้ง
วิธีแก้ปัญหา
1. สิ่งที่ง่ายที่สุดคือการลบตัวดัดแปลงสุดท้ายของฟังก์ชัน FinalSelectStudentById
2. มีอีกวิธีหนึ่ง อย่าใช้ SQLSession โดยตรงใน StudentDao แต่ใช้ฟังก์ชั่น GetSQlSession () ด้วยวิธีนี้ CGLIB จะดำเนินการ GetSqlSession () และส่งคืนวัตถุเป้าหมายเดิม
สรุป
1. แก้ไขปัญหาการแก้ไขข้อบกพร่องหลายข้อและดูข้อมูลวัตถุในรันไทม์จริง
2. สำหรับไบต์ของคลาส CGLIB ที่สร้างขึ้นคุณสามารถใช้เครื่องมือ dumpclass ในการทิ้งแล้วย่อยสลายและวิเคราะห์
สรุป
ข้างต้นคือการศึกษาเชิงลึกของการแก้ไขปัญหาการบูตฤดูใบไม้ผลิ @Transactional ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน หากคุณมีคำถามใด ๆ โปรดฝากข้อความถึงฉันและบรรณาธิการจะตอบกลับทุกคนในเวลา ขอบคุณมากสำหรับการสนับสนุนเว็บไซต์ Wulin.com!