Google Guice 快速入門

Guice是Google開源的一個依賴注入類庫,相比於Spring IoC來講更小更快。Elasticsearch大量使用了Guice,本文簡單的介紹下Guice的基本概念和使用方式css

基本使用

1. 引入依賴

若是使用gradle的話,添加下列依賴html

compile group'com.google.inject.extensions', name: 'guice-multibindings', version: '4.2.0'
compile group'com.google.inject', name: 'guice', version: '4.2.0'

當構建工具解決完項目的依賴以後,咱們就能夠開始使用Guice了java

項目骨架

咱們來假設一個簡單的項目框架。首先咱們須要一個業務接口,簡單的包含一個方法用於執行業務邏輯。  
它的實現也很是簡單mysql

// UserService接口
public interface UserService {
    void process();
}

// 實現類
public class UserServiceImpl implements UserService {
    @Override
    public void process() {
        System.out.println("我須要作一些業務邏輯");
    }
}

而後咱們須要一個日誌接口,它和它的實現也很是簡單web

// 日誌接口
public interface LogService {
    void log(String msg);
}

// 實現類
public class LogServiceImpl implements LogService {
    @Override
    public void log(String msg) {
        System.out.println("------LOG:" + msg);
    }
}

最後是一個系統接口和相應的實現。  
在實現中咱們使用了業務接口和日誌接口處理業務邏輯和打印日誌信息。sql

public interface Application {
    void work();
}

public class MyApp implements Application {
    private UserService userService;
    private LogService logService;

    @Inject
    public MyApp(UserService userService, LogService logService) {
        this.userService = userService;
        this.logService = logService;
    }

    @Override
    public void work() {
        userService.process();
        logService.log("程序正常運行");
    }
}

在 MyApp 類中定義了 UserService 和 LogService 兩個變量,可是尚未給它們建立對象,而 word 方法中分別調用了 process 和 log 方法,它們的實際執行結果由最終注入的對象決定api

簡單的依賴注入

首先來配置依賴關係。咱們繼承AbstractModule類,並重寫configure方法便可。在configure方法中,咱們能夠調用AbstractModule類提供的一些方法來配置依賴關係。安全

最經常使用的方式就是 bind(接口或父類).to(實現類或子類) 的方式來設置依賴關係微信

public class MyAppModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(LogService.class).to(LogServiceImpl.class);
        bind(UserService.class).to(UserServiceImpl.class);
        bind(Application.class).to(MyApp.class);
    }
}

這樣一來,當Guice遇到接口或父類須要注入具體實現的時候,就會使用這裏配置的實現類或子類來注入。  
若是但願在構造器中注入依賴的話,只須要添加 @Inject 註解便可session

Guice配置完以後,咱們須要調用 Guice.createInjector 方法傳入配置類來建立一個注入器,而後使用注入器的 getInstance 方法獲取目標類,Guice會按照配置幫咱們注入全部依賴。

咱們使用單元測試來看看效果

public class MyAppTest {
    private static Injector injector;

    @BeforeClass
    public static void init() {
        injector = Guice.createInjector(new MyAppModule());
    }

    @Test
    public void testMyApp() {
        Application myApp = injector.getInstance(Application.class);
        myApp.work();
    }
}

//程序結果
//我須要作一些業務邏輯
//------LOG:程序正常運行

依賴綁定

下面這些例子都是Guice文檔上的例子

鏈式綁定

咱們在綁定依賴的時候不只能夠將父類和子類綁定,還能夠將子類和更具體的子類綁定。

下面的例子中,當咱們須要 TransactionLog 的時候,Guice最後會爲咱們注入 MySqlDatabaseTransactionLog 對象。

// 鏈式綁定:TransactionLog -> DatabaseTransactionLog -> MySqlDatabaseTransactionLog
public class BillingModule extends AbstractModule {
  @Override 
  protected void configure() {
    bind(TransactionLog.class).to(DatabaseTransactionLog.class);
    bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
  }
}

註解綁定

