本文由 Shaw 发表在 ScalaCool 团队博客。

在 Play! Framework 系列(三)中我们简单介绍了一下 Play 框架自身支持的两种依赖注入(运行时依赖注入、编译时依赖注入)。相信大家对 Play! 的依赖注入应该有所了解了。本文将详细地介绍一些在日常开发中所采用的依赖注入的方式,以供大家进行合理地选择。

Guice 和 手动注入

在上一篇文章中我们所介绍的「运行时依赖注入」以及「编译时依赖注入」就是用的 Guice 以及手动注入,在这里就不作详细介绍了,大家可以去看看上篇文章以及相应的 Demo

接下来我们介绍比较常用的依赖注入模式。

cake pattern(蛋糕模式)

我们首先介绍一下 Scala 中比较经典的一种依赖注入的模式—— cake pattern(也叫“蛋糕模式”),“蛋糕模式”也属于「编译时依赖注入」的一种,她不需要依赖 DI 框架。那 “蛋糕模式” 是如何实现的呢?我们知道,在 Scala 中,多个 trait(特质)能够 “混入” 到 class 中,这样在某个 class 中我们就能够得到所有 trait 中定义的东西了。“蛋糕模式”就是基于此种特性而实现的。

接下来我们就通过一个例子来了解一下“蛋糕模式”:

我们需要在页面上显示一个包含所有会员信息的会员列表,需要显示的内容有:

  1. 会员信息
  2. 会员卡的信息

需求很简单,接下来我们用代码组织一下业务:

我们需要从数据库中查询「会员卡」以及「会员」的信息,所以这里我们首先定义一个数据库连接的类:DatabaseAccessService 来对相应的数据库进行操作:

trait DatabaseAccessServiceComp {val databaseAccessService = new DatabaseAccessService()
}class DatabaseAccessService{...
}
复制代码

大家可能会发现,在我们之前文章中的 service 中并没有定义 trait,而这里却定义了,并且在 trait 中,我们实例化了 DatabaseAccessService, 这就是“蛋糕模式”中所需要的,现在看好像并没有什么卵用,别急,等我们将所有的 service 都定义好了,她就有用了。

接下来我们定义 WxcardService 以及 WxcardMemberService:

//定义 WxcardService
trait WxcardServiceComp {this: DatabaseAccessServiceComp =>val wxcardService = new WxcardService(databaseAccessService)
}class WxcardService(databaseAccessService: DatabaseAccessService) {...
}//定义 WxcardMembrService
trait WxcardMemberServiceComp {this: DatabaseAccessServiceComp =>val wxcardMemberService = new WxcardMemberService(databaseAccessService)
}class WxcardMemberService(databaseAccessService: DatabaseAccessService) {...
}
复制代码

写法与上面定义的 DatabaseAccessService 没有什么区别,因为上面两个 service 都需要依赖 DatabaseAccessService,所以在特质中用「自身类型」来将其混入,如果需要多个依赖,可以这样写:

this DatabaseAccessServiceComp with BarComp with FooComp =>
复制代码

最后我们需要定义一个 WxcardController,来将数据传递到相应的页面上去:

class WxcardController (cc: ControllerComponents,wxcardService: WxcardService,wxcardMemberService: WxcardMemberService
) extends AbstractController(cc) {...}
复制代码

可以看到 WxcardController 需要依赖我们上面定义的一些 service,那么在蛋糕模式下,我们怎样才能将这些依赖注入到 WxcardController 中呢,由于“蛋糕模式”也是「编译时依赖注入」的一种,那么我们可以参考上一篇文章中所采用的方式:

同样,我们需要实现自己的 ApplicationLoader:

//定义 load 那部分代码省略了,大家可以去看 Demo
...class MyComponents(context: ApplicationLoader.Context)extends BuiltInComponentsFromContext(context)with play.filters.HttpFiltersComponentswith DatabaseAccessServiceCompwith WxcardServiceCompwith WxcardMemberServiceComp {lazy val wxcardController = new WxcardController(controllerComponents, wxcardService, wxcardMemberService)lazy val router: Router = new Routes(httpErrorHandler, wxcardController)
}
复制代码

通过上面的代码,就完成了注入,可以看到我们定义的所有 xxxServiceComp 特质都被混入到了 MyComponents 中,这样,当 Play加载时,我们所定义的 service 就都在这里被实例化了,为什么呢?因为我们在定义 xxxServiceComp 时,都会有这么一行代码:

val xxxService = new XxxService()
复制代码

这就是为什么我们之前要在每个 service 中都定义一个 trait,因为 Scala 中的 class 可以混入多个 trait,在这里,我们可以将所有需要的依赖都混入到 MyComponents 中,然后实现注入。

