Escribir en frente
Esta demostración explica cómo solucionar problemas de una NullPointerException causada por @Transactional.
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-transactional-nullpointerexception
Código para ubicar nullpointerException
La demostración es un simple ejemplo de transacción de primavera, que proporciona el siguiente estudiante y usa @Transactional para declarar la transacción:
@Componente @transaccionalPublic Class StudentDao {@aUtowired private sqlsession sqlsession; Public Student SelectStudentById (ID Long) {return sqlsession.selectone ("selectStudentByid", id); } Public Final Student FinalSectEdStudentById (ID Long) {return sqlsession.selectone ("selectStudentByid", id); }}Una vez que se inicie la aplicación, SelectStudentByID y FinalSectectStudentByid se llamarán a su vez:
@PostConstruct public void init () {studentdao.selectstudentbyid (1); studentdao.finalselectstudentByid (1); }Use MVN Spring-Boot: ejecute o importe el proyecto en el IDE para comenzar. La información de excepción que se realiza es:
Causado por: java.lang.nullpointerException en sample.mybatis.dao.studentdao.finalSelectstudentByid (studentdao.java:27) en com.example.demo.transactional.nullpointerexception.demonullpointerexceptionapplication.init (d. Sun.Reflect.nativemethodaccessorImpl.invoke0 (método nativo) en Sun.reflect.nativemethodacCessorSpl.invoke (nativemethodaccessorImpl.Java:62) en Sun.reflect.delegatingMethodaccessImpl.invoke (delegando a Methodaccessorsimpul.Java:43) java.lang.reflect.method.invoke (método.java:498) en org.springframework.beans.factory.annotation.initdestroyannotationBeanPoscessor $ lifecycycleElement.invoke (initestroyannotationbeanpostPostPostRespross.Java:366) at org.springframework.beans.factory.annotation.initdestroyannotationBeanPostProcessor $ lifecyClemetadata.invokeinitmethods (initDestroyAnnotationBeanPostProcessor.Java:311)
¿Por qué no hay problema en la ejecución de SelectStudentById en el código de aplicación, y la ejecución de Finales ElectionStudentByID lanza una NullPointerException?
En el mismo frijol, se ha inyectado sqlsession sqlsession, y en selectstudentbyid no es nulo. ¿Por qué nulo en FinalSelectstudentByid Function?
Obtenga el nombre de clase del tiempo de ejecución real
Por supuesto, cuando comparamos las dos funciones, podemos saber que se debe a que el modificador de finales selectivos es definitivo. Pero, ¿cuál es la razón específica?
Primero establecemos un punto de interrupción en el lugar donde se lanza la excepción, depuramos el código y obtiene la clase específica en tiempo de ejecución:
System.err.println (StudentDao.getClass ());
El resultado de la impresión es:
clase Sample.mybatis.dao.studentdao $$ mejorado
Se puede ver que es una clase procesada por Spring AOP, pero ¿cuál es su contenido específico de Bytecode?
análisis de copa
Usamos la herramienta DumpClass para volcar la clase en 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
Encuentra el PID del proceso Java:
$ JPS5907 DemonullpointerExceptionApplication
Volcar todas las clases relevantes:
sudo java -jar dumpclass.jar 5907 'sample.mybatis.dao.studentdao*' /tmp /dumpresult
Análisis de desmontaje
Use javap o herramienta gráfica JD-GUI para descomponer la muestra.mybatis.dao.studentdao $$ mejorado
El resultado después de la descomposición es:
Clase StudentDao $$ mejorado
StudentDao$$EnhancerBySpringCGLIB$$210b005d
La llamada real a SelectStudentByid es esta.cglib $ Callback_0, es decir, MethodInterceptor TMP4_1. Veamos en realidad depurarlo más tarde y ver el tipo específico
Public Final Student SelectStudentById (Long ParamLong) {try {MethodInterceptor tmp4_1 = this.cglib $ callback_0; if (tmp4_1 == null) {tmp4_1; Cglib $ bind_callbacks (esto); } Methodinterceptor tmp17_14 = this.cglib $ llamado Callback_0; if (tmp17_14! = null) {objeto [] tmp29_26 = nuevo objeto [1]; Long tmp35_32 = nuevo Java/Lang/Long; Long tmp36_35 = tmp35_32; tmp36_35; TMP36_35. <Init> (paramLong); tmp29_26 [0] = tmp35_32; return (estudiante) tmp17_14.intercept (this, cglib $ selectstudentbyid $ 0 $ $ método, tmp29_26, cglib $ selectstudentbyid $ 0 $ proxy); } return super.selectstudentById (paramLong); } Catch (RuntimeException | Error localRunteMeException) {lanzar localrunteException; } Catch (lanzable localThrowable) {lanzar una nueva no defensa de la concepción (localThrowable); }}Tomemos la depuración real. Aunque el código de Studentdao $$ Mentorbyspringcglib $$ 210b005d no puede verse directamente, aún se puede ejecutar en un solo paso.
Al depurar, puedes ver
1. StudentDao $$ mejorado
2. El tipo real de este.cglib $ llamado Callback_0 es cglibaopproxy $ DynamicAdVisedInterceptor, en el que el objeto de destino original realmente se guarda
3. Después de que CGLIBAOPPROXY $ DynamicAdVised Interceptor es procesado por Transaction Interceptor, eventualmente llamará al objeto de destino original guardado por sí solo con la reflexión.
Causas de excepción de lanzamiento
Así que ordene todo el análisis:
1. Después de usar @Transactional, Spring AOP generará una clase de proxy CGLIB. StudentDao inyectado por @Autowired en el código de usuario real también es una instancia de esta clase proxy.
2. La clase Proxy StudentDao $$ mejorado
3.StudentDao $$ mejorado
4. StudentDao $$ Mentorbyspringcglib $$ 210b005d llama a SelectStudentByid, y de hecho, a través de cglibaopproxy $ DynamicAdvised Interceptor, eventualmente llamará al objeto de destino original guardado por sí solo con reflexión.
5. Por lo tanto, no hay problema al llamar a la función SelectStudentByid
Entonces, ¿por qué la función SQLSession en la función final de la función NULL y luego lanza una NullPointerException?
1.StudentDao $$ mejorado
2. El modificador de la función final de selección final es final, CGLIB no tiene forma de reescribir esta función
3. Cuando se ejecuta en FinalSelectEdentStudentByid, la ejecución real del código en el estudiante original
4. Pero el objeto es una instancia de StudentDao $$ mejorado Todos los campos dentro son nulos, por lo que se lanzará una NullPointerException.
Soluciones al problema
1. Lo más fácil es, por supuesto, eliminar el modificador final de la función final de la función
2. Hay otra forma. No use SQLSession directamente en Studentdao, pero use la función getSqlSession (). De esta manera, CGLIB también procesará getsqlSession () y devolverá el objeto objetivo original
Resumir
1. Solucionar problemas de múltiples problemas de depuración y ver la información del objeto en tiempo de ejecución real
2. Para el código de byting de la clase generada por CGLIB, puede usar la herramienta DumpClass para descargar y luego descomponer y analizar
Resumir
Lo anterior es el estudio en profundidad de la resolución de problemas de arranque de primavera @Transactional. Espero que sea útil para todos. Si tiene alguna pregunta, déjame un mensaje y el editor responderá a todos a tiempo. ¡Muchas gracias por su apoyo al sitio web de Wulin.com!