앞에 쓰십시오
이 데모는 @transactional으로 인한 NullPointerException을 문제 해결하는 방법을 설명합니다.
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-transactional-nullpointerexception
NullPointerException을 찾는 코드
데모는 간단한 스프링 트랜잭션 예제로 다음 학생의 학생들을 제공하고 @transactional을 사용하여 거래를 선언합니다.
@component @transactionalPublic 클래스 StudentDao {@autowired private sqlsession sqlsession; 공개 학생 SelectStudentByid (Long Id) {return sqlsession.selectone ( "selectStudentById", id); } 공개 최종 학생 FinalSelectStudEntById (Long ID) {return sqlsession.selectone ( "selectStudentById", id); }}응용 프로그램이 시작되면 SelectStudentByid 및 FinalSelectStudentById가 다음에 호출됩니다.
@PostConstruct public void init () {windentDao.SelectStudentById (1); StudentDao.FinalSelectStudentById (1); }MVN 스프링 부츠 사용 : 프로젝트를 IDE로 실행하거나 가져와 시작하십시오. 예외 정보는 다음과 같습니다.
샘플에서 java.lang.nullpointerexception에서 java.lang.nullpointerexception. mybatis.dao.studentdao.finalselectstudentByid (windentdao.java:27)의 com.example.demo.transactional.nullpointerexception.demonullPointerexceplation.init (demonullPoinceplication.30) sun.reflect.nativeMethodaccessorimpl.invoke0 (기본 메소드)에서 sun.reflect.nativeMethodaccessorimpl.invoke (nativeMeThodAccessorImpl.java:62)에서 sun.reflect.delegatingMethodaccessorimpl.invoke (at at at at at at atmethorimpl.4) java.lang.reflect.method.invoke (method.java:498) at org.springframework.beans.annotation.initdestroyannotationbeanpostprocessor $ lifecycleelement.invoke (initdestroyannotationbeanpostprocessor.java:366) at org.springframework.beans.beans.factory.annotation.initdestroyannotationbeanpostprocessor $ lifecyclemetadata.invokeinitmethods (initdestroyannotationbeanpostprocessor.java:311).
애플리케이션 코드에서 SelectStudentById를 실행하는 데 문제가없는 이유는 무엇입니까? FinalSElectStudentById를 실행하면 NullPointerException이 발생합니까?
동일한 Bean에서 SQLSESSION SQLSESSION이 주입되었으며 SelectStudentById에서는 NULL입니다. NULL이 FinalSectStudentById 기능의 이유는 무엇입니까?
실제 런타임의 클래스 이름을 얻으십시오
물론 두 기능을 비교할 때 FinalsElectStudentById의 수정자가 최종적이기 때문이라는 것을 알 수 있습니다. 그러나 구체적인 이유는 무엇입니까?
먼저 예외가 발생하는 장소에서 중단 점을 설정하고 코드를 디버깅하고 런타임에 특정 클래스를 가져옵니다.
System.err.println (windentDao.getClass ());
인쇄 결과는 다음과 같습니다.
클래스 샘플 .mybatis.dao.studentDao $$ EnhancerByspringCglib $$ 210B005D
Spring AOP가 처리 한 클래스라는 것을 알 수 있지만 특정 바이트 코드 컨텐츠는 무엇입니까?
덤프 클래스 분석
덤프 클래스 도구를 사용하여 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를 사용하여 sample.mybatis.dao.studentDao $$ EnhancerByspringCglib $$ 210B005D를 분해하십시오.
분해 후 결과는 다음과 같습니다.
클래스 학생 Dao $$ EnhancerByspringCglib $$ 210B005d StudentDao를 확장합니다
StudentDao$$EnhancerBySpringCGLIB$$210b005d FinalSelectStudentById 관련 콘텐츠가 없습니다
SelectStudentByid에 대한 실제 호출은 this.cglib $ Callback_0, 즉 메소드 인라인 수용기 TMP4_1입니다. 실제로 나중에 디버깅하고 특정 유형을 보자.
공개 최종 학생 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) {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 (this, cglib $ selectStudentByid $ 0 $ method, tmp29_26, cglib $ selectStudentByid $ 0 $ proxy); } return super.selectStudentById (paramlong); } catch (runtimeexception | error localRuntImeexception) {localRuntImeexception 던지기; } catch (Throwable LocalThrowable) {새로운 미등성이없는 출시 (LocalThrowable); }}실제 디버깅을하겠습니다. StudentDao $$ EnhancerByspringCglib $$ 210B005D의 코드는 직접 볼 수는 없지만 여전히 단일 단계에서 실행할 수 있습니다.
디버깅 할 때 볼 수 있습니다
1. StudentDao $$ EnhancerByspringCglib $$ 210B005D는 null입니다
2. this.cglib $ callback_0의 실제 유형은 cglibaopproxy $ dynamicAdvisedinterceptor이며 원래 대상 객체가 실제로 저장된 것입니다.
3. CGLIBAOPPROXY $ DYNAMICADVISEDINterceptor가 TransactionInterinterceptor에 의해 처리 된 후 결국 반사와 함께 저장된 원래 대상 객체를 호출합니다.
던지기 예외의 원인
따라서 전체 분석을 정렬하십시오.
1. @transactional을 사용한 후 Spring AOP는 CGLIB 프록시 클래스를 생성합니다. 실제 사용자 코드에 @autowired가 주입 한 StudentDao는이 프록시 클래스의 인스턴스입니다.
2. 프록시 클래스 StudentDao $$ EnhancerByspringCglib $$ 210B005D CGLIB에 의해 생성 된 학생들은 StudentDao에서 상속됩니다.
3. StudentDao $$ EnhancerByspringCglib $$ 210B005D는 NULL입니다
4. StudentDao $$ EnhancerByspringCglib $$ 210B005D는 SelectStudentById를 호출하고 있으며 실제로 CGLIBAOPPROXY $ DynamicAdViSeInterceptor를 통해 결국 반사와 함께 저장된 원래 대상 객체를 호출합니다.
5. 따라서 selectStudentByID 함수를 호출하는 데 아무런 문제가 없습니다.
그렇다면 FinalsElectStudentByID의 SQLSession이 NULL을 작동시키고 NullPointerException을 던지는 이유는 무엇입니까?
1. StudentDao $$ EnhancerByspringCglib $$ 210B005D는 NULL입니다
2. FinalsElectStudentById 함수의 수정자는 최종적이며 CGLIB는이 기능을 다시 작성할 방법이 없습니다.
3. FinalsElectStudentById에서 실행될 때 원래 StudentDao에서 코드의 실제 실행
4. 그러나 대상은 StudentDao $$ EnhancerByspringCglib $$ 210B005D의 인스턴스입니다. 내부의 모든 필드는 Null이므로 NullPointerException이 발생합니다.
문제에 대한 해결책
1. 가장 쉬운 것은 물론 FinalSelectStudentById 함수의 최종 수정자를 제거하는 것입니다.
2. 다른 방법이 있습니다. StudentDao에서 직접 sqlsession을 사용하지 말고 getSqlsession () 함수를 사용하십시오. 이러한 방식으로 CGLIB는 GetSQlSession ()을 처리하고 원래 대상 객체를 반환합니다.
요약
1. 여러 디버깅 문제를 해결하고 실제 런타임의 객체 정보를 확인하십시오.
2. CGLIB 생성 클래스의 바이트 코드의 경우 덤프 클래스 도구를 사용하여 덤프 한 다음 분해 및 분석 할 수 있습니다.
요약
위의 것은 @transactional 문제 해결에 대한 심층적 인 연구입니다. 모든 사람에게 도움이되기를 바랍니다. 궁금한 점이 있으면 메시지를 남겨 주시면 편집자가 제 시간에 모든 사람에게 답장을 드리겠습니다. Wulin.com 웹 사이트를 지원해 주셔서 대단히 감사합니다!