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

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

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

为何要使用「依赖注入」

在许多 Java 框架中,「依赖注入」早已不是一个陌生的技术,Play 框架从 2.4 开始推荐使用 Guice 来做为依赖注入。git

采用依赖注入最大的好处就是为了「解耦」,举个栗子:github

上一篇文章的例子中,咱们实现了一个 EmployeeService 用来对公司的员工进行操做:数据库

package services

import models._

class EmployeeSerivce{
  ...
}
复制代码

在以前的实现中,咱们没有加入数据库的操做,那么如今咱们想要引入一个数据库链接的类:DatabaseAccessService 来对数据库进行链接以便咱们对相应的数据库表进行操做,则:api

新建一个数据库链接操做的 Service:mvc

package services

class DatabaseAccessService{}
复制代码

EmployeeSerivce 须要依赖 DatabaseAccessService:app

package services

import models._

class EmployeeSerivce(db: DBService){
  ...
}
复制代码

好了,如今咱们须要在 EmployeeController 中使用 EmployeeSerivce,若是不采用依赖注入,则:框架

class EmployeeController @Inject() ( cc: ControllerComponents ) extends AbstractController(cc) {
  val db = new DatabaseAccessService()
  val employeeSerivce = new EmployeeSerivce(db)

  def employeeList = Action { implicit request: Request[AnyContent] =>
    val employees = employeeSerivce.getEmployees()
    Ok(views.html.employeeList(employees))
  }
}
复制代码

能够看到,为了实例化 EmployeeSerivce,DatabaseAccessService 也须要实例化,若是随着需求的增长,EmployeeSerivce 所须要依赖的东西增长,那么咱们每次实例化 EmployeeSerivce 的时候都须要将她的依赖也实例化一遍,并且她的依赖也有可能会依赖其余东西,这样就使得咱们的代码变得很是冗余,也极难维护。ui

为了解决这一问题,咱们引入了依赖注入,Play支持两种方式的依赖注入,分别是:「运行时依赖注入」以及「编译时依赖注入」,接下来咱们就经过这两种依赖注入来解决咱们上面提出的问题。

运行时依赖注入(runtime dependency)

Play 的运行时依赖注入默认采用 Guice,关于 Guice,咱们后面的文章当中会介绍,这里只须要知道她。为了支持 Guice 以及其余的运行时依赖注入框架,Play 提供了大量的内置组件。详见 play.api.inject

那么在 Play 中咱们将如何使用这种依赖注入呢?回到咱们文章刚开始讲的那个栗子中,如今咱们经过依赖注入的方式来从新组织咱们的代码:

首先 EmployeeSerivce 须要依赖 DatabaseAccessService,这里其实就存在一个「依赖注入」,那咱们这样去实现:

package services

import models._
import javax.inject._

class EmployeeSerivce @Inject() (db: DBService){
  ...
}
复制代码

在上面的代码中,咱们引入了 import javax.inject._,而且能够看到多了一个 @Inject() 注解,咱们实现运行时依赖注入就采用该注解。

那么在 EmployeeController 中,咱们的代码就变成了:

class EmployeeController @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。

接下来咱们经过相应的代码来做进一步的解释:

import controllers._
import play.api._
import play.api.routing.Router
import services._
import router.Routes


//自定义 ApplicationLoader
class MyApplicationLoader extends ApplicationLoader {
  def load(context: Context): Application = {
    new MyComponents(context).application
  }
}

class MyComponents(context: Context)
    extends BuiltInComponentsFromContext(context)
    with play.filters.HttpFiltersComponents {

  lazy val databaseAccessService = new DatabaseAccessService

  lazy val employeeSerivce = new EmployeeSerivce(databaseAccessService)

  lazy val employeeController = new EmployeeController(employeeSerivce, controllerComponents)

  lazy val router: Router = new Routes(httpErrorHandler, employeeController)
}
复制代码

咱们经过继承 BuiltInComponentsFromContext 使得程序可以根据 Play 所提供的 context 来加载 Play 框架自己所须要的一些组件。

那么回到咱们的「编译时的依赖注入」中来,能够看到在 class MyComponents 中,咱们将全部的 service 都实例化了,而且将这些实例注入到相应的依赖她们的模块中:

//将两个 service 实例化
lazy val databaseAccessService = new DatabaseAccessService

//EmployeeSerivce 依赖 DatabaseAccessService,将实例 databaseAccessService 注入其中
lazy val employeeSerivce = new EmployeeSerivce(databaseAccessService)

//将 employeeSerivce 注入到 employeeController 中
lazy val employeeController = new EmployeeController(employeeSerivce, controllerComponents)
复制代码

使用 BuiltInComponentsFromContext 时,咱们须要本身实现一下 router:

lazy val router: Router = new Routes(httpErrorHandler, employeeController)
复制代码

tip:须要注意的是,若是咱们实现了本身的 ApplicationLoader,咱们须要在 application.conf 文件中声明一下:

play.application.loader = MyApplicationLoader
复制代码

经过自定义 ApplicationLoader 咱们就实现了编译时期的依赖注入,那么 EmployeeSerivce 就变成了:

package services

import models._

class EmployeeSerivce (db: DBService){
  ...
}
复制代码

能够看到, 这里就省去了 @Inject() 注解。

一样的,对于 EmployeeController:

package controllers

import play.api._
import play.api.mvc._
import models._
import services._

// 没有了 @Inject() 注解
class EmployeeController ( employeeSerivce: EmployeeSerivce, cc: ControllerComponents ) extends AbstractController(cc) {
  ...
}
复制代码

经过使用编译时期的依赖注入,咱们只须要在将全部的依赖实例化一次就够了,而且使用这种方式,咱们可以在编译时期就能发现程序的一些异常。一样的,使用该方法也会有一些问题,就是咱们须要写许多样板代码。另外本文的编译时期的依赖注入彻底是本身手动注入的,看上去也比较繁琐,不是那么直观,若是要使用更优雅的方式,咱们可使用 macwire,这个咱们在后面的文章中会详细讲解。

结语

本文简单介绍了一下 Play 支持的两种依赖注入的模式,文中提到的一些第三方依赖注入的框架咱们会在后面的文章中详细介绍。本文的例子请戳源码连接

相关文章
相关标签/搜索