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 是使用 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 的功能。
沒有留言:
張貼留言