请求响应系统优化实践:基于IOC与DI的分层解耦案例分析
本篇是来源于www.bilibili.com/video/BV1m8…的案例 进行实践分析(自己的理解🙃😊😉)
案例-理解分析
下面是具体的每个步骤,我还是默认完成了前三步,从第四步开始分析。看这篇之前还是得先自己跟着视频完成一遍,这里主要是帮助理解。
直接开门见山了哈,首先确保后端程序跑起来了哈,接着前端发送过来一段请求:http://localhost:8080/emp.html,可以看到这里的8080/后面是一个前端页面的代码文件emp.html,所以就先访问了我idea中的此文件
我们可以来看看这段代码中的script片段:
这里有个钩子函数(mounted每个生命周期都会调用《这是前端那边的知识》,里面的axios是用于在前端代码中向服务端发送异步请求的工具,它将这里的URL发送给web服务端,web服务端本身并不直接处理,而是转交给servlet容器(Tomcat),由dispatcherServlet根据这个请求的URL与Controller中的 @RequestMapping 标注的很多方法进行匹配,若匹配成功则调用该方法,于是就通过 "/listEmp" 找到了该方法
然后就开始一步步是处理这个方法:
第一步:
这里是动态的拿到了这里文件中的emp.xml
并把xml文件中的信息放到了集合empList中,用到了引入的XmlParserUtils(xml解析工具)。————>这一步后面我们开始解耦的时候把它叫做(Dao层——数据访问)留个印象哈!!!
下面就是这个xml文件,我们可以把它理解为一个轻量级的(临时的)数据库
用于存放信息。!!!
第二步:
这里就是把集合empList遍历了一遍,把里面的123换成了对应的数据,再返回集合,只不过用了stream的循环forEach和箭头 ——>函数,显得比较高级
这里的emp就相当于是一个迭代器 每次代表集合中的一个“子元素”。
这部分在后面解耦合中是属于(Service层——逻辑处理)!!留个印象嗷
第三步:
响应数据:在Result(前面提到过的统一响应结果)中 用到success方法 传递了响应回去的数据(这个集合),再经过 @ResponseBody 这个注解的处理,就会将这个对象转换成一个json 响应回客户端。(详细点就是Controller完成了这个业务逻辑后将数据返回给web服务器端,再由web服务器响应数据回客户端(浏览器),这样在浏览器中就可以看到效果啦~)
这部分在后面解耦合中是属于(Controller层——接收请求、响应数据)!留个印象!
案例-优化(分层解耦)
前面一直说到的解耦解耦,这不马上就来了嗷~
前面的全是把代码放在Controller的这个方法中 ,如果开发的是一个比较复杂的工程,那么Controller方法中有太多的数据操作、逻辑处理,代码的复用性和扩展性太差了,而且难以维护,所以我们会用到 —— 分层解耦,但我们先要了解什么是三层架构~!!
1.三层架构
引言:我们在进行软件设计和开发的时候,需要尽量让每个接口、类或者方法的职责尽量的单一(一个类或一个方法就只做一件事儿只管一块功能)
———— 单一职责原则;这样可以使 接口 类以及方法的复杂度更低,可读性更强拓展性更好,更利于后期的维护
三层架构当中请求的执行流程:
如果按照三层架构的模式,现在我想修改某一个功能接口的业务逻辑,就可以不用动Controller层和Dao层。只用改动Service层
所以我们可以把上面的代码进行三层架构的思想进行优化,
新建dao包和service包
我们先来编写dao层的代码:
在dao包中 先定义一个接口(因为dao层是访问数据,但访问数据的实现方式有很多,可能访问的是文件中的数据、也有可能访问的是数据库中的数据、也有可能访问的是别人提供给我们的一个接口获取到的数据,要想灵活的去切换各种实现,我们就可以通过面向接口的方式进行编程)
返回值是List集合
再在dao层中创建一个实现类EmpDaoA(连包带类)
这里类去实现EmpDao接口,并实现其中的方法,把前面的原始Controller中属于dao层的代码(数据访问)放进这个方法中,结果返回集合empList
Dao层就完事儿了~
我们再来编写Service层的代码:(模式跟dao层差不多)
还是先定义一个接口:(返回值也是集合List)
同样再在Service层中创建一个实现类EmpServiceA(连包带类)
这里类去实现EmpService接口,并实现其中的方法,把前面的原始Controller中属于Service层的代码(逻辑处理)放进这个方法中,结果返回集合empList。但注意这里empList会报错
我们再来分析:Service层是要调用Dao层来获取,所以要在service中创建Dao层的对象(采用面向接口的方式创建)
再接下来就是调用empDao当中的方法listEmp( ),来获取处理之后的数据:
这样service层就写好啦~
我们最后来看Controller层的代码:
它仅负责接收请求、响应数据;
因为Controller层是要调用Service层来获取,所以也要在Controller中创建Service层的对象(采用面向接口的方式创建)
接下来就是调用empService当中的方法listEmp( ),来获取处理之后的数据:
之后呢再将数据 响应回去;
我们再来梳理一下整个流程:(真的真的很细了)
在我们前端发起请求之后,先到达Controller程序,它只负责接受请求、响应数据,当他接收到请求之后,就直接去调用Service层中的方法listEmp()①
Service层主要负责逻辑处理,要获取数据,它又去调用Dao层中的方法listEmp()②,由dao程序来负责数据的访问操作、查询数据;再将查询后的数据(集合) 返回给service③,service拿到数据之后再对数据进行逻辑处理并返回数据(集合)给controller程序④;controller拿到的这个结果就是处理完毕之后的结果,最终响应给前端。这样我们就完成三层架构的拆分。
2.分层解耦
依赖程度越高 耦合度就越高!
回到刚刚三层架构中,Controller层去调用了Service层(new了一个Service层的实现类)一旦这样做了之后,如果Service层中的这个实现类名字发生了变化/或者我们要切换一个实现类 如:EmpServiceB,那Controller层中的代码就必须改,不然就报错 ,同理:Service层去调用了dao层(new了一个dao层的实现类),该实现类一有变动,Service层中的代码也得跟着变。
根据软件设计原则: 我们目的是实现解除耦合(层与层之间没有依赖):
根据上文可知,如果我们想要不发生耦合,那么就一定不能new下一层的实现类对象,所以第一步就是要把后半截删掉
,但光这样操作会报错,是因为你声明了这一个变量,但这个变量没有赋值为Null。【报错:空指针异常】;所以有一个神秘的容器即将登场了。
没错IOC(控制反转)!!!特别重要
控制反转: lnversion Of Control,简称 IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
依赖注入: Dependency Iniection,简称 DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
bean对象: IOC 容器中创建,管理的对象,称之为bean.
根据前面的依旧耦合的代码,
因为Controller层需要Service层的实现类、Service层需要Dao层的实现类
所以为了实现解耦,我们干脆把需要的实现类都交给IOC容器,然后Controller和Service 他们需要什么类型的bean(放在IOC容器中的实现类),IOC就给他们注入bean对象(也即是依赖注入)。这样应该好理解的多了
第一步: 我们需要将Service层以及Dao层的实现类,交给IOC容器管理。
只需要再这两个实现类头上依次加上一个注解 @Component 即可
(这里的 @Component后面还会细分出来几个)
第二步: 要为Controller层以及Service层注入运行时,依赖的对象。
就在声明成员变量上方加上注解 @Autowired即可(自动装配~cool)
感觉到这儿好像可以啦,但!!如果我们还有增加一个Service层的实现类EmpServiceB,并且想由EmpServiceA切换到EmpServiceB呢?
我们就可以去掉EmpServiceA头上的 @Component, 使 @Component在EmpServiceB的头上,即可以实现切换。
总之, IOC容器中就只能存放同一类型下的一个实现类!
再然后关于 @Component衍生出来了三个同样功能的注解;注意!只是标注的位置不同(这样会更规范)作用都还是把某个实现类对象交给IOC容器:
在Web程序的开发中 更推荐用下面三个衍生注解标注Bean。
而 @Component则是当在项目开发中 某一个类不能归到三层中的任意一层,那么就用它。
还有就是 @Controller注解其实可以不用标注在Controller层上,因为Controller层上已经有 @RestController这个注解,前面讲过它包含了 @Controller和@ResponseBody
所以我们现在有四个注解可以用来声明一个Bean,而每个Bean其实都有自己的名字,我们随便看一个注解的源码:
我们可以通过注解中的这个Value属性来指定Bean的一个名字,如果我们没有指定名字,那么默认名字是类名首字母小写
比如上面这个类的Bean的名字默认就是empDaoA
也可以直接定义名字 方法如下:在注解后直接写
好的,灵魂提问又要来了😄😄,这四大注解声明Bean一定会生效吗?哈哈哈哈别崩溃继续研究:
Bean组件扫描: · 前面声明bean的四大注解,要想生效还需要被组件扫描注解 @ComponentScan扫描。 ·@ComponentScan注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包。
我们来看看 @SpringBootApplication的源码:
所以:默认扫描的范围是启动类所在包及其子包,(要是跟这个项目中的com.itheima这个包平级的话就会报错)
如果想要解决这个问题:
① 在启动类上再写一个 @ComponentScan注解,手动来设置包扫描
当然我们不推荐哈哈哈哈哈。(绝对没有白雪!!)
这里小小总结一下:
继续坚持就是胜利!!
前面我们说了IOC容器中同类型的只能放一个实现类进去(用到四个注解),但如果我在Service层里的实现类EmpServiceA和实现类EmpServiceB,头上都写上 @Service呢?
程序会报错滴~(不能完成自动装配,不能超过一个 同类型的Bean)
在都标注了 @Service的前提下,解决方法有下面几种:
- @Primary 提高优先级,用哪个就在 @Service上再加上这个注解
我也觉得有点脑残但肯定有这样做的意义hahaha~
- @Qualifier 直接在需要注解的变量声明处(源头)直接说<我要注入哪个Bean>这里注意Bean的名称默认是类名首字母小写,前面说过~
注意!除了最后一种 其余都是要依靠 @Autowired是按照类型进行注入
- @Resource 就直接不用 @Autowired了,因为它是直接按照名称进行注入。
总结!!!
我嘞个豆!这篇终于终于结束了!!真搓了一天啊又到了深夜🙄😬😨😅🤣😂🙃😊😉🤑 😊😉辛苦了辛苦了~
en~ 这章确实综合性比较强,通过自己一点一点的梳理,最后能理清、理解并且可以总结出自己的一篇心得 别说还真挺有成就感的嘿嘿嘿~
”只要一直在跑,就一定错不了!“peace
转载自:https://juejin.cn/post/7395021416570535973