欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringMVC空指針異常NullPointerException解決及原理解析

 更新時間:2023年08月10日 11:48:37   作者:LYX6666  
這篇文章主要介紹了SpringMVC空指針異常NullPointerException解決及原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

在寫單元測試的過程中,出現(xiàn)過許多次java.lang.NullPointerException,而這些空指針的錯誤又是不同原因造成的,本文從實際代碼出發(fā),研究一下空指針的產(chǎn)生原因。

一句話概括:空指針異常,是在程序在調(diào)用某個對象的某個方法時,由于該對象為null產(chǎn)生的。

所以如果出現(xiàn)此異常,大多數(shù)情況要判斷測試中的對象是否被成功的注入,以及Mock方法是否生效

基礎

出現(xiàn)空指針異常的錯誤信息如下:

java.lang.NullPointerException
    at club.yunzhi.workhome.service.WorkServiceImpl.updateOfCurrentStudent(WorkServiceImpl.java:178)
    at club.yunzhi.workhome.service.WorkServiceImplTest.updateOfCurrentStudent(WorkServiceImplTest.java:137)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

這實際上是方法棧,就是在WorkServiceImplTest.java測試類的137行調(diào)用WorkServiceImpl.java被測試類的178行出現(xiàn)問題。

下面從兩個實例來具體分析。

實例

(代碼僅為了報錯時方便分析,請勿仔細閱讀,避免浪費時間)

目的:測試服務層的一個用于更新作業(yè)的功能。

接口

/**
     * 更新作業(yè)分數(shù)
     * @param id
     * @param score
     * @return 
     */
    Work updateScore(Long id, int score);

接口實現(xiàn):

@Service
public class WorkServiceImpl implements WorkService {
    private static final Logger logger = LoggerFactory.getLogger(WorkServiceImpl.class);
    private static final String WORK_PATH = "work/";
    final WorkRepository workRepository;
    final StudentService studentService;
    final UserService userService;
    final ItemRepository itemRepository;
    final AttachmentService attachmentService;
    public WorkServiceImpl(WorkRepository workRepository, StudentService studentService, UserService userService, ItemRepository itemRepository, AttachmentService attachmentService) {
        this.workRepository = workRepository;
        this.studentService = studentService;
        this.userService = userService;
        this.itemRepository = itemRepository;
        this.attachmentService = attachmentService;
    }
    ...
    @Override
    public Work updateScore(Long id, int score) {
        Work work = this.workRepository.findById(id)
                .orElseThrow(() -> new ObjectNotFoundException("未找到ID為" + id + "的作業(yè)"));
        if (!this.isTeacher()) {
            throw new AccessDeniedException("無權判定作業(yè)");
        }
        work.setScore(score);
        logger.info(String.valueOf(work.getScore()));
        return this.save(work);
    }
    @Override
    public boolean isTeacher() {
        User user = this.userService.getCurrentLoginUser();
 130    if (user.getRole() == 1) {
            return false;
        }
        return true;
    }

測試:

@Test
    public void updateScore() {
        Long id = this.random.nextLong();
        Work oldWork = new Work();
        oldWork.setStudent(this.currentStudent);
        oldWork.setItem(Mockito.spy(new Item()));
        int score = 100;

        Mockito.when(this.workRepository.findById(Mockito.eq(id)))
                .thenReturn(Optional.of(oldWork));

        Mockito.doReturn(true)
                .when(oldWork.getItem())
                .getActive();

        Work work = new Work();
        work.setScore(score);


        Work resultWork = new Work();
        Mockito.when(this.workRepository.save(Mockito.eq(oldWork)))
                .thenReturn(resultWork);

 203    Assertions.assertEquals(resultWork, this.workService.updateScore(id, score));
        Assertions.assertEquals(oldWork.getScore(), work.getScore());

    }

運行測試,出現(xiàn)空指針:java.lang.NullPointerException

at club.yunzhi.workhome.service.WorkServiceImpl.isTeacher(WorkServiceImpl.java:130)
at club.yunzhi.workhome.service.WorkServiceImplTest.updateScore(WorkServiceImplTest.java:203)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

問題出在功能代碼的第130行,可以看到報錯的代碼根本不是要測試的方法,而是被調(diào)用的方法。
再看測試代碼的203行,測試時的本來目的是為了Mock掉這個方法,但使用的是when().thenReturn方式。

對于Mock對象(完全假的對象),使用when().thenReturndoReturn().when的效果是一樣的,都可以制造一個假的返回值。

但是對于Spy對象(半真半假的對象)就不一樣了,when().thenReturn會去執(zhí)行真正的方法,再返回假的返回值,在這個執(zhí)行真正方法的過程中,就可能出現(xiàn)空指針錯誤。

doReturn().when會直接返回假的數(shù)據(jù),而根本不執(zhí)行真正的方法。

參考鏈接:https://sangsoonam.github.io/...

所以把測試代碼的改成:

-    Mockito.when(this.workService.isTeacher()).thenReturn(true);
   +    Mockito.doReturn(true).when(workService).isTeacher();

再次運行,就能通過測試。

目的:還是測試之前的方法,只不過新增了功能。

接口

/**
     * 更新作業(yè)分數(shù)
     * @param id
     * @param score
     * @return 
     */
    Work updateScore(Long id, int score);

接口實現(xiàn)(在原有的儲存學生成績方法上新增了計算總分的功能)

@Override
    public Work updateScore(Long id, int score) {
        Work work = this.workRepository.findById(id)
                .orElseThrow(() -> new ObjectNotFoundException("未找到ID為" + id + "的作業(yè)"));
        if (!this.isTeacher()) {
            throw new AccessDeniedException("無權判定作業(yè)");
        }
        work.setScore(score);
        work.setReviewed(true);
        logger.info(String.valueOf(work.getScore()));
   +    //取出此學生的所有作業(yè)
   +    List<Work> currentStudentWorks = this.workRepository.findAllByStudent(work.getStudent());
   +    //取出此學生
   +    Student currentStudent = this.studentService.findById(work.getStudent().getId());
   +    currentStudent.setTotalScore(0);
   +    int viewed = 0;
   +
   +    for (Work awork : currentStudentWorks) {
   +        if (awork.getReviewed() == true) {
   +            viewed++;
   +            //計算總成績
   +            currentStudent.setTotalScore(currentStudent.getTotalScore()+awork.getScore());
   +            //計算平均成績
   +            currentStudent.setAverageScore(currentStudent.getTotalScore()/viewed);
   +        }
   +    }
   +
   +    studentRepository.save(currentStudent);
        return this.save(work);
    }

由于出現(xiàn)了對學生倉庫studentRepository的調(diào)用,需要注入:

final WorkRepository workRepository;
    final StudentService studentService;
    final UserService userService;
    final ItemRepository itemRepository;
    final AttachmentService attachmentService;
   +final StudentRepository studentRepository;