當咱們須要將多個同一類型的對象注入不一樣對象的時候,就須要使用註解區分這些依賴了。  
最簡單的辦法就是使用 @Named 註解進行區分

首先須要在要注入的地方添加 @Named 註解

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
      TransactionLog transactionLog) 
{
    ...
  }

而後在綁定中添加 annotatedWith 方法指定 @Named 中指定的名稱。  
因爲編譯器沒法檢查字符串,因此Guice官方建議咱們保守地使用這種方式

bind(CreditCardProcessor.class)
        .annotatedWith(Names.named("Checkout"))
        .to(CheckoutCreditCardProcessor.class);

若是但願使用類型安全的方式,能夠自定義註解

@BindingAnnotation 
@Target({ FIELD, PARAMETER, METHOD }) 
@Retention(RUNTIME)
public @interface PayPal {

}

而後在須要注入的類上應用

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@PayPal CreditCardProcessor processor,
      TransactionLog transactionLog)
 
{
    ...
  }

在配置類中,使用方法也和@Named相似

bind(CreditCardProcessor.class)
        .annotatedWith(PayPal.class)
        .to(PayPalCreditCardProcessor.class);

實例綁定

有時候須要直接注入一個對象的實例,而不是從依賴關係中解析。  
若是咱們要注入基本類型的話只能這麼作

bind(String.class)
        .annotatedWith(Names.named("JDBC URL"))
        .toInstance("jdbc:mysql://localhost/pizza");

bind(Integer.class)
        .annotatedWith(Names.named("login timeout seconds"))
        .toInstance(10);

若是使用 toInstance 方法注入的實例比較複雜的話,可能會影響程序啓動。這時候可使用 @Provides 方法代替

@Provides方法

當一個對象很複雜,沒法使用簡單的構造器來生成的時候,咱們可使用 @Provides 方法,也就是在配置類中生成一個註解了 @Provides 的方法。

在該方法中咱們能夠編寫任意代碼來構造對象

public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    ...
  }

  @Provides
  TransactionLog provideTransactionLog() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
    transactionLog.setThreadPoolSize(30);
    return transactionLog;
  }
}

@Provides 方法也能夠應用 @Named 和自定義註解,還能夠注入其餘依賴,Guice會在調用方法以前注入須要的對象

  @Provides 
  @PayPal
  CreditCardProcessor providePayPalCreditCardProcessor(
        @Named("PayPal API key"String apiKey) {
    PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
    processor.setApiKey(apiKey);
    return processor;
  }

Provider綁定

若是項目中存在多個比較複雜的對象須要構建,使用 @Provide 方法會讓配置類變得比較亂。咱們可使用Guice提供的 Provider接口 將複雜的代碼放到單獨的類中。辦法很簡單,實現 Provider<T> 接口的get方法便可。在 Provider 類中,咱們可使用 @Inject 任意注入對象

public class DatabaseTransactionLogProvider implements Provider<TransactionLog{
  private final Connection connection;

  @Inject
  public DatabaseTransactionLogProvider(Connection connection) {
    this.connection = connection;
  }

  public TransactionLog get() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setConnection(connection);
    return transactionLog;
  }
}

在配置類中使用 toProvider 方法綁定到 Provider 上便可

  public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(TransactionLog.class)
        .toProvider(DatabaseTransactionLogProvider.class);
  }

做用域

默認狀況下Guice會在每次注入的時候建立一個新對象。若是但願建立一個單例依賴的話,能夠在實現類上應用 @Singleton 註解

@Singleton
public class InMemoryTransactionLog implements TransactionLog {
  /* everything here should be threadsafe! */
}

或者也能夠在配置類中指定

bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);

在@Provides方法中也能夠指定單例

  @Provides 
  @Singleton
  TransactionLog provideTransactionLog() {
    ...
  }

若是一個類型上存在多個衝突的做用域,Guice會使用 bind() 方法中指定的做用域。若是不想使用註解的做用域,能夠在 bind() 方法中將對象綁定爲 Scopes.NO_SCOPE

