Play框架提供基于JSR 330的依賴注入。Play默認的JSR 330的實現是用Guice實現的,但是其它的JSR 330實現也可以用。
如果你有個組件(例如Controller)需要依賴其它組件,那么你就可以通過使用@Inject注解來聲明。@Inject注解可以在屬性或者構造方法上使用。例如,注入一個屬性:
import javax.inject.*;import play.libs.ws.*;public class MyComponent { @Inject WSClient ws; // ...} 注意這些是實例變量,通常情況下注入一個靜態屬性是沒有意義的,因為它會失去封裝的意義。另一個例子,注入一個構造方法:
import javax.inject.*;import play.libs.ws.*;public class MyComponent { PRivate final WSClient ws; @Inject public MyComponent(WSClient ws) { this.ws = ws; } // ...} 雖然屬性注入更短,但比較推薦使用構造方法注入的方式。這樣更可測試,由于在單元測試中你需要傳入所有的構造方法參數來創建一個對象實例,編譯器需要確保依賴存在。且由于沒有setter的存在,構造方法注入的方式也更易于理解。3.組件的生命周期
依賴注入系統管理注入組件的生命周期,當需要時創建它們然后將其注入到其它組件中。以下是組件生命周期如何工作的介紹:
在任何時候,當某個組件被需要時就被創建。如果一個組件不止一次的被用到,默認情況下,將會創建多個組件實例。如果你只想創建一個該組件的單例,你需要用@Singleton注解將其標記為一個單例。實例在它們被需要時會懶加載。如果一個組件從來都未被其它組件使用,那么它根本不會被創建。這通常情況下是有意義的,但是在一些情況下,你希望組件被立即加載哪怕它們從未被使用。舉例來說,你可能要向一個遠端系統發送一個信息或者在系統啟動時預加載一個緩存。你可以通過使用eager binding的方式強制創建一個組件。實例不會被自動清理,除了正常的垃圾回收。當組件不再被引用時,將會被GC回收,但是Play框架不會做任何特殊的事情來關閉組件,例如close方法。無論如何,Play提供了一類叫做closeapplicationLifecycly的特殊的組件,你可以將其注冊進去以在系統停止的時候關閉它們。4.單例
有時你或許想讓組件保持一些狀態,例如緩存或者一個連向外部資源的鏈接,或者一個創建成本很高的組件。在這些情況下,創建一個該組件的單例是非常重要的。這些需求可以通過使用@Singleton注解來實現。例如:
import javax.inject.*;@Singletonpublic class CurrentSharePrice { private volatile int price; public void set(int p) { price = p; } public int get() { return price; }}5.停止/清理
當Play框架關閉時,一些組件需要被清理,例如停止線程池。Play框架提供了ApplicationLifecycle組件來注冊這些需要在Play關閉時清理的組件:import javax.inject.*;import play.inject.ApplicationLifecycle;import java.util.concurrent.Callable;import java.util.concurrent.CompletableFuture;@Singletonpublic class MessageQueueConnection { private final MessageQueue connection; @Inject public MessageQueueConnection(ApplicationLifecycle lifecycle) { connection = MessageQueue.connect(); lifecycle.addStopHook(() -> { connection.stop(); return CompletableFuture.completedFuture(null); }); } // ...}ApplicationLifecycle將會按與創建順序相反的順序來停止所有組件。這意味著任何你所依賴的組件都將會被安全的引用,因為你依賴它們,它們必須在你的組件創建之前被創建,且直到該組件停止為止才能被清理。注意,確保每個注冊到銷毀機制中的組件一定要是單例。任何非單例都可能是一個內存泄漏的隱患,因為新的組件在創建時都會注冊到銷毀機制中。
6.提供自定義綁定
實踐證明,通過接口來定義一個組件是非常好的,好于直接注入實現類本身。這樣一來,你可以注冊不同的實現類。若你聲明的是接口,那這時依賴注入系統就需要知道需要在這個接口下綁定哪個實現類。最簡單的方式就是通過@ImplementedBy注解來綁定實現類。以下是實現方式:
import com.google.inject.ImplementedBy;@ImplementedBy(EnglishHello.class)public interface Hello { String sayHello(String name);}public class EnglishHello implements Hello { public String sayHello(String name) { return "Hello " + name; }}在一些更復雜的情況下,你可能想要提供更復雜的綁定,例如當一個接口下有許多實現類需要注入時,可以通過@Nmed注解來區別。在下例中,你可以實現一個Guice Module:
import com.google.inject.AbstractModule;import com.google.inject.name.Names;public class Module extends AbstractModule { protected void configure() { bind(Hello.class) .annotatedWith(Names.named("en")) .to(EnglishHello.class); bind(Hello.class) .annotatedWith(Names.named("de")) .to(GermanHello.class); }} 如果你調用了Module并且將其置于根目錄,它會自動地注冊到Play框架中。亦或,你想起一個其它名字或者將其放置到其它包中,你可以在application.conf文件中的play.modules.enabled清單中追加它的全類名:play.modules.enabled += "modules.HelloModule" 你也可以禁用根目錄中Module的自動注冊,通過將其加到disabled modules中來實現:play.modules.disabled += "Module"7.可配置的綁定
有時你或許想在配置Guice綁定的時候讀取Play框架的Configuration或者使用一個ClassLoader。你可以通過將其加入到你的module構造方法中的方式獲取入口。
在下例中,Hello綁定需要讀取configuration文件中的內容。
import com.google.inject.AbstractModule;import com.google.inject.ConfigurationException;import com.google.inject.name.Names;import play.Configuration;import play.Environment;public class Module extends AbstractModule { private final Environment environment; private final Configuration configuration; public Module( Environment environment, Configuration configuration) { this.environment = environment; this.configuration = configuration; } protected void configure() { // Expect configuration like: // hello.en = "myapp.EnglishHello" // hello.de = "myapp.GermanHello" Configuration helloConf = configuration.getConfig("hello"); // Iterate through all the languages and bind the // class associated with that language. Use Play's // ClassLoader to load the classes. for (String l: helloConf.subKeys()) { try { String bindingClassName = helloConf.getString(l); Class<? extends Hello> bindingClass = environment.classLoader().loadClass(bindingClassName) .asSubclass(Hello.class); bind(Hello.class) .annotatedWith(Names.named(l)) .to(bindingClass); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } }}
新聞熱點
疑難解答