   -public WorkServiceImpl(WorkRepository workRepository, StudentService studentService, UserService userService, ItemRepository itemRepository, AttachmentService attachmentService) {
   +public WorkServiceImpl(WorkRepository workRepository, StudentService studentService, UserService userService, ItemRepository itemRepository, AttachmentService attachmentService, StudentRepository studentRepository) {
        this.workRepository = workRepository;
        this.studentService = studentService;
        this.userService = userService;
        this.itemRepository = itemRepository;
        this.attachmentService = attachmentService;
   +    this.studentRepository = studentRepository;
    }

然后是測試代碼

class WorkServiceImplTest extends ServiceTest {
    private static final Logger logger = LoggerFactory.getLogger(WorkServiceImplTest.class);
    WorkRepository workRepository;
    UserService userService;
    ItemRepository itemRepository;
    ItemService itemService;
    WorkServiceImpl workService;
    AttachmentService attachmentService;
   +StudentService studentService;
   +StudentRepository studentRepository;
    @Autowired
    private ResourceLoader loader;
    @BeforeEach
    public void beforeEach() {
        super.beforeEach();
        this.itemService = Mockito.mock(ItemService.class);
        this.workRepository = Mockito.mock(WorkRepository.class);
        this.userService = Mockito.mock(UserService.class);
        this.itemRepository = Mockito.mock(ItemRepository.class);
        this.studentService = Mockito.mock(StudentService.class);
        this.studentRepository = Mockito.mock(StudentRepository.class);
        this.workService = Mockito.spy(new WorkServiceImpl(this.workRepository, this.studentService,
   +            this.userService, this.itemRepository, this.attachmentService, this.studentRepository));
    }
    ...
    @Test
    public void updateScore() {
        Long id = this.random.nextLong();
        Work oldWork = new Work();
        oldWork.setScore(0);
        oldWork.setStudent(this.currentStudent);
        oldWork.setItem(Mockito.spy(new Item()));
   +    Work testWork = new Work();
   +    testWork.setScore(0);
   +    testWork.setReviewed(true);
   +    testWork.setStudent(this.currentStudent);
   +    testWork.setItem(Mockito.spy(new Item()));
        int score = 100;
   +    List<Work> works= Arrays.asList(oldWork, testWork);
   +
   +    Mockito.doReturn(Optional.of(oldWork))
   +            .when(this.workRepository)
   +            .findById(Mockito.eq(id));
   +    Mockito.doReturn(works)
   +            .when(this.workRepository)
   +            .findAllByStudent(oldWork.getStudent());
        Mockito.doReturn(true)
                .when(oldWork.getItem())
                .getActive();
   +    Mockito.doReturn(this.currentStudent)
   +            .when(this.studentService)
                .findById(oldWork.getStudent().getId());
        Work work = new Work();
        work.setScore(score);
        work.setReviewed(true);
        Work resultWork = new Work();
        Mockito.when(this.workRepository.save(Mockito.eq(oldWork)))
                .thenReturn(resultWork);
        Mockito.doReturn(true).when(workService).isTeacher();
        Assertions.assertEquals(resultWork, this.workService.updateScore(id, score));
        Assertions.assertEquals(oldWork.getScore(), work.getScore());
        Assertions.assertEquals(oldWork.getReviewed(),work.getReviewed());
   +    Assertions.assertEquals(oldWork.getStudent().getTotalScore(), 100);
   +    Assertions.assertEquals(oldWork.getStudent().getAverageScore(), 50);
    }
    ...
}

順利通過測試,看似沒什么問題,可是一跑全局單元測試,就崩了。

[ERROR] Failures: 
492[ERROR]   WorkServiceImplTest.saveWorkByItemIdOfCurrentStudent:105 expected: <club.yunzhi.workhome.entity.Student@1eb207c3> but was: <null>
493[ERROR] Errors: 
494[ERROR]   WorkServiceImplTest.getByItemIdOfCurrentStudent:73 » NullPointer
495[ERROR]   WorkServiceImplTest.updateOfCurrentStudent:138 » NullPointer
496[INFO] 
497[ERROR] Tests run: 18, Failures: 1, Errors: 2, Skipped: 0

一個斷言錯誤,兩個空指針錯誤。

可是這些三個功能我根本就沒有改,而且是之前已經(jīng)通過測試的功能,為什么會出錯呢?

拿出一個具體的錯誤,從本地跑一下測試:

測試代碼

@Test
    public void updateOfCurrentStudent() {
        Long id = this.random.nextLong();
        Work oldWork = new Work();
        oldWork.setStudent(this.currentStudent);
        oldWork.setItem(Mockito.spy(new Item()));
        Mockito.when(this.workRepository.findById(Mockito.eq(id)))
                .thenReturn(Optional.of(oldWork));
        //Mockito.when(this.studentService.getCurrentStudent()).thenReturn(this.currentStudent);
        Mockito.doReturn(true)
                .when(oldWork.getItem())
                .getActive();
        Work work = new Work();
        work.setContent(RandomString.make(10));
        work.setAttachments(Arrays.asList(new Attachment()));
        Work resultWork = new Work();
        Mockito.when(this.workRepository.save(Mockito.eq(oldWork)))
                .thenReturn(resultWork);
 137    Assertions.assertEquals(resultWork, this.workService.updateOfCurrentStudent(id, work));
        Assertions.assertEquals(oldWork.getContent(), work.getContent());
        Assertions.assertEquals(oldWork.getAttachments(), work.getAttachments());
    }

功能代碼

@Override
    public Work updateOfCurrentStudent(Long id, @NotNull Work work) {
        Assert.notNull(work, "更新的作業(yè)實體不能為null");
        Work oldWork = this.workRepository.findById(id)
                .orElseThrow(() -> new ObjectNotFoundException("未找到ID為" + id + "的作業(yè)"));
 178    if (!oldWork.getStudent().getId().equals(this.studentService.getCurrentStudent().getId())) {
            throw new AccessDeniedException("無權更新其它學生的作業(yè)");
        }
        if (!oldWork.getItem().getActive()) {
            throw new ValidationException("禁止提交已關閉的實驗作業(yè)");
        }
        oldWork.setContent(work.getContent());
        oldWork.setAttachments(work.getAttachments());
        return this.workRepository.save(oldWork);
    }

報錯信息java.lang.NullPointerException

at club.yunzhi.workhome.service.WorkServiceImpl.updateOfCurrentStudent(WorkServiceImpl.java:178)
at club.yunzhi.workhome.service.WorkServiceImplTest.updateOfCurrentStudent(WorkServiceImplTest.java:137)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

根據(jù)報錯信息來看,是測試類在調(diào)用功能代碼178行時,出現(xiàn)了空指針,
經(jīng)過分析,在執(zhí)行this.studentService.getCurrentStudent().getId()時出現(xiàn)的。

然后就來判斷studentService的注入情況,

//父類的BeforeEach
    public void beforeEach() {
        this.studentService = Mockito.mock(StudentService.class);
        this.currentStudent.setId(this.random.nextLong());
        Mockito.doReturn(currentStudent)
                .when(this.studentService)
                .getCurrentStudent();
    }
//測試類的BeforeEach
    @BeforeEach
    public void beforeEach() {
        super.beforeEach();
        this.itemService = Mockito.mock(ItemService.class);
        this.workRepository = Mockito.mock(WorkRepository.class);
        this.userService = Mockito.mock(UserService.class);
        this.itemRepository = Mockito.mock(ItemRepository.class);
        this.studentService = Mockito.mock(StudentService.class);
        this.studentRepository = Mockito.mock(StudentRepository.class);
        this.workService = Mockito.spy(new WorkServiceImpl(this.workRepository, this.studentService,
                this.userService, this.itemRepository, this.attachmentService, this.studentRepository));
    }

問題就出在這里,由于測試類執(zhí)行了繼承,父類已經(jīng)Mock了一個studentService并且成功的設定了Moockito的返回值,但測試類又進行了一次賦值,這就使得父類的Mock失效了,于是導致之前本來能通過的單元測試報錯了。

所以本實例的根本問題是,重復注入了對象。

這導致了原有的mock方法被覆蓋,以至于執(zhí)行了真實的studentService中的方法,返回了空的學生。

解決方法:

  • 在測試類WorkServiceImplTest中刪除studentService的注入,使用父類。
  • 使用子類的studentService,并在所有的報錯位置,加入對應的mock方法

總結

java.lang.NullPointerException直接翻譯過來是空指針,但根本原因卻不是空對象,一定是由于某種錯誤的操作(錯誤的注入),導致了空對象。

最常見的情況,就是在測試時執(zhí)行了真正的方法,而不是mock方法。
此時的解決方案,就是檢查所有的依賴注入和Mock是否完全正確,如果正確,就不會出現(xiàn)空指針異常了。

最根本的辦法,還是去分析,找到誰是那個空對象,問題就迎刃而解。

以上就是SpringMVC空指針異常NullPointerException解決及原理解析的詳細內(nèi)容,更多關于SpringMVC空指針異常解決的資料請關注腳本之家其它相關文章!

相關文章

  • Java實時監(jiān)控日志文件并輸出的方法詳解

    Java實時監(jiān)控日志文件并輸出的方法詳解

    這篇文章主要給大家介紹了關于Java實時監(jiān)控日志文件并輸出的方法,文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面跟著小編一起來學習學習吧。
    2017-06-06
  • SpringBoot底層注解超詳細介紹

    SpringBoot底層注解超詳細介紹

    這篇文章主要介紹了SpringBoot底層注解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-09-09
  • Java類加載器之ContextClassLoader詳解

    Java類加載器之ContextClassLoader詳解

    這篇文章主要介紹了Java類加載器之ContextClassLoader詳解,ContextClassLoader是一種與線程相關的類加載器,類似ThreadLocal,每個線程對應一個上下文類加載器,需要的朋友可以參考下
    2023-10-10
  • druid的borrow行為方法源碼解析

    druid的borrow行為方法源碼解析

    這篇文章主要為大家介紹了druid的borrow行為方法源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • Java數(shù)組的基本操作方法整理

    Java數(shù)組的基本操作方法整理

    這篇文章主要給大家介紹了關于Java中數(shù)組的定義和使用的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-06-06
  • idea設置@Author文件頭注釋的實現(xiàn)步驟

    idea設置@Author文件頭注釋的實現(xiàn)步驟

    本文主要介紹了idea設置@Author文件頭注釋的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-07-07
  • java連接數(shù)據(jù)庫增、刪、改、查工具類

    java連接數(shù)據(jù)庫增、刪、改、查工具類

    這篇文章主要介紹了java連接數(shù)據(jù)庫增、刪、改、查工具類,需要的朋友可以參考下
    2014-05-05
  • JavaWeb 使用Session實現(xiàn)一次性驗證碼功能

    JavaWeb 使用Session實現(xiàn)一次性驗證碼功能

    這篇文章主要介紹了JavaWeb 使用Session實現(xiàn)一次性驗證碼功能,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • 簡單了解java類型轉換常見的錯誤

    簡單了解java類型轉換常見的錯誤

    這篇文章主要介紹了簡單了解java類型轉換常見的錯誤,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • Java網(wǎng)絡編程之簡單的服務端客戶端應用實例

    Java網(wǎng)絡編程之簡單的服務端客戶端應用實例

    這篇文章主要介紹了Java網(wǎng)絡編程之簡單的服務端客戶端應用,以實例形式較為詳細的分析了java網(wǎng)絡編程的原理與服務器端客戶端的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-04-04

最新評論