Guice和它的擴展提供了不少做用域,有單例Singleton,Session做用域SessionScoped,Request請求做用域RequestScoped等等。咱們能夠根據須要選擇合適的做用域

Servlet集成

Guice也能夠和Servlet項目集成,這樣咱們就能夠不用編寫冗長的 web.xml,以依賴注入的方式使用Servlet和相關組件

安裝Guice Servlet擴展

要在Servlet項目中集成Guice,首先須要安裝Guice Servlet擴展。若是使用Maven,添加下面的依賴。

compile group'com.google.inject.extensions', name: 'guice-servlet', version: '4.2.0'

加依賴以後,在 web.xml 中添加下面的代碼,讓Guice過濾全部web請求

<filter>
    <filter-name>guiceFilter</filter-name>
    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>guiceFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

配置Injector

和普通的項目同樣,Servlet項目一樣須要配置Injector。一種比較好的辦法就是在 ContextListener 中配置 Injector。Guice的Servlet集成提供了 GuiceServletContextListener,咱們繼承該類並在 getInjector 方法中配置 Injector 便可。

public class MyGuiceServletConfigListener extends GuiceServletContextListener {

  @Override
  protected Injector getInjector() {
    return Guice.createInjector(new ServletModule());
  }
}

固然僅僅繼承 GuiceServletContextListener 仍是不夠的。咱們還須要在 web.xml 中添加幾行代碼

<listener>
    <listener-class>yitian.study.servlet.MyGuiceConfigListener</listener-class>
</listener>

配置Servlet和過濾器

默認的ServletModule就會啓用一些功能。若是須要自定義Servlet和Filter,就須要繼承ServletModule並重寫configureServlets()方法。配置Servlet和Filter的方法和配置普通依賴注入相似

public class MyServletConfig extends ServletModule {
    @Override
    protected void configureServlets() {
        serve("/*").with(MainServlet.class);
        filter("/*").through(CharEncodingFilter.class);
    }
}

而後將ServletModule替換爲咱們本身的實現類

public class MyGuiceConfigListener extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(new MyServletConfig());
    }
}

注入Servlet相關對象

除了配置Servlet以外,Guice還容許咱們把Request、Response和Session對象注入到非Servlet對象中。下面是Guice的一個例子

@RequestScoped
class SomeNonServletPojo {

  @Inject
  SomeNonServletPojo(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
    ...
  }
}

咱們還可使用Guice注入請求參數。下面這個類的做用是獲取全部請求參數並轉換爲字符串形式。

@RequestScoped
public class Information {
    @Inject
    @RequestParameters
    Map<StringString[]> params;

    public String getAllParameters() {
        return params.entrySet()
                .stream()
                .map(entry -> entry.getKey() + " : " + Arrays.toString(entry.getValue()))
                .collect(Collectors.joining(", "));
    }
}

以後,咱們就能夠將該對象注入到Servlet中使用,將結果返回給頁面

@Singleton
public class MainServlet extends HttpServlet {
    @Inject
    private Injector injector;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        req.setAttribute("name", name);
        Information info = injector.getInstance(Information.class);
        req.setAttribute("params", info.getAllParameters());
        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }

    @Override
    public void init() throws ServletException {
        super.init();
    }
}

Guice Servlet擴展還有其餘功能,這裏就不在列舉了。詳情請參看Guice文檔

JSR-330標準

JSR-330是一項Java EE標準,指定了Java的依賴注入標準。Spring、Guice和Weld等不少框架都支持JSR-330。下面這個表格來自於Guice文檔,咱們能夠看到JSR-330和Guice註解基本上能夠互換。

JSR-330

Guice官方推薦咱們首選JSR-330標準的註解。

以上就是Guice的基本知識了。若是須要更詳細的使用方法,請參考Guice文檔

本文代碼可在 CSDN code 下載

本文轉載自:http://www.what21.com/article/b_java_1491385837669.html 


本文分享自微信公衆號 - 小旋鋒(whirlysBigData)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索