后端之路第二站(正片)——SprintBoot之:分层解耦

很抽象,我自己也不好理解,仅作为一个前端转后端的个人理解

一、先解释一个案例,以这个案例来分析"三层架构"

这里我先解释一下黑马程序员里的这个案例,兄弟们看视频的可以跳过这节课:Day05-08. 请求响应-响应-案例_哔哩哔哩_bilibili 看我这里的解说就可以,因为他这里把前端的文件塞到后端这里弄,还要解析XML文件来获取数据,完全没必要,没有任何地方会这样做了,直接看我的解释过一遍就行

首先他把一个写了我们要的数据的XML引入到spring boot项目中,我们可以理解类似为前端有时会在一个js文件里写一堆数据用于测试、模拟后端数据的(但实际后端应该是连接数据库,数据是来源于数据库的)

然后他的这个鬼XML文件里的数据里还不直接写明白"性别"是(男/女),而是写成(1/0),"职业"也是,非要写成(1/2/3),然后还得到获取数据的地方把(1/0)、(1/2/3)解析成(男/女)、(讲师/班主任/....),这不是脱裤子放屁,多此一举吗

于是,他又去请求页面,再解析获取到XML数据后,再去进行以上这些逻辑处理......我们暂且理解为vue里的<script></script>这部分的逻辑处理吧

最后再把这些数据发送请求

(我只是为了方便各位理解!!不是一个东西啊!!)

那么结合前端我们可以理解为有点类似"表单数据传送",我们先获取表单的所有数据,然后进行逻辑处理:拆分出数据然后封装进一个对象,最后发送请求给后端

但是看看这一坨代码这么乱......

二、三层架构是啥

那么分析一下一坨代码,可以看出其实可以分成三大块:

【数据访问】:解析数据源,拿到数据

【逻辑处理】:把数据抽出来进行一些逻辑处理

【接收请求、响应数据】:把数据封装好给回前端

那么我们应该把这三块分开来写,这样的话,那一部分出问题可以直接找到这一部分查看问题

因此人们就规定应该分成这三块:

这三块要做的事:

  • Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。

  • Service:业务逻辑层。处理具体的业务逻辑。

  • Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。

那么这个三层架构的执行流程是:

1、前端发起的请求,由Controller层接收(Controller响应数据给前端)

2、Controller层调用Service层来进行逻辑处理(Service层处理完后,把处理结果返回给Controller层)

3、Serivce层调用Dao层(逻辑处理过程中需要用到的一些数据要从Dao层获取)

4、Dao层操作文件中的数据(Dao拿到的数据会返回给Service层)

好,那么与之相反的,我们开发人员写代码的流程是:(切记!不要去管这里代码写了什么,过一遍流程就行!!!这里的代码不值得去花时间学习)

1、在【请求处理类】同级目录下创建一个【dao】目录

2、然后在【dao】目录下创建一个代表该获取的数据的接口(java里的接口,不是请求数据的那个接口)

然后创建这个接口的实现类(连同包含实现类的文件夹目录一块),implements实现这个类,然后把获取数据、解析数据的内容放到这里实现

记得把数据return出去

3、然后同样的流程【创建service目录】------>【创建代表数据的接口】------>【然后创建(连文件夹包一起)实现这个接口的实现类】------>【记得先创建并调用Dao的实现类对象,因为现在数据在Dao那】------>【然后service的实现类的内容就是处理数据的逻辑】

4、最后,创建【controller】目录,把一个【请求处理类】放到【controller目录】------>【然后现在完整、处理好的数据源在service实现类】------>【在方法外面创建service的实现类对象】------>【然后方法里调用service实现类对象,拿到数据】------>【然后通过请求return出去,响应给前端】

总结就是:

开发视角:数据源从外部到Dao------>然后我们要用service获取Dao的数据,然后处理------>然后Controller最后获取service处理好的数据,响应回前端

前端视角:发送请求给到Controller------>Controller找service要------>service找Dao要

三、分层解耦

1、理解耦合是什么?

耦合问题

首先需要了解软件开发涉及到的两个概念:内聚和耦合。

  • 内聚:通俗讲就是各功能功能模块跟自身功能联系

比如【Controller】的功能是接受请求、实现请求接口、响应回前端,【service】的功能是处理数据、做逻辑处理,【Dao】功能就是解析数据

那么这三块分开,各自里面的内容是各自的功能实现,这就是【内聚高】,互不影响

那么如果不分开,这三个内容的代码全堆【Controller】那里,那么【Controller】本来功能只用实现请求、响应前端的,现在当黑奴,一人干几份活,这就是【内聚低

  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。

比如【Controller】接收到前端请求后,找数据需要通过调用【service】来获取数据,而【service】找数据也得靠调用【Dao】来获取数据,这种联系就叫【耦合

当他们是三个部门,当少了一方部门、或者其中一个部门要改名字啥的,会牵连到三个部门,这就是【耦合高

但是软件工程开发要求【内聚低耦合高

那么就要分层解耦

2、分层解耦是什么?

那么现在我们要解耦,该怎么办?

我们思考一下【Controller】调用的是什么?是【service的实现类】。

那么【Controller】调用的时候其实不管你是叫【service_A】还是【service_B】,只要是implement实现了【service】这个接口的实现类,【Controller】都需要

那么就可以有这么一个外部【容器】,他装有实现了【service】接口的【实现类】,当【Controller】需要的时候就去【容器】找有没有,有的话就拿去用

(我们理解为"华为公司"需要招聘员工,不管你是清华学子、还是北大学子,只要你是实现、集合了"985/211重本院校"的学生,他都要。那么我们就可以设一个"人才市场",里面有各个[实现、集合了"985/211重本院校"的学生],华为公司需要谁,就去人才市场要)

这里还有几个概念要知道:

控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。(通俗理解给自己签订卖身契,卖到人口市场,随时被人交易)

依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。(通俗理解为买家跟人口市场绑定了,有符合要求的就自动买了)

IOC容器中创建、管理的对象,称之为:bean对象 (理解为人口市场的奴隶hh)

四、IOC控制反转 + DI注入依赖

1、首先先把【Controller】【service】【Dao】之间的联系删掉

(理解为华为公司取消了跟某些高校的强制合作,不要固定那几个高校推荐内推的学生,选择让人才市场给自己分配员工)

2、然后开始【控制反转】

给【service】和【Dao】加上容器注解【@Component】

加上它,就意味着要将当前类"交给"IOC容器管理,成为IOC容器里的一个bean对象

(可以理解为高材生把自己的简历投放到人才市场了,由人才市场保管自己的简历)

3、然后开始【依赖注入】

要将【Controller】绑定上【依赖注入】,加上注解【@Autowired】

加上【@Autowired】就意味着运行时,IOC容器会提供该类型的bean对象,并赋值给该变量

(理解为华为公司把自己的HR丢人才市场,任由HR发挥,只要符合要求就直接招进公司)

4、那如果要更换bean对象怎么办?

加入我有一个【service】实现类【A】,我现在不要用它了,我要改成【B】怎么办?

把【A】的【@Component】注释掉,在【B】上面加上【@Component】就行了......就这么简单

五、Bean另外的衍生注解

|-----------------|----------------------|--------------------------------------|
| 注解 | 说明 | 位置 |
| @Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
| @Controller | @Component 的衍生注解 | 标注在控制器类上 |
| @Service | @Component的衍生注解 | 标注在业务类上 |
| @Repository | @Component的衍生注解 | 标注在数据访问类上 (由于与mybatis整合,用的少) |

是啥个意思呢?

意思是【控制反转】有的时候不一定要用【@Component】,其实【Controller】、【Service】、【Dao】的【控制反转】可以分别用【@Controller 】、【@Service 】、【@Repository】来注解

然后因为其实【@Service 】、【@Repository 】的源代码里其实是包含了【@Component 】的,所以用【@Service 】、【@Repository 】就等于用【@Component

但是spring boot的web开发里,【Controller】控制器不能用【Component】,不过它除了注解【@Controller】,我们之前讲过【@RestController】也包含了【@Controller】,所以用【@ReastController】就可以了

六、主包(主目录)外面的包的Bean对象扫描不到

问题:使用前面学习的四个注解声明的bean,一定会生效吗?

答案:不一定。(原因:bean想要生效,还需要被组件扫描,比如你主包外面的bean对象就不会生效)

那就要用到组件扫描:【@ComponentScan】

但是【@ComponentScan】并不是乱用的,首先回到我们的【启动类】

我们知道【@SpringBootApplication】是【启动类】注解,但其实他还是包含了【@ComponentScan】的组件扫描注解,它可以扫描【当前包以及其自包里的所有的Bean】

但是要是Bean在主包的外面怎么办?

那这是才只能手动写【@ComponentScan】来导入要扫描的"包(目录)",并且还得带上原本当前这个"包(目录)",因为会覆盖下面的【@SpringBootApplication】

具体语法是:

java 复制代码
@ComponentScan( { "外部含Bean的那个包" , "当前包" } )

但其实不建议这样,一般情况推荐全部Bean都放到启动类的包下,方便全局扫描

七、DI注入依赖选择一个Bean来注入

我们前面说过,如果想要注入依赖的时候,容器里有两个符合条件的实现类,只选择其中一个的话,就把另一个实现类的【@Component】注释掉

但是如果我容器里有成千上万个符合条件的【实现类Bean】怎么办?难道我要全部一个一个去注释吗?

比如下面这个例子,我有两个都写了【@Sevice】(怕忘了,再提醒一下这个也等于@Component)的【service实现类Bean】

然后此时再运行时就会报错,因为超过两个同类型的实现类Bean

这是因为注入依赖里的注解【@Autowired】,它是根据类型来自动获取容器里所有的符合的实现类Bean的,那么容器里有两个就拿两个,有十个就拿十个,而一旦超过了一个Bean就会报错

那么解决办法有三个

1、(不太推荐)@Primary:在要希望生效的那个Bean上添加这个注解

2、(可以)@Qualifier:在要注入依赖的地方、@Autowired前面指定你要哪一个Bean

3、(非常推荐)@Resouce:直接在注入依赖的地方,去掉@Autowired,替换成这个并指定要哪个Bean

1、@Primary是在要希望生效的那个Bean上添加这个注解,不推荐是因为跟注释【@Component】其实是半斤八两的啊.......你还得去一个一个找到这个Bean,然后再Bean的代码里改

2、@Qualifier就是在要注入依赖的地方、@Autowired前面指定你要哪一个Bean,这样确实很方便,直接指明要谁,美中不足就是,我感觉跟@Autowired有点重复,两在一块总感觉有点多余

另外解释为什么【实现类EmpServiceA】在@Qualifier这要写成【empServiceA】,因为我前面懒,省略了讲:Bean对象的名字其实是【实现类】名字的首字母小写......而不是它原本的实现类的那个名字

3、@Resouce为甚推荐?因为它是直接在注入依赖的地方,去掉@Autowired,替换成@Resouce,并写上指定要哪个Bean的名字就行

为什么它可以直接不要【@Autowired】,因为【@Resouce】是根据名字来注入Bean的!!

另外【@Primary】跟【@Qualifier】是spring boot框架提供的,而【@Resouce】是Java的JDK提供的

ok,本爷的spring boot学习先告一段落,后面先学MySQL,然后等再学回spring boot的时候再更新博客

相关推荐
snakeshe101034 分钟前
深入理解 Java 注解:从原理到实战
后端
Lucaju38 分钟前
吃透 Spring AI Alibaba 多智能体|四大协同模式+完整代码
后端
Nyarlathotep011339 分钟前
Redis的对象(5):有序集合对象
redis·后端
Java水解1 小时前
Spring Boot 消息队列与异步处理
spring boot·后端
桦说编程1 小时前
AI 真的让写代码变快了吗?
后端
云烟成雨TD2 小时前
Spring AI 1.x 系列【14】三月双版本连发!Spring AI 最新功能全掌握
java·人工智能·spring
AskHarries2 小时前
openclaw升级和参数调整
后端·ai编程
creaDelight2 小时前
基于 Django 5.x 的全功能博客系统 DjangoBlog 深度解析
后端·python·django
Rust语言中文社区3 小时前
【Rust日报】 Danube Messaging - 云原生消息平台
开发语言·后端·rust