1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > java实体类set方法报空指针异常_SpringMVC空指针异常NullPointerException的原因和解决方法...

java实体类set方法报空指针异常_SpringMVC空指针异常NullPointerException的原因和解决方法...

时间:2018-12-18 18:56:21

相关推荐

java实体类set方法报空指针异常_SpringMVC空指针异常NullPointerException的原因和解决方法...

前言

在写单元测试的过程中,出现过许多次java.lang.NullPointerException,而这些空指针的错误又是不同原因造成的,本文从实际代码出发,研究一下空指针的产生原因。

一句话概括:空指针异常,是在程序在调用某个对象的某个方法时,由于该对象为null产生的。

所以如果出现此异常,大多数情况要判断测试中的对象是否被成功的注入,以及Mock方法是否生效。

基础

出现空指针异常的错误信息如下:

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行调用WorkServiceImpl.java被测试类的178行出现问题。

下面从两个实例来具体分析。

实例

(代码仅为了报错时方便分析,请勿仔细阅读,避免浪费时间)

1

目的:测试服务层的一个用于更新作业的功能。

接口

/**

* 更新作业分数

* @param id

* @param score

* @return

*/

Work updateScore(Long id, int score);

接口实现:

@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 + "的作业"));

if (!this.isTeacher()) {

throw new AccessDeniedException("无权判定作业");

}

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());

}

运行测试,出现空指针:

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行,可以看到报错的代码根本不是要测试的方法,而是被调用的方法。

再看测试代码的203行,测试时的本来目的是为了Mock掉这个方法,但使用的是when().thenReturn方式。

对于Mock对象(完全假的对象),使用when().thenReturn和doReturn().when的效果是一样的,都可以制造一个假的返回值。

但是对于Spy对象(半真半假的对象)就不一样了,when().thenReturn会去执行真正的方法,再返回假的返回值,在这个执行真正方法的过程中,就可能出现空指针错误。

而doReturn().when会直接返回假的数据,而根本不执行真正的方法。

参考链接:https://sangsoonam.github.io/...

所以把测试代码的改成:

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

+ Mockito.doReturn(true).when(workService).isTeacher();

再次运行,就能通过测试。

2

目的:还是测试之前的方法,只不过新增了功能。

接口

/**

* 更新作业分数

* @param id

* @param score

* @return

*/

Work updateScore(Long id, int score);

接口实现(在原有的储存学生成绩方法上新增了计算总分的功能)

@Override

public Work updateScore(Long id, int score) {

Work work = this.workRepository.findById(id)

.orElseThrow(() -> new ObjectNotFoundException("未找到ID为" + id + "的作业"));

if (!this.isTeacher()) {

throw new AccessDeniedException("无权判定作业");

}

work.setScore(score);

work.setReviewed(true);

logger.info(String.valueOf(work.getScore()));

+ //取出此学生的所有作业

+ List 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);

}

由于出现了对学生仓库studentRepository的调用,需要注入:

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 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: but was:

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

一个断言错误,两个空指针错误。

可是这些三个功能我根本就没有改,而且是之前已经通过测试的功能,为什么会出错呢?

拿出一个具体的错误,从本地跑一下测试:

测试代码

@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, "更新的作业实体不能为null");

Work oldWork = this.workRepository.findById(id)

.orElseThrow(() -> new ObjectNotFoundException("未找到ID为" + id + "的作业"));

178 if (!oldWork.getStudent().getId().equals(this.studentService.getCurrentStudent().getId())) {

throw new AccessDeniedException("无权更新其它学生的作业");

}

if (!oldWork.getItem().getActive()) {

throw new ValidationException("禁止提交已关闭的实验作业");

}

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)

根据报错信息来看,是测试类在调用功能代码178行时,出现了空指针,

经过分析,在执行this.studentService.getCurrentStudent().getId()时出现的。

然后就来判断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));

}

问题就出在这里,由于测试类执行了继承,父类已经Mock了一个studentService并且成功的设定了Moockito的返回值,但测试类又进行了一次赋值,这就使得父类的Mock失效了,于是导致之前本来能通过的单元测试报错了。

所以本实例的根本问题是,重复注入了对象。

这导致了原有的mock方法被覆盖,以至于执行了真实的studentService中的方法,返回了空的学生。

解决方法:

在测试类WorkServiceImplTest中删除studentService的注入,使用父类。

使用子类的studentService,并在所有的报错位置,加入对应的mock方法

总结

java.lang.NullPointerException直接翻译过来是空指针,但根本原因却不是空对象,一定是由于某种错误的操作(错误的注入),导致了空对象。

最常见的情况,就是在测试时执行了真正的方法,而不是mock方法。

此时的解决方案,就是检查所有的依赖注入和Mock是否完全正确,如果正确,就不会出现空指针异常了。

最根本的办法,还是去分析,找到谁是那个空对象,问题就迎刃而解。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。