SpringBoot:分层解耦(三层架构,控制反转,依赖注入)

💥 该系列属于【SpringBoot基础】专栏,如您需查看其他SpringBoot相关文章,请您点击左边的连接

目录

一、项目结构

二、案例引入

三、三层架构

1. 介绍

2. 代码拆分

(1)控制层 Controller

(2)业务逻辑层 Service

业务接口

业务实现类

(3)数据访问层 Dao

数据访问接口

数据访问实现类

(4)接口测试

四、解除耦合

1. 高内聚与低耦合

2. 控制反转和依赖注入

3. 解耦后的代码

(1)控制层 Controller

(2)业务逻辑层 Service

业务接口

业务实现类

(3)数据访问层 Dao

数据访问接口

数据访问实现类

4. IOC和DI的进一步探讨

(1) IOC

@Controller

@Service

@Repository 

(2) DI

@Primary

@Qualifier

@Resource


一、项目结构

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是按照名称注入

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值