Table of contents


All content is base on guice 2.x.

Base Concept

  • Module - 用來決定物件依賴關係的元件
  • Binders - 用來將物件綁定在一起的元件
    1. Linked Binding - 將類別與實作類別綁定在一起的元件
    2. Binding Annotations - 更細緻的描述要binding那個實作
    3. Instance Bindings - Binding instance到特定的類別
    4. @Provides Methods - Factory Method,用來提供特定型別的instance,只要有需有這個instance時,就會自動被binding
    5. Provider Bindings - 另一種型式的Factory Method
    6. Untargetted Bindings - 提供跟binder但不指定與誰進行binding,通常與 @ImplementedBy或@ProvidedBy搭配使用
    7. Built-in Bindings - 內建的bindings
      1. java.util.logging.Logger 如以binding logger
      2. Injector 可以把Injector inject到其它物件
      3. Providers 可以把 Provider inject到其它物件
      4. TypeLiterals 處理泛型用的
      5. The Stage
      6. MembersInjectors 可以對某個物件進行inject,這個很常用(由其是在測試時,用來inject mock object)
  • TBD
    1. AssistedInject
    2. PrivateModules

Hello Guice

    package com.kent;
     
    public interface Service {
        public String sayHelloTo(String name);
    }

    package com.kent;
     
    public class ServiceImpl implements Service {
        public String sayHelloTo(String id) {
            return "Hello! " + id;
        }
    }

    package com.kent;
     
    import com.google.inject.Inject;
     
    public class Client {
     
        @Inject
        private Service                service;
     
        public String doThing(String id) {
            return service.sayHelloTo(id);
        }
    }

    package com.kent;
     
    import static org.junit.Assert.assertEquals;
     
    import org.junit.Before;
    import org.junit.Test;
     
    import com.google.inject.AbstractModule;
    import com.google.inject.Guice;
    import com.google.inject.Inject;
    import com.google.inject.Injector;
    import com.google.inject.Module;
     
    public class TestClient {
     
        @Inject
        private Service service;
     
        @Before
        public void setUp() throws Exception {
     
            Module module = new AbstractModule() {
                @Override
                protected void configure() {
                    bind(Service.class).to(ServiceImpl.class);
                }
            };
            Injector injector = Guice.createInjector(module);
            injector.injectMembers(this);
        }
     
        @Test
        public void testInection() throws Exception {
            assertEquals("Hello! Kent", service.sayHelloTo("Kent"));
        }
     
    }

Using injector.injectMembers(this); to inject code back to test case,so that we can get the setted service for work.

Hello Guice on web

To enable Guice on web application, we need got the guice-serlet.jar and add some settings in web.xml

Guice是透過GuiceFilter取得threadlocal裡request,response等相關的content,所以必須要掛上這個filter。

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
        <display-name>Archetype Created Web Application</display-name>
        <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>
    </web-app>

Guice利用GuiceServletContextListener,在server啟動時,把Injector放到ServletContext裡,這樣,其他的servlet就可以從ServletCotext取得Injector。

    package com.kent;
     
    import javax.servlet.ServletContextEvent;
     
    import com.google.inject.Binder;
    import com.google.inject.Guice;
    import com.google.inject.Injector;
    import com.google.inject.Module;
    import com.google.inject.servlet.GuiceServletContextListener;
     
    public class GuiceServletConfig extends GuiceServletContextListener {
     
        public static final String KEY = Injector.class.getName();
     
        @Override
        public void contextDestroyed(ServletContextEvent servletContextEvent) {
            servletContextEvent.getServletContext().removeAttribute(KEY);
        }
     
        @Override
        public void contextInitialized(ServletContextEvent servletContextEvent) {
            servletContextEvent.getServletContext().setAttribute(KEY, getInjector());
        }
     
        @Override
        protected Injector getInjector() {
            return Guice.createInjector(new Module() {
                public void configure(Binder binder) {
                    binder.bind(Service.class).to(ServiceImpl.class);
                }
            });
        }
    }

在上面的動作中,Injector被放入ServletContext,在servlet的init(),可以取得Injector,然後用injector.injectMembers(this)對servlet進行injection

    package com.kent;
     
    import java.io.IOException;
     
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    import com.google.inject.Inject;
    import com.google.inject.Injector;
    import com.google.inject.Singleton;
     
    @Singleton
    public class HelloServlet extends HttpServlet {
     
        private static final long serialVersionUID = 1L;
        @Inject
        private Service           service;
     
        @Override
        public void init(ServletConfig config) throws ServletException {
     
            ServletContext context = config.getServletContext();
            Injector injector = (Injector) context.getAttribute(Injector.class.getName());
            if (injector == null) {
                throw new ServletException("Guice Injector not found");
            }
            injector.injectMembers(this);
        }
     
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.getOutputStream().print(service.sayHelloTo("web"));
        }
     
    }

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
        <display-name>Archetype Created Web Application</display-name>
        <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>
        <listener>
            <listener-class>com.kent.GuiceServletConfig</listener-class>
        </listener>
        <servlet>
            <servlet-name>HelloServlet</servlet-name>
            <servlet-class>com.kent.HelloServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>HelloServlet</servlet-name>
            <url-pattern>/*</url-pattern>
        </servlet-mapping>
    </web-app>

Scopes

Guice預設的scope是每次產生instnace,如果要變這個預設的行為,是透過scope,預設的scope有

  1. @Singleton - 讓物件變成Singleton,可以讓它宣告成Singleton (必須是threadsafe)
  2. @SessionScoped - 跟http session一樣長的scope (必須是threadsafe,必須正確安裝ServletModule)
  3. @RequestScoped - 每次的 http request才會新物件的scope (不用是threadsafe,必須正確安裝ServletModule)

如果class沒辦法用Annotation宣告(如比說該class來自其他lib),那可以在binding時,再指定scope,像這樣

    bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
    bind(UserPreferences.class).toProvider(UserPreferencesProvider.class).in(ServletScopes.REQUEST);
    // singleton可以是eager或lazy,eager或lazy是指contruct的方式
    bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();

Singleton Scope

Singleton可以省下很多物件建立及GC的資源,如果物件有以下特性,就可以考慮是否要宣告成Singleton,(,如果物件是stateless,建立新物件的速度,會比從singleton取快得多)

  • stateful objects, such as configuration or counters
  • objects that are expensive to construct or lookup
  • objects that tie up resources, such as a database connection pool.

雖然Singleton對java來說,一直常被列為anti-pattern(主要是因為Singleton的物件沒辦法被overwrite,失去了OO的特性), 尤其是採DI的開發方式而言,不過guice可以保留Singleton的優點,但卻沒有Singleton的缺點,所以可以放心使用

If the object truly is stateless, it’s faster for Guice to create a new instance than it is to retrieve a singleton. If Guice creates a new instance every time, it can bypass the scoping layer entirely, not to mention the logic inside the singleton scope.

Injection

Automatic Injection

Guice automatically injects all of the following:

  • instances passed to toInstance() in a bind statement
  • provider instances passed to toProvider() in a bind statement

The objects will be injected while the injector itself is being created. If they’re needed to satisfy other startup injections, Guice will inject them before they’re used.

Extensions

Multibindings

Multibindings是可以用來設計plugin的架構,主要的功能是把一個interface binding到多個實作上面,而且不需要recompile,可以透過config檔來加入新完成的plugins.

Resource