后端之路第二站(正片)——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】

具体语法是:

@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的时候再更新博客

  • 46
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值