至于为什么要叫“蛋糕模式”,我个人是这么理解的: 我们定义的 xxxServiceComp 比如 WxcardServiceComp 相当于蛋糕中的某一层,而那些需要被多次依赖的 xxxServiceComp,比如上面定义的 DatabaseAccessServiceComp 可以看作是蛋糕中的调味料(比如水果,巧克力啥的),将这些蛋糕一层一层地放在一起,然后再混入一些调味料,就组成了一个大的蛋糕—— MyComponents。

可以看到“蛋糕模式”中,我们需要写非常多的样板代码,要为每个 service 都定义一个 trait,感觉心很累,那么接下来我们就介绍一种比较轻巧而又简洁的的方式。

macwire

macwire 是基于 「Scala 宏」来实现的,我们使用她可以让依赖注入变得非常简单,并且使我们的代码量减少许多。接下来,我们就通过 macwire 来实现一下上面的例子。

首先在项目中引入 macwire,在 build.sbt 文件中增加一行依赖:

libraryDependencies ++= Seq("org.scalatestplus.play"   %% "scalatestplus-play" % "3.0.0-M3" % Test,//在这里添加 macwire 的依赖"com.softwaremill.macwire" %% "macros"             % "2.3.0"    % Provided,
)
复制代码

然后定义 service:

//定义 DatabaseAccessServiceclass DatabaseAccessService{...
}//定义 WxcardServiceclass WxcardService(databaseAccessService: DatabaseAccessService) {...
}//定义 WxcardMembrServiceclass WxcardMemberService(databaseAccessService: DatabaseAccessService) {...
}
复制代码

可以看到,我们现在就不需要定义 trait 了,接下来,定义 WxcardController:

class WxcardController (cc: ControllerComponents,wxcardService: WxcardService,wxcardMemberService: WxcardMemberService
) extends AbstractController(cc) {...}
复制代码

controller 的定义和上面的一样,接下来,我们就使用 macwire 来实现依赖注入,macwire 也是「编译时依赖注入」的一种,所以我们同样需要实现 ApplicationLoader:

import com.softwaremill.macwire._
...class MyComponents(context: ApplicationLoader.Context)extends BuiltInComponentsFromContext(context)with play.filters.HttpFiltersComponents {lazy val databaseAccessService = wire[DatabaseAccessService]lazy val wxcardService = wire[WxcardService]lazy val wxcardMemberService = wire[WxcardMemberService]lazy val wxcardController = wire[WxcardController]lazy val router: Router = {val prefix = "/"wire[Routes]}
}
复制代码

在上面的代码中,我们只需要将相应的依赖通过下面的方式实例化就可以了:

lazy val wxcardService = wire[WxcardService]
复制代码

就是在类型外面添加了一个 wire,这样就完成了实例化,并且也不需要指定依赖的参数,macwire 会自动帮我们完成实例化和注入:

比如上面的

lazy val databaseAccessService = wire[DatabaseAccessService]
lazy val wxcardService = wire[WxcardService]
lazy val wxcardMemberService = wire[WxcardMemberService]
lazy val wxcardController = wire[WxcardController]
复制代码

macwire 就帮我们转化成了:

lazy val databaseAccessService = new DatabaseAccessService()
lazy val wxcardService = new WxcardService(databaseAccessService)
lazy val wxcardMemberService = new WxcardMemberService(databaseAccessService)
lazy val wxcardController = new WxcardController(controllerComponents, wxcardService, wxcardMemberService)
复制代码

我们只需要在定义某个类的时候声明我们需要哪些依赖,实例化和注入 macwire 都会帮我们去完成,macwire 在实例化某个类的时候,会去当前文件或者与当前文件有关的代码中查找相关的依赖,找到了就完成注入,若没有找到说明该依赖没有被定义过,或者没有正确引入。

在日常开发中,我们会创建很多个 service,将所有的 service 放在 MyComponents 中实例化会使得代码显得很臃肿,而且也不便于维护。通常我们会专门定义一个 Module 来组织这些 service:

package configimport com.softwaremill.macwire._
import services._trait ServicesModule {lazy val databaseAccessService = wire[DatabaseAccessService]lazy val wxcardService = wire[WxcardService]lazy val wxcardMemberService = wire[WxcardMemberService]
}复制代码

这里我们新建了一个 ServiceModule.scala 文件来将组织这些 service。

那么上面的 ApplicationLoader 文件就可以这样去写:

import com.softwaremill.macwire._
...class MyComponents(context: ApplicationLoader.Context)extends BuiltInComponentsFromContext(context)with play.filters.HttpFiltersComponentswith config.ServicesModule {lazy val wxcardController = wire[WxcardController]lazy val router: Router = {val prefix = "/"wire[Routes]}
}
复制代码

可以看到 macwire 使用起来非常简单,并且能够简化我们的依赖注入。在我们的项目中所采用的是 macwire,所以推荐大家使用 macwire。

结语

关于 Play 中的「依赖注入」到这里就结束了,希望能够给大家一些帮助,另外 Play 系列的文章从上一篇到现在拖了太久了,非常抱歉,感谢一直以来的关注,后面我会加快写作节奏的,本文的例子请戳源码链接。

