💥 该系列属于【SpringBoot基础】专栏,如您需查看其他SpringBoot相关文章,请您点击左边的连接
目录
一、项目结构
Person类:含有id,age,name属性。
二、案例引入
在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一些(单一职责原则)。
对于一个根据ID查询第二年的Person信息的功能,如果不采用分层的Controller代码如下:
@RestController
public class PersonController {
//接收请求
@RequestMapping("/getListById")
public Result<Person> findPerson(@RequestParam(value = "id") Integer id) {
// 模拟从数据库中读取数据,并返回查找到的Person
Map<Integer, Person> map = readFromDataBase();
Person person = map.get(id);
// 处理业务逻辑
person.setAge(person.getAge() + 1);
// 返回数据
return Result.success(person);
}
private Map<Integer, Person> readFromDataBase() {
Map<Integer, Person> map = new HashMap<>();
map.put(1, new Person(1, 23, "张三"));
map.put(2, new Person(2, 24, "李四"));
return map;
}
}
可以看出,如果不采用分层设计的做法,将接收参数、读取数据、处理业务逻辑以及返回结果全部封装在一个函数内,随着项目时间的推移和功能模块的增多,这样的函数会变得越来越庞大和复杂。
这种方式会带来诸多问题,如代码的可维护性降低、扩展性受限、复用性不足。因此有必要借助分层思想来优化这种设计模式。换言之,分层的优点有:复用性强、便于维护、利于扩展。
三、三层架构
1. 介绍
其实上述案例的处理逻辑从组成上看可以分为三个部分:
-
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
-
逻辑处理:负责业务逻辑处理的代码。
-
请求处理、响应数据:负责,接收页面的请求,给页面响应数据。
按照上述的三个组成部分,在项目开发中可以将代码分为三层:
-
Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
-
Service:业务逻辑层。处理具体的业务逻辑。
-
Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
2. 代码拆分
(1)控制层 Controller
接收前端发送的请求,对请求进行处理,并响应数据。
@RestController
public class PersonController {
//接收请求
@RequestMapping("/getListById")
public Result<Person> findPerson(@RequestParam(value = "id") Integer id) {
//调用Service层方法
PersonService personService = new PersonServiceImpl();
Person person = personService.findPerson(id);
// 返回数据
return Result.success(person);
}
}
(2)业务逻辑层 Service
处理具体的业务逻辑。
业务接口
public interface PersonService {
Person findPerson(Integer id);
}
业务实现类
public class PersonServiceImpl implements PersonService {
@Override
public Person findPerson(Integer id) {
//调用Dao方法
PersonMapper personMapper = new PersonMapperImpl();
Person person = personMapper.getListById(id);
//处理业务逻辑
person.setAge(person.getAge() + 1);
return person;
}
}
(3)数据访问层 Dao
负责数据的访问操作,包含数据的增、删、改、查。
数据访问接口
public interface PersonMapper {
Person getListById(Integer id);
}
数据访问实现类
public class PersonMapperImpl implements PersonMapper {
// 构造数据用来模拟数据库
private Map<Integer, Person> readFromDataBase() {
Map<Integer, Person> map = new HashMap<>();
map.put(1, new Person(1, 23, "张三"));
map.put(2, new Person(2, 24, "李四"));
return map;
}
@Override
public Person getListById(Integer id) {
// 模拟从数据库中读取数据,并返回查找到的person
Map<Integer, Person> map = readFromDataBase();
Person person = map.get(id);
return person;
}
}
(4)接口测试
四、解除耦合
1. 高内聚与低耦合
软件开发涉及到的两个概念:内聚和耦合。
-
内聚:软件中各个功能模块内部的功能联系。
-
耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
软件设计原则:高内聚低耦合。
高内聚:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即“高内聚”。
低耦合:软件中各个层、模块之间的依赖关联程序越低越好。
高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。
需要什么对象,就直接new一个就可以了, 这种做法层与层之间代码就耦合了。因此不使用new创建对象。
但是不通过new创建对象,程序就会报错,因此可以提供一个解决思路:提供一个容器,把对象放到容器中存储,当程序运行时,就从容器中拿出对象以供程序正常运行。
2. 控制反转和依赖注入
想要实现上述解决思路,就涉及到Spring中的两个核心概念:
-
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部容器,这种思想称为控制反转。
对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器
-
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。
程序运行时需要某个资源,此时容器就为其提供这个资源。
例:Controller程序运行时需要Service对象,Spring容器就为其提供并注入Service对象。
IOC容器中创建、管理的对象,称之为:bean对象。
模型如下所示:
以Controller获取Service对象为例,具体模型如下所示:
3. 解耦后的代码
(1)控制层 Controller
@RestController
public class PersonController {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private PersonService personService;
//接收请求
@RequestMapping("/getListById")
public Result<Person> findPerson(@RequestParam(value = "id") Integer id) {
//调用Service层方法
Person person = personService.findPerson(id);
// 返回数据
return Result.success(person);
}
}
(2)业务逻辑层 Service
处理具体的业务逻辑。
业务接口
public interface PersonService {
Person findPerson(Integer id);
}
业务实现类
@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class PersonServiceImpl implements PersonService {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private PersonMapper personMapper;
@Override
public Person findPerson(Integer id) {
Person person = personMapper.getListById(id);
person.setAge(person.getAge() + 1);
return person;
}
}
(3)数据访问层 Dao
负责数据的访问操作,包含数据的增、删、改、查。
数据访问接口
public interface PersonMapper {
Person getListById(Integer id);
}
数据访问实现类
@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class PersonMapperImpl implements PersonMapper {
// 构造数据用来模拟数据库
private Map<Integer, Person> readFromDataBase() {
Map<Integer, Person> map = new HashMap<>();
map.put(1, new Person(1, 23, "张三"));
map.put(2, new Person(2, 24, "李四"));
return map;
}
@Override
public Person getListById(Integer id) {
// 模拟从数据库中读取数据,并返回查找到的person
Map<Integer, Person> map = readFromDataBase();
Person person = map.get(id);
return person;
}
}
4. IOC和DI的进一步探讨
(1) IOC
在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component
而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:
注解 | 说明 | 位置 |
---|---|---|
@Controller | @Component的衍生注解 | 标注在控制器类上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少) |
@Component | 声明bean的基础注解 | 不属于以上三类时,用此注解 |
@Controller
@RestController // @RestController = @Controller + @ResponseBody
public class PersonController {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private PersonService personService;
//接收请求
@RequestMapping("/getListById")
public Result<Person> findPerson(@RequestParam(value = "id") Integer id) {
//调用Service层方法
Person person = personService.findPerson(id);
// 返回数据
return Result.success(person);
}
}
@Service
@Service //将当前对象交给IOC容器管理,成为IOC容器的bean
public class PersonServiceImpl implements PersonService {
@Autowired
private PersonMapper personMapper;
@Override
public Person findPerson(Integer id) {
Person person = personMapper.getListById(id);
person.setAge(person.getAge() + 1);
return person;
}
}
@Repository
@Repository //将当前对象交给IOC容器管理,成为IOC容器的bean
public class PersonMapperImpl implements PersonMapper {
// 构造数据用来模拟数据库
private Map<Integer, Person> readFromDataBase() {
Map<Integer, Person> map = new HashMap<>();
map.put(1, new Person(1, 23, "张三"));
map.put(2, new Person(2, 24, "李四"));
return map;
}
@Override
public Person getListById(Integer id) {
// 模拟从数据库中读取数据,并返回查找到的person
Map<Integer, Person> map = readFromDataBase();
Person person = map.get(id);
return person;
}
}
(2) DI
在上面的案例中,使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。
@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)
如果该类型有多个:
则会抛出异常。
***************************
APPLICATION FAILED TO START
***************************
Description:
Field personMapper in com.example.service.Impl.PersonServiceImpl required a single bean, but 2 were found:
- personMapperImpl: defined in file [C:\Users\18039\Desktop\SpringBootTest\springbootdemo\target\classes\com\example\mapper\Impl\PersonMapperImpl.class]
- personMapperImpl2: defined in file [C:\Users\18039\Desktop\SpringBootTest\springbootdemo\target\classes\com\example\mapper\Impl\PersonMapperImpl2.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
如何解决上述问题呢?Spring提供了以下几种解决方案:
-
@Primary
-
@Qualifier
-
@Resource
@Primary
@Qualifier
@Resource
@Autowird 与 @Resource的区别
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
@Autowired 默认是按照类型注入,而@Resource是按照名称注入