Escreva na frente
Esta demonstração explica como solucionar problemas de uma NullPointerException causada por @Transaction.
https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-transaction-nullpointerException
Código para localizar NullPointerException
A demonstração é um exemplo simples de transação de primavera, que fornece o seguinte Studentdao e usa @Transaction para declarar a transação:
@Componente @transactionalpublic class StudentDao {@AUTOWIRED SQLSession SQLSession; public Student SelectStudentById (Long Id) {return sqlSession.SelectOne ("SelectStudentById", ID); } public final Student FinalSelectStudentById (Long Id) {return sqlSession.SelectOne ("SelectStudentById", ID); }}Após o início do aplicativo, o SelectStudentById e o FinalSelectStudentById serão chamados por sua vez:
@PostConstruct public void init () {StudentDao.SelectStudentById (1); Studentdao.FinalSelectStudentById (1); }Use MVN Spring-Boot: Execute ou importe o projeto para o IDE para iniciar. As informações de exceção lançadas é:
Causado por: java.lang.nullPointerException em sample.mybatis.dao.studentdao.FinalSelectStudentById (studentdao.java:27) em com complimpIpsepPlication.Demo.Transaction.NullPointerException.DemonullPoTexceptImExtion.Demo.Demo.Transaction.NULLPoiToiCception.DemonullPonsceptAppLication.DemoMoMoMoMoMo.Transaction.NullPoiToiMception. sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.method.invoke (method.java:498) em org.springframework.beans.factory.annotation.initdestroyannotationBeanPostProcessor $ lifeCycleElement.invoke (initestroyannotationBeanPostProcessor.java org.springframework.beans.factory.annotation.initdestroyannotationBeanPostProcessor $ LIFECYCLEMETADATA.invokeinitMethods (initDestroyannotationBeanPostprocessor.java:311)
Por que não há problema em executar o SelectStudentById no código do aplicativo e a execução do FinalsElectStudentById lança uma NullPointerException?
No mesmo feijão, o SQLSession SQLSession foi injetado e, no seleto, não é nulo. Por que o NULL na função FinalSelectStudentById?
Obtenha o nome da classe do tempo de execução real
Obviamente, quando comparamos as duas funções, podemos saber que é porque o modificador do FinalSelectStudentById é final. Mas qual é o motivo específico?
Primeiro estabelecemos um ponto de interrupção no local onde a exceção é lançada, depra o código e obtemos a classe específica em tempo de execução:
System.err.println (StudentDao.getClass ());
O resultado da impressão é:
Classe Sample.mybatis.dao.studentdao $$ ENIDANCERBYSPRINGCGLIB $$ 210B005D
Pode -se observar que é uma classe processada pela Spring AOP, mas qual é o seu conteúdo específico de bytecode?
Análise de Dumpclass
Usamos a ferramenta DumpClass para despejar a classe na 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
Encontre o processo Java PID:
$ JPS5907 DemonullPointerExceptionApplication
Despeje todas as classes relevantes:
sudo java -jar dumpclass.jar 5907 'sample.mybatis.dao.studentdao*' /tmp /dumpresult
Análise de desmontagem
Use JAVAP ou ferramenta gráfica JD-gui para decompor o sample.mybatis.dao.studentdao $$ ENIDANCERBYSPRINGCGLIB $$ 210B005D.
O resultado após a decomposição é:
Classe StudentDao $$ ENFERMEBYSPRINGCGLIB $$ 210B005D Estende Studentdao
StudentDao$$EnhancerBySpringCGLIB$$210b005d não possui FinalsElectStudentById Conteúdo relacionado
A chamada real para selecionarstudentById é this.cglib $ callback_0, ou seja, MethodInterceptor TMP4_1. Vamos realmente depurar mais tarde e ver o tipo específico
public final Student SelectStudentById (Long paramlong) {try {MethodIntercept TMP4_1 = this.cglib $ callback_0; if (tmp4_1 == null) {tmp4_1; Cglib $ bind_callbacks (this); } MethodIntercept TMP17_14 = this.cglib $ callback_0; if (tmp17_14! = null) {objeto [] tmp29_26 = novo objeto [1]; Tmp35_32 longo = novo java/lang/long; Tmp36_35 longo = tmp35_32; tmp36_35; tmp36_35. <ingem> (paramlong); tmp29_26 [0] = tmp35_32; return (aluno) tmp17_14.Intercept (this, cglib $ selectStudentById $ 0 $ Método, tmp29_26, cglib $ selectStudentById $ 0 $ proxy); } retornar super.selectStudentById (paramlong); } Catch (RunTimeException | Erro LocalRuntimeException) {Throw LocalRuntimeException; } catch (localwlowable) {lança uma nova não declaração de abastecimento (local de trabalho); }}Vamos levar a depuração real. Embora o código do Studentdao $$ ENFERBYSPRINGCGLIB $$ 210B005D não possa ser visto diretamente, ele ainda pode ser executado em uma única etapa.
Ao depurar, você pode ver
1. StudentDao $$ ENIFERBYSPRINGCGLIB $$ 210B005D É NULL
As
3. Após o cglibaopProxy $ dynamicVisedInterceptor é processado pelo transactionInterceptor, ele acabará chamando de objeto de destino original salvo por si mesmo com reflexão.
Causas de exceção de arremesso
Então, resolva toda a análise:
1. Depois de usar @Transaction, a Spring AOP gerará uma classe de procuração do CGLIB. StudentDao injetado por @Autowired no código de usuário real também é uma instância desta classe proxy.
2. A classe proxy Studentdao $$ ENFERBYSPRINGCGLIB $$ 210B005D Gerada pelo CGLIB é herdada de Studentdao
3.STUDENTDAO $$ ENFERBYSPRINGCGLIB $$ 210B005D É NULL
4. StudentDao $$ ENFERBYSPRINGCGLIB $$ 210B005D está chamando o SelectStudentById e, de fato, através do CGLIBAOPPROXIA $ DynamicVedInterceptor, ele acabará chamando o objeto de destino original salvo por si mesmo com reflexão.
5. Portanto, não há problema em chamar a função SelectStudentById
Então, por que a SQLSession na função FinalSelectStudentbyid NULL e depois joga uma NullPointerException?
1.StudentDao $$ ENFERBYSPRINGCGLIB $$ 210B005D É NULL
2. O modificador da função FinalsElectStudentById é final, o CGLIB não tem como reescrever esta função
3. Quando executado em FinalsElectStudentById, a execução real do código no Studentdao original
4. Mas o objeto é uma instância de Studentdao $$ ENFERBYSPRINGCGLIB $$ 210B005D. Todos os campos dentro dele são nulos, então uma NullPointerException será lançada.
Soluções para o problema
1. O mais fácil é, obviamente, remover o modificador final da função final do final
2. Há outra maneira. Não use o SQLSession diretamente no Studentdao, mas use a função getSQLSession (). Dessa forma, o CGLIB também processará getSqlSession () e retornará o objeto de destino original
Resumir
1. Solucione problemas de depuração vários problemas e veja as informações do objeto em tempo de execução real
2. Para o bytecode da classe gerada pelo CGLIB, você pode usar a ferramenta DumpClass para despejar e depois decompor e analisar
Resumir
O exposto acima é o estudo aprofundado da solução de problemas de inicialização da primavera @Transactional. Espero que seja útil para todos. Se você tiver alguma dúvida, deixe -me uma mensagem e o editor responderá a todos a tempo. Muito obrigado pelo seu apoio ao site wulin.com!