Play! Framework 系列(三):依赖注入
在Play! Framework 系列(二)中我们介绍了 Play 的项目结构。在日常处理业务逻辑的时候,我们都会用到依赖注入,本文将介绍一下 Play! 中的依赖注入以及如何合理地去使用她。
为什么要使用「依赖注入」
在许多 Java 框架中,「依赖注入」早已不是一个陌生的技术,Play 框架从 2.4 开始推荐使用 Guice 来作为依赖注入。
采用依赖注入最大的好处就是为了「解耦」,举个栗子:
在上一篇文章的例子中,我们实现了一个 EmployeeService 用来对公司的员工进行操作:
1 2 3 4 5 6 7 | package services import models._ classEmployeeSerivce{ ... } |
在之前的实现中,我们没有加入数据库的操作,那么现在我们想要引入一个数据库连接的类:DatabaseAccessService 来对数据库进行连接以便我们对相应的数据库表进行操作,则:
新建一个数据库连接操作的 Service:
1 2 3 | package services classDatabaseAccessService{} |
EmployeeSerivce 需要依赖 DatabaseAccessService:
1 2 3 4 5 6 7 | package services import models._ classEmployeeSerivce(db: DBService){ ... } |
好了,现在我们需要在 EmployeeController 中使用 EmployeeSerivce,如果不采用依赖注入,则:
1 2 3 4 5 6 7 8 9 10 11 | classEmployeeController@Inject() ( cc: ControllerComponents ) extends AbstractController(cc) { val db = newDatabaseAccessService() val employeeSerivce = newEmployeeSerivce(db) def employeeList = Action { implicit request: Request[AnyContent] => val employees = employeeSerivce.getEmployees() Ok(views.html.employeeList(employees)) } } |
可以看到,为了实例化 EmployeeSerivce,DatabaseAccessService 也需要实例化,如果随着需求的增加,EmployeeSerivce 所需要依赖的东西增加,那么我们每次实例化 EmployeeSerivce 的时候都需要将她的依赖也实例化一遍,而且她的依赖也有可能会依赖其他东西,这样就使得我们的代码变得非常冗余,也极难维护。
为了解决这一问题,我们引入了依赖注入,Play支持两种方式的依赖注入,分别是:「运行时依赖注入」以及「编译时依赖注入」,接下来我们就通过这两种依赖注入来解决我们上面提出的问题。
运行时依赖注入(runtime dependency)
Play 的运行时依赖注入默认采用 Guice,关于 Guice,我们后面的文章当中会介绍,这里只需要知道她。为了支持 Guice 以及其他的运行时依赖注入框架,Play 提供了大量的内置组件。详见 play.api.inject。
那么在 Play 中我们将如何使用这种依赖注入呢?回到我们文章刚开始讲的那个栗子中,现在我们通过依赖注入的方式来重新组织我们的代码:
首先 EmployeeSerivce 需要依赖 DatabaseAccessService,这里其实就存在一个「依赖注入」,那我们这样去实现:
1 2 3 4 5 6 7 8 | package services import models._ import javax.inject._ classEmployeeSerivce@Inject() (db: DBService){ ... } |
在上面的代码中,我们引入了 import javax.inject._,并且可以看到多了一个 @Inject() 注解,我们实现运行时依赖注入就采用该注解。
那么在 EmployeeController 中,我们的代码就变成了:
1 2 3 4 5 6 7 8 9 | classEmployeeController@Inject() ( employeeSerivce: EmployeeSerivce, cc: ControllerComponents ) extends AbstractController(cc) { def employeeList = Action { implicit request: Request[AnyContent] => val employees = employeeSerivce.getEmployees() Ok(views.html.employeeList(employees)) } } |
可以看到我们不需要再去写那么多的实例了,我们只要在需要某种依赖的地方声明一下我们需要什么样的依赖, Play 在运行时就会将我们需要的依赖注入到相应的组件中去。
tip:@Inject 必须放在类名的后面,构造参数的前面。
「运行时依赖注入」,顾名思义就是在程序运行的时候进行依赖注入,但是她不能在编译时进行校验,为了能让程序在编译时就能实现对依赖注入的校验, Play支持了「编译时依赖注入」。
编译时依赖注入(compile time dependency injection)
为了实现编译时依赖注入,我们需要知道 Play 提供的一个特质:ApplicationLoader,该特质中的 load 方法将会在程序启动的时候加载我们的应用程序,在这个过程中,Play 框架本身以及我们自己的程序代码所依赖的东西都会被实例化。
默认情况下,Play 提供了一个 Guice 模块,该模块下的 GuiceApplicationBuilder 会根据 Play 框架给定的 context 去将该程序所依赖的所有组件联系在一起。
如果我们要自定义 ApplicationLoader,我们也需要一个像 GuiceApplicationBuilder 的东西,好在 Play 提供了这么一个东西,那就是:BuiltInComponentsFromContext,我们可以通过继承这个类来实现我们自己的 ApplicationLoader。
接下来我们通过相应的代码来作进一步的解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import controllers._ import play.api._ import play.api.routing.Router import services._ import router.Routes //自定义 ApplicationLoader classMyApplicationLoaderextendsApplicationLoader { def load(context: Context): Application = { newMyComponents(context).application } } classMyComponents(context: Context) extendsBuiltInComponentsFromContext(context) with play.filters.HttpFiltersComponents { lazyval databaseAccessService = newDatabaseAccessService lazyval employeeSerivce = newEmployeeSerivce(databaseAccessService) lazyval employeeController = newEmployeeController(employeeSerivce, controllerComponents) lazyval router: Router = newRoutes(httpErrorHandler, employeeController) } |
我们通过继承 BuiltInComponentsFromContext 使得程序能够根据 Play 所提供的 context 来加载 Play 框架本身所需要的一些组件。
那么回到我们的「编译时的依赖注入」中来,可以看到在 class MyComponents 中,我们将所有的 service 都实例化了,并且将这些实例注入到相应的依赖她们的模块中:
1 2 3 4 5 6 7 8 | //将两个 service 实例化 lazyval databaseAccessService = newDatabaseAccessService //EmployeeSerivce 依赖 DatabaseAccessService,将实例 databaseAccessService 注入其中 lazyval employeeSerivce = newEmployeeSerivce(databaseAccessService) //将 employeeSerivce 注入到 employeeController 中 lazyval employeeController = newEmployeeController(employeeSerivce, controllerComponents) |
使用 BuiltInComponentsFromContext 时,我们需要自己实现一下 router:
1 | lazyval router: Router = newRoutes(httpErrorHandler, employeeController) |
tip:需要注意的是,如果我们实现了自己的 ApplicationLoader,我们需要在 application.conf 文件中声明一下:
1 | play.application.loader = MyApplicationLoader |
通过自定义 ApplicationLoader 我们就实现了编译时期的依赖注入,那么 EmployeeSerivce 就变成了:
1 2 3 4 5 6 7 | package services import models._ classEmployeeSerivce (db: DBService){ ... } |
可以看到,这里就省去了 @Inject() 注解。
同样的,对于 EmployeeController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package controllers import play.api._ import play.api.mvc._ import models._ import services._ // 没有了 @Inject() 注解 classEmployeeController ( employeeSerivce: EmployeeSerivce, cc: ControllerComponents ) extends AbstractController(cc) { ... } |
通过使用编译时期的依赖注入,我们只需要在将所有的依赖实例化一次就够了,并且使用这种方式,我们能够在编译时期就能发现程序的一些异常。同样的,使用该方法也会有一些问题,就是我们需要写许多样板代码。另外本文的编译时期的依赖注入完全是自己手动注入的,看上去也比较繁琐,不是那么直观,如果要使用更优雅的方式,我们可以使用 macwire,这个我们在后面的文章中会详细讲解。
结语
本文简单介绍了一下 Play 支持的两种依赖注入的模式,文中提到的一些第三方依赖注入的框架我们会在后面的文章中详细介绍。本文的例子请戳源码链接
本文转载自:https://scala.cool/2017/11/play-3/
相关文章
- 某PHP发卡系统SQL注入
- spring学习笔记(4)依赖注入详解
- 依赖倒置、控制反转和依赖注入辨析
- spring3: 依赖和依赖注入-xml配置-DI的配置
- MySQL数据库学习笔记(九)----JDBC的ResultSet接口(查询操作)、PreparedStatement接口重构增删改查(含SQL注入的解释)
- Scalaz(15)- Monad:依赖注入-Reader besides Cake
- Spring依赖注入原理分析
- 重新整理.net core 计1400篇[七] (.net core 中的依赖注入)
- SQL注入参数类型及提交方法
- ASP.NET Core 6框架揭秘实例演示[06]:依赖注入框架设计细节
- [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
- [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架
- [ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
- 在ABAP里模拟实现Java Spring的依赖注入
- SAP Spartacus 服务器端渲染的依赖注入之 ProductPageEventBuilder
- SAP Spartacus 定义在app.module.ts里的providers依赖注入元数据何时得到处理
- Angular 依赖注入里factory函数的调用时机
- Spring依赖注入(四):Bean的循环依赖是如何产生和解决的?
- 〖Python 数据库开发实战 - Python与MySQL交互篇②〗- SQL 注入攻击案例
- JS中注入eval, Function等系统函数截获动态代码
- 不用被动注入的方式, 主动从 Spring Boot 应用容器中的获取 Bean 的方法
- 转载:SQL注入演示demo
- mysql手工注入总结
- php 的依赖注入(汽车和轮胎的示例)
- Spring依赖注入:注解注入
- Spring依赖注入(五):盘一盘Spring的三级缓存
- 通过PreparedStatement预防SQL注入