2017/09/18

Guice

Guice 是 google 推出的一個輕量級依賴注入框架,解決Java中的 Dependency Injection 依賴注入問題,這個功能就像是 Spring 的 DI IoC。但因為 Spring 的框架 scope 龐大,如果只是想要一個單純的 DI library,那麼 Guice 是一個很好的選擇。

JSR 330 DI

在 Spring 誕生後,Google 也提供了另一個 DI 的實作 Guice,後來在 2009 年,定義了 JSR 330 DI 的規範,隨後 Spring 與 Guice 也都支援了 JSR 330。

javax.inject.* 提供了依賴注入的定義類別,但沒有限制依賴配置方式,
依賴配置方式取決於注入器的實作,injector 可以有多種配置設定的方式,可以基於XML、annotation、DSL(Domain-specific language),甚至是Java代碼,在 injector 實作的部分,可以採用反射、代碼生成技術等等,不受限制。


@Inject

可在constructor、field、method上使用,也可以在static 的非 final 的field、method上使用。使用該註解標註的constructor、field、method訪問修飾符 (private、package- private、protected、public 中任意一種) 不受限制。Injector在進行注入時,要按照constructors、fiedls、methods的順序進行。

對被標註 @Inject 的constructor的要求:

  • 在滿足上述說明的情況下,可以有其他的依賴作為方法的參數,別的要求倒沒有什麼。

對被標註 @Inject 的field的要求:

  • 不能是final

對被標註 @Inject 的method的要求:

  • 方法不能是abstract
  • 可以有其他的依賴作為該方法的參數

  • 當一個方法標註了 @Inject 並覆寫了其他標註了 @Inject 的方法時,對於每一個實例的每一次注入請求,該方法只會被注入一次。

  • 當一個方法沒有標註 @Inject 並覆寫了其他標註了 @Inject 的方法時,該方法不會被注入。


@Qualifier

用於標記限定器 annotation,用來指定採用哪個 class

假設 class A 有兩個 subclass A1,A2。B 依賴了A,那麼DI容器在為 B 的實例注入 A 時到底該注入 A1 或 A2 呢?

class B {
    @Inject
    A a;
}

解決方式是在 A1 及 A2 分別寫上 Qualifier 標記

@A_1
public class A1{
}

@A_2
public class A2{
}

在 class B 中指定 A1 或是 A2

class B{
    @Inject
    @A_1
     A a;
}

也可以用 @Named 進行標記

@Named("A1")
Public class A1{
}

@Named("A2")
Public class A2{
}

class B{
    @Inject
    @Named("A1")
    A a;
}

@Scope @Singleton

@Scope 用在 class 上,用來告訴 injector,為該 class 建立多少個 instances。

@Singleton 就是指產生一個 instance。

Guice example in scala sbt project

在 build.sbt 中加上 guice library

libraryDependencies += "com.google.inject" % "guice" % "4.1.0"

定義兩個 Service 介面,分別有 UserServiceImpl 及 LogServiceImpl 實作。

trait UserService {
  def process(): Unit
}

class UserServiceImpl extends UserService {
  override def process(): Unit = {
    System.out.println("UserServiceImpl in process")
  }
}

trait LogService {
  def log(msg: String): Unit
}

class LogServiceImpl extends LogService {

  override def log(msg: String): Unit = {
    System.out.println("log message:" + msg)
  }
}

定義 Application 介面,在 MyApp 實作的 constructor 中,引用了 UserService 及 LogService,將來由 guice 動態指定 UserServiceImpl 及 LogServiceImpl 實作。

import javax.inject.Inject

trait Application {
  def work(): Unit
}

class MyApp @Inject()(val userService: UserService, val logService: LogService) extends Application {
  override def work(): Unit ={
    userService.process()
    logService.log("MyApp is working")
  }
}

Guice 的 Module 定義,必須要 extends AbstractModule,在 configure 中,設定 class 實作的 Denpendency 關係。

import com.google.inject.AbstractModule

class AppModule extends AbstractModule {
  override protected def configure(): Unit = {
    bind(classOf[UserService]).to(classOf[UserServiceImpl])
    bind(classOf[LogService]).to(classOf[LogServiceImpl])

    bind(classOf[Application]).to(classOf[MyApp])
  }
}

scala 主程式,以 Guice.createInjector(new AppModule) 產生 injector,藉由 inject 取得 Application 的 instance,然後就能呼叫 work。

import com.google.inject.Guice

object Main {
  def main(args: Array[String]) {
    println("Main")

    val injector = Guice.createInjector(new AppModule)

    val myApp = injector.getInstance(classOf[Application])

    myApp.work()
  }
}

執行結果

Main
UserServiceImpl in process
log message:MyApp is working

如果要限制 MyApp 為 Singleton,可以在 MyApp 上加上 @Singleton

import javax.inject.{Inject, Singleton}

trait Application {
  def work(): Unit
}

@Singleton
class MyApp @Inject()(val userService: UserService, val logService: LogService) extends Application {
  override def work(): Unit ={
    userService.process()
    logService.log("MyApp is working")
  }
}

也可在 Module 中設定 class dependency 的地方,加上 .in(classOf[Singleton]) 的限制

bind(classOf[Application]).to(classOf[MyApp]).in(classOf[Singleton])

或是寫成 asEagerSingleton,在程式啟動時,就馬上產生 MyApp

bind(classOf[Application]).to(classOf[MyApp]).asEagerSingleton

Spring vs Guice

關於選擇Spring還是Google-Guice的一些想法

SpringComparison

以往的 Spring 是使用 xml 的方式定義 java bean,一般會認為 Guice 處理速度比 Spring 快,但可能只在啟動的時候有差異,因為 spring 需要讀取 xml 設定檔,而 guice 完全都是用程式碼處理的。

Guice 是由 Google 的 AdWords 專案誕生的,他不像是 Spring 整合了許多不同的 Java EE Framework,只是單純且專注在處理 Dependency Injection 的問題。在官方 Spring Comparison 文件中提到一個例子,他是由 Spring 轉換到 Guice,發現大約有 3/4 的程式碼是不需要的,用 Guice 寫的 module 程式碼短,且容易閱讀。

Guice 不支援以設定檔的方式設定 DI,完全是以 annotations 及 generics 程式碼的方式處理,因此可以達成動態 DI 的功能。

References

Guice簡明教程

Guice 快速入門

Guice Getting Started

Google Guice的動機

Java 依賴注入標準(JSR-330)簡介

JSR330 DI

沒有留言:

張貼留言