Play! Framework 系列(四):DI 模式比较相关推荐

  1. ASP.NET企业开发框架IsLine FrameWork系列之十四--框架配置信息大全(中)

    ASP.NET企业开发框架IsLine FrameWork系列之十四--框架配置信息大全(中) 接上文 上文中讲到配置日志模块的第二步,这篇文章继续给大家介绍日志配置方法. Step 3.在</ ...

  2. 在X++中使用IoC/DI模式应对不断变化的客户需求

    IoC/DI(Inverse of Control/Dependency Injection,控制反转/依赖注入)模式是一种企业级架构模式,通过将应用程序控制权反转交移给框架,并以构造器注入.属性设置 ...

  3. hadoop系列四:mapreduce的使用(二)

    转载请在页首明显处注明作者与出处 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的一些内容,如hadoop,spark,storm,机器学习等. 当前使用的hadoop版本为2.6 ...

  4. spi协议时序图和四种模式实际应用详解

    大家好,我是无际. 上个章节我们讲解了spi接口定义,今天我们更加深入讲解下spi协议时序图和spi四种模式的用法. 刚开始接触单片机开发时,最怕就是看时序图,对于我来说就是奇怪的知识. 特别是SPI ...

  5. sed修炼系列(四):sed中的疑难杂症

    sed系列文章: sed修炼系列(一):花拳绣腿之入门篇 sed修炼系列(二):武功心法(info sed翻译+注解) sed修炼系列(三):sed高级应用之实现窗口滑动技术 sed修炼系列(四):s ...

  6. Play! Framework 系列(三):依赖注入

    在Play! Framework 系列(二)中我们介绍了 Play 的项目结构.在日常处理业务逻辑的时候,我们都会用到依赖注入,本文将介绍一下 Play! 中的依赖注入以及如何合理地去使用她. 为什么 ...

  7. java面试常考系列四

    转载自 java面试常考系列四 题目一 大O符号(big-O notation)的作用是什么?有哪些使用方法? 大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好 ...

  8. android夜间模式揭露动画,Android Material Design系列之夜间模式

    今天我们讲讲夜间模式的实现,这篇文章的名字应该叫:<Android Material Design系列之夜间模式>.在Android 5.0 之后,实现夜间模式并非很难了,支持的5.0库提 ...

  9. ASP.NET企业开发框架IsLine FrameWork系列之十五--框架配置信息大全(下)

    ASP.NET企业开发框架IsLine FrameWork系列之十五--框架配置信息大全(下) 接上文   三.IsLine.ExceptionProcess 部分 这部分主要管理异常,对异常的管理分 ...

最新文章

  1. mina android 服务器,MINA框架----------android客户端与服务器端
  2. 客户端如何通过咏南中间件调用存储过程和数据分页查询和文件传输的演示
  3. make j4什么意思_为什么天天坚持撸铁 肌肉增长不明显
  4. enq: TT - contention等待事件
  5. 李飞飞离职Google重返斯坦福,CMU计算机学院院长Andrew Moore接任
  6. 如何在Android Studio中获取SHA-1指纹证书以获得调试模式?
  7. CheerpJ调用的两种方式
  8. libcaffe.so: undefined reference to `cv.read
  9. java中final类调用_Java中final的使用
  10. 如何共享OneNote笔记本
  11. Qt打包程序报错“应用程序无法正常启动(0xc000007b)”
  12. All-Pay Contests 论文定理推导(博弈论+机制设计)
  13. linux怎么进入系统安装界面,Linux图形化界面安装全过程
  14. WPF自定义分页控件
  15. 光盘镜像和系统启动盘制作
  16. 国内公有云对比(1.5)- 功能篇之青云
  17. 关于 IBM Tivoli Storage Manager在Oracle中的应用
  18. 802.11ac linux驱动下载,下载的驱动程序Realtek 8811CU Wireless LAN 802.11ac USB NIC 1030.22.0405.2017...
  19. 深度学习入门(一)——深度学习是什么?
  20. 目标跟踪介绍(单目标)

热门文章

  1. dataframe两个表合并_R语言读取多个excel文件后合并:rbind/merge/cmd合并
  2. Android 对话框(Dialog)大全 建立你自己的对话框
  3. springboot socket服务端_从零开始学SpringBoot之Spring Boot WebSocket:编码分析
  4. vue项目没有router文件夹_vueRouter没有报错,但是页面渲染空白
  5. 虚拟机上部署的项目 访问路径怎么写_桌面虚拟化即将流行开来——基于Hyper-V虚拟机的桌面虚拟化部署...
  6. xpath以某个字符开始_XPATH技术补充-实例
  7. pythonshell画图_Python3使用plotly模块保存图片与shell下生成表格
  8. 弱网测试用什么农_为什么用木蜡油做的家具,用甲醛测试仪测试会显示甲醛超标?...
  9. windows下 mysql 转移data目录
  10. java面试题十四 基本类型的默认值