12 2月 2010

Guice Injector and Spring AnnotationConfigApplicationContext

目前Dependency Injection Framework比較活躍的除了Spring外就是Google的Guice了,Guice以Annotation為主,不需要複雜的設定檔,很容易就能上手,而且所需要的Library很小,對於一些比較小的系統,不希望使用太多Library的開發者而言(例如Android),Guice提供了一個較Spring有利的DI Framework。

Spring在使用Annotation上有些舊包袱,但在JSR-299,JSR-330後也逐漸為大家接受,但是在3.0之前,仍需要一個XML設定檔,相較Guice完全不用的情形下是有些許的不便(當然2.5自己加工一下也是可以達成不用讀取XML而直接使用Annotaion)。

Spring3.0多了個AnnotationConfigApplicationContext,可以讓我們完全不用讀取任何XML的檔案就能依Annotation完成DI的組裝工作,下面就簡單列一下兩種DI Framework的做法吧。

package org.elliot.di;

public interface Module {
 public String getModuleName();
}
package org.elliot.di;

import org.springframework.stereotype.Component;

@Component //Spring component => a bean
public class DefaultModule implements Module{
 public String getModuleName() {
  return "Default";
 }
}
訂了一個非常沒用的Interface,再實作一個很無聊的Implementation,DefaultModule上訂的@Component是Spring自定的,也可以改用JSR-299所定的@Resource,這個的做用基本上就是將它當做是之前Spring xml configuration中所訂的一個bean

package org.elliot.di;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.inject.Inject;

@Component //Spring component => a bean
public class Service {
 @Autowired //Spring Autowired
 @Inject //Guice Inject
 private Module module;
 
 public Module getModule() {
  return module;
 }

 public void setModule(Module module) {
  this.module = module;
 }

 public void showModuleName() {
  System.out.println(this.module.getModuleName());
 }
}
Service提供一個被注入的標的module,@Autowired是Spring的Annotaion,@Inject則是Guice的Annotaion,做用雷同,代表這是一個可以被注入的Field。

再來是Spring與Guice想法不同之處,Spring必需把Service也定為一個Component,這樣才可以透過BeanFactory或是Context取得,但Guice則不用,你可以留到你程式要用時再透過Guice Container來組裝,Spring目前似乎沒有這樣的想法(不確定...)。我比較想要的是可以自行new 一個Service instance,再丟給DI Container來將所需要的東西注入。


Guice雖然沒有設定檔,但你還是需要一個AbstractModule來指出一個組裝的需求,就像下列這樣,

package org.elliot.guice;

import org.elliot.di.DefaultModule;
import org.elliot.di.Module;

import com.google.inject.AbstractModule;

public class GuiceConfigModule extends AbstractModule {

 @Override
 protected void configure() {
  bind(Module.class).to(DefaultModule.class);
 }

}
必需要extends AbstractModule,實做protected void configure();這裡指定了只要Field型態是Module的都用DefaultModule的instance來注入。


再來就是簡單的測試,順便展示基本的用法

package org.elliot.guice;

import static org.junit.Assert.assertNotNull;

import org.elliot.di.Service;
import org.junit.Before;
import org.junit.Test;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class GuiceDITest {
 private Service service;
 
 @Before
 public void setUp() throws Exception {
  Injector injector = Guice.createInjector(new GuiceConfigModule());
  service = injector.getInstance(Service.class);
 }
 
 @Test
 public void testGuice() {
  assertNotNull(service.getModule());
  service.showModuleName();
 }
}
這是Guice的簡單測試,例用Guice.createInjector來產生一個Injector,這個Injector就同於Spring的Context,你需要相關的instance都跟Injector要。


Spring的也很簡單

package org.elliot.spring;

import static org.junit.Assert.assertNotNull;

import org.elliot.di.Service;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringDITest {
 private Service service;

 @Before
 public void setUp() throws Exception {
  AnnotationConfigApplicationContext context = 
   new AnnotationConfigApplicationContext("org.elliot");
  service = context.getBean(Service.class);
 }

 @Test
 public void testGuice() {
  assertNotNull(service.getModule());
  service.showModuleName();
 }
}
基本上就是將之前常用的ClassPathXmlApplicationContext, FileSystemXmlApplicationContext換成AnnotationConfigApplicationContext。

兩個TestCase要做的事完全一樣,看得出來Spring也能縮減相當程度的複雜度,但是Guice在速度跟耗用記憶體上還是具有優勢,只是我又少了一個用Guice的理由...

沒有留言: