在SpringMVC中,一个标准的架构是这样的: ![](https://img.kancloud.cn/04/7c/047c0e7d740901940bc97b2ee0d4cce7_341x391.png) 它们间的分工如下: * [ ] 实体:负责映射数据表结构。 * [ ] 数据仓库:负责操作数据表完成基本的增查改删。 * [ ] 服务M层:负责进行逻辑运算。 * [ ] 控制器C层:负责数据输入、数据转发、数据输出。 上一小节的内容明显地违背了这一原则,这也直接使我们在上一个小节中放弃了更健壮、更具有追溯性的单元测试。本节中,我们引入主要进行业务处理的serivce M层,进而使当前项目结构更加的贴近于生产项目。 ## Service初始化 和angular的依赖注入相同,spring也是支持依赖注入的。我们在前面的小节中早早的使用了`@Autowired`注解完成了该依赖注入的过程。在spring项目,spring充当了整个项目的管理者。当我们需要某个功能的服务时,只需要用`@Autowired`注解来进行声明。spring看到该声明后,会在需要的时候将符合此声明的服务传送给我们。除了使用`@Autowired`注解外,spring的依赖注入也是支持在构造函数中进行声明的。 在使用spring的依赖注入时,由于java语言相对于typescript语言更加**“呆板”**。我们不能够像angular一样声明某个`类`来做为该服务对象的`规范`,而是应该声明某个`接口`。这是由于JAVA**“呆板”**的认为`类`是用于创建对象的,而`接口`才是真真切切用来定规范的。 > 实际上我们会越来越多的喜欢上JAVA的这种**“呆板”** ### 规范实始化 新建service包,并在该班中建立KlassService接口: service/KlassService.java ``` package com.mengyunzhi.springBootStudy.service; /** * 班级服务 */ public interface KlassService { } ``` ``` panjiedeMac-Pro:springBootStudy panjie$ tree . ├── SpringBootStudyApplication.java ├── config │   └── WebConfig.java ├── controller │   ├── KlassController.java │   └── TeacherController.java ├── entity │   ├── Klass.java │   └── Teacher.java ├── repository │   ├── KlassRepository.java │   └── TeacherRepository.java └── service └── KlassService.java 5 directories, 9 files ``` ### 建立规范 按前面刚刚讲过的分工理论,C层不应该进行任何的逻辑运算,它的作用就是接收数据后再将数据进行转发,那么这个转发则是通过调用服务层特定方法来实现的。所以C层如果想进行数据转发,那我们在M层中则应该建立其进行转发的方法。 ``` package com.mengyunzhi.springBootStudy.service; import com.mengyunzhi.springBootStudy.entity.Klass; /** * 班级服务 */ public interface KlassService { /** * 通过ID获取班级 * * @param id 班级ID * @return 班级实体 */ Klass getById(Long id); /** * 更新班级 * * @param id 预更新的班级ID * @param klass 新的班级信息 */ void update(Long id, Klass klass); } ``` ## 尝试注入 有了规范,我们将此规范注入到KlassController中,然后重新启动项目看看会发生什么 controller/KlassController.java ``` public class KlassController { private static final Logger logger = LoggerFactory.getLogger(KlassController.class); @Autowired KlassService klassService; ``` 启动项目,在控制台中我们得到如下错误: ``` Description: Field klassService in com.mengyunzhi.springBootStudy.controller.KlassController required a bean of type 'com.mengyunzhi.springBootStudy.service.KlassService' that could not be found. ``` 它的意思大概是说:没有找到任何一个实现了KlassService所标注功能规范的对象。没错,就应该是这样。`接口`=`规范`,我们说`usb`是个接口一种规范。而用户实际上需要的却是实现了`usb接口`规范的`usb鼠标`。 ### 实现接口 在spring中,我们如下为spring添加一个`usb鼠标`。 我们来到KlassService,使用快捷键来快速的生成了一个实现了该接口规范的实现类。 ![](https://img.kancloud.cn/4e/0c/4e0c1d0307717e34cdb30366b482f685_952x332.gif) 然后在该类上使用`@Service`来表时该类接受spring的统一管理。 service/KlassServiceImpl.java ``` package com.mengyunzhi.springBootStudy.service; import com.mengyunzhi.springBootStudy.entity.Klass; import org.springframework.stereotype.Service; @Service ➊ public class KlassServiceImpl implements➋ KlassService➌ { @Override public Klass getById(Long id) { return null; } @Override public void update(Long id, Klass klass) { } } ``` * ➊➋该类接受spring的统一管理。当需要➌时,使用本类创建对象来提供服务。 此时当KlassController在使用注解`@Autowired`表示需要KlassService时,spring便会依此类来创建一个对象来满足KlassController的要求。 ### 完善代码 我们将以前存在于C层中的代码进行迁移: service/KlassService.java ``` package com.mengyunzhi.springBootStudy.service; import com.mengyunzhi.springBootStudy.entity.Klass; import com.mengyunzhi.springBootStudy.repository.KlassRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 班级服务实现 */ @Service public class KlassServiceImpl implements KlassService { /*班级仓库*/ @Autowired KlassRepository klassRepository; /** * 获取某个班级 * * @param id 班级ID * @return 班级 */ @Override public Klass getById(Long id) { return this.klassRepository.findById(id).get(); } /** * 更新班级 * 获取数据库中的老数据 * 使用传入的新数据对老数据的更新字段赋值 * 将更新后的老数据重新保存在数据表中 * * @param id 要更新的班级ID * @param klass 新班级数据 */ @Override public void update(Long id, Klass klass) { Klass oldKlass = klassRepository.findById(id).get(); oldKlass.setName(klass.getName()); oldKlass.setTeacher(klass.getTeacher()); klassRepository.save(oldKlass); } } ``` 接着重新对C层代码进行整理: controller/KlassController.java ``` package com.mengyunzhi.springBootStudy.controller; import com.mengyunzhi.springBootStudy.entity.Klass; import com.mengyunzhi.springBootStudy.repository.KlassRepository; import com.mengyunzhi.springBootStudy.service.KlassService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 班级控制器 */ @RestController @RequestMapping("Klass") public class KlassController { private static final Logger logger = LoggerFactory.getLogger(KlassController.class); @Autowired KlassService klassService; @Autowired KlassRepository klassRepository; @GetMapping("{id}") @ResponseStatus(HttpStatus.OK) public Klass get(@PathVariable Long id) { return this.klassService.getById(id); } @GetMapping public List<Klass> getAll(@RequestParam String name) { return this.klassRepository.findAllByNameContains(name); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void save(@RequestBody Klass klass) { klassRepository.save(klass); } /** * 更新班级 * * @param id 要更新的班级ID * @param klass 新班级数据 */ @PutMapping("{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void update(@PathVariable Long id, @RequestBody Klass klass) { this.klassService.update(id, klass); } } ``` ### 继续重构 一不做二不休,借此机会,将KlassController中原有的getAll及save方法也转移到服务层中: **请自行尝试后继续阅读** service/KlassService.java ``` package com.mengyunzhi.springBootStudy.service; import com.mengyunzhi.springBootStudy.entity.Klass; import java.util.List; /** * 班级服务 */ public interface KlassService { /** * 获取所有班级列表 * * @param name 班级名称 * @return */ List<Klass> getAll(String name); /** * 通过ID获取班级 * * @param id 班级ID * @return 班级实体 */ Klass getById(Long id); /** * 新增 * * @param klass 班级 */ void save(Klass klass); /** * 更新班级 * * @param id 预更新的班级ID * @param klass 新的班级信息 */ void update(Long id, Klass klass); } ``` service/KlassServiceImpl.java ``` package com.mengyunzhi.springBootStudy.service; import com.mengyunzhi.springBootStudy.entity.Klass; import com.mengyunzhi.springBootStudy.repository.KlassRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * 班级服务实现 */ @Service public class KlassServiceImpl implements KlassService { /*班级仓库*/ @Autowired KlassRepository klassRepository; @Override public List<Klass> getAll(String name) { return this.klassRepository.findAllByNameContains(name); } /** * 获取某个班级 * * @param id 班级ID * @return 班级 */ @Override public Klass getById(Long id) { return this.klassRepository.findById(id).get(); } @Override public void save(Klass klass) { this.klassRepository.save(klass); } /** * 更新班级 * 获取数据库中的老数据 * 使用传入的新数据对老数据的更新字段赋值 * 将更新后的老数据重新保存在数据表中 * * @param id 要更新的班级ID * @param klass 新班级数据 */ @Override public void update(Long id, Klass klass) { Klass oldKlass = klassRepository.findById(id).get(); oldKlass.setName(klass.getName()); oldKlass.setTeacher(klass.getTeacher()); klassRepository.save(oldKlass); } } ``` controller/KlassController.java ``` package com.mengyunzhi.springBootStudy.controller; import com.mengyunzhi.springBootStudy.entity.Klass; import com.mengyunzhi.springBootStudy.service.KlassService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 班级控制器 */ @RestController @RequestMapping("Klass") public class KlassController { private static final Logger logger = LoggerFactory.getLogger(KlassController.class); @Autowired KlassService klassService; @GetMapping("{id}") @ResponseStatus(HttpStatus.OK) public Klass get(@PathVariable Long id) { return this.klassService.getById(id); } @GetMapping public List<Klass> getAll(@RequestParam String name) { return this.klassService.getAll(name); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void save(@RequestBody Klass klass) { this.klassService.save(klass); } /** * 更新班级 * * @param id 要更新的班级ID * @param klass 新班级数据 */ @PutMapping("{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void update(@PathVariable Long id, @RequestBody Klass klass) { this.klassService.update(id, klass); } } ``` ### 测试 代码最怕的就是**不重构**,而重构最怕就是**重构后出错**,而解决重构后出错的唯一办法则是**充分地测试**,在充分的测试中最可靠的便是使用代码来进行测试的**单元测试**。 此时,我们来到当前唯一我们书写的测试文件:controller/KlassControllerTest.java ![](https://img.kancloud.cn/07/e3/07e38687722696a7a6656dd3afd5f72e_1204x307.png) 点一下前面的绿色小按钮,然后等待测试结果: ![](https://img.kancloud.cn/f8/97/f8979e1a3c978c5d79469dce18f72932_767x164.png) 结果通过说明重构过程完美! > 行百里者半于九十。完了功能性的代码时,真正的工作才刚刚开始,让我们一起尽情拥抱伟大的单元测试吧。 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.4.6](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.4.6) | - |