Guice依賴注入(Scope)

本教程主要詳細講解Guice依賴注入中的一些高級選項,他們分別是ScopeEagerly Loading BindingsStageOptional Injection。我們將一一對他們進行講解。

基礎環境


技術 版本
Java 1.8+
Guice 4.2.3

初始化項目


  • 初始化項目
mvn archetype:generate -DgroupId=com.edurt.sli.guice -DartifactId=guice-binder-scope -DarchetypeArtifactId=maven-archetype-quickstart -Dversion=1.0.0 -DinteractiveMode=false
  • 修改pom.xml增加Guice依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">


    <parent>
        <artifactId>spring-learn-integration</artifactId>
        <groupId>com.edurt.sli</groupId>
        <version>1.0.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>guice-binder-scope</artifactId>
    <name>Guice依賴注入(Scope)</name>

    <properties>
        <system.java.version>1.8</system.java.version>
        <guice.version>4.2.3</guice.version>
        <lombok.version>1.18.2</lombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>${guice.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${plugin.maven.compiler.version}</version>
                <configuration>
                    <source>${system.java.version}</source>
                    <target>${system.java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

guice: guice就是我們核心要使用的依賴

Singleton


Guice支持我們在其他DI框架中逐漸習慣的ScopeScope機制。Guice默認提供已定義依賴項的新實例。

  • 創建 Service接口,用於提供使用的測試使用到的方法,代碼如下
package com.edurt.sli.guice.scope;

public interface Service
{
    void println(String source);
}
  • 創建 ScopeService類,用於構建對 Service的實現
package com.edurt.sli.guice.scope;

import java.time.LocalDateTime;

public class ScopeService
        implements Service
{
    @Override
    public void println(String source)
    
{
        System.out.println(String.format("%s on %s", source, LocalDateTime.now()));
    }
}
  • 創建用於測試注入的應用類 ScopeApplication,代碼如下
package com.edurt.sli.guice.scope;

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

public class ScopeApplication
{
    public static void main(String[] args)
    
{
        Injector injector = Guice.createInjector(binder -> binder.bind(Service.class).to(ScopeService.class).in(Scopes.SINGLETON));
        Service service = injector.getInstance(Service.class);
        service.println("Singleton Scope");
    }
}

我們運行程序輸出

Scope on 2020-12-18T16:01:37.792656

通過代碼binder.bind(Service.class).to(ScopeService.class).in(Scopes.SINGLETON)我們指定了ScopeService的Scope,他將會被標誌爲一個單例實例。當然我們也可以使用@Singleton標誌該類的作用域,我們修改ScopeService類文件代碼如下:

package com.edurt.sli.guice.scope;

import javax.inject.Singleton;

import java.time.LocalDateTime;

@Singleton
public class ScopeService
        implements Service
{
    @Override
    public void println(String source)
    
{
        System.out.println(String.format("%s on %s", source, LocalDateTime.now()));
    }
}

ScopeApplication中的binder.bind(Service.class).to(ScopeService.class).in(Scopes.SINGLETON)代碼修改爲binder.bind(Service.class).to(ScopeService.class)

兩種方式實現的效果都是一致的。此時ScopeService會被構建爲單例實例。

當然還有一個asEagerSingleton()方法也可以用來標記單例模式。

他們的對比圖如下:

使用方式 PRODUCTION DEVELOPMENT
.asEagerSingleton() eager eager
.in(Singleton.class) eager lazy
.in(Scopes.SINGLETON) eager lazy
@Singleton eager* lazy

自定義Scope


Guice還支持我們用戶自定義作用域,通常情況下我們不需要自己實現Scope,一般內置的作用域對於大多數的應用已經足夠了。如果您正在編寫一個web應用程序,那麼ServletModule爲HTTP請求和HTTP會話提供了簡單的、良好作用域實現是一個很好的想法。

  • 自定義Scope註解

Scope註解用於標記當前Scope在容器中使用的作用域。將使用它來註釋guice構造的類型,@Provides方法和bind語法中的in()。Scope註解代碼如下:

package com.edurt.sli.guice.seso;

import com.google.inject.ScopeAnnotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({TYPE, METHOD})
@Retention(RUNTIME)
@ScopeAnnotation
public @interface CustomScope
{
}

在使用自定義Scope時,請確保導入了正確的Scope註解。否則,您可能會得到一個SCOPE_NOT_FOUND錯誤。

  • 實現Scope接口

Scope接口確保每個Scope實例擁有一到多個類型實例。實現的Scope接口代碼如下:

package com.edurt.sli.guice.seso;

import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;

public class CustomScopeImpl
        implements Scope
{
    ThreadLocal<Object> threadLocal = new ThreadLocal<>();

    @Override
    public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped)
    
{
        return () -> {
            T instance = (T) threadLocal.get();
            if (instance == null) {
                instance = unscoped.get();
                threadLocal.set(instance);
            }
            return instance;
        };
    }
}

我們在上述代碼中實現了一個簡單線程抽取Scope,我們只是爲了做測試使用,具體的Scope還需要根據業務自己使用。當我們傳遞的線程中沒有構造一個對象時,先構造一個,然後放入線程上下文中,以後每次都從線程中獲取對象。

  • 使用創建 CustomScopeApplication用來使用自定義的Scope代碼如下:
package com.edurt.sli.guice.seso;

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

public class CustomScopeApplication
{
    public static void main(String[] args)
    
{
        Injector injector = Guice.createInjector(binder -> {
            binder.bind(Service.class).to(ScopeService.class).in(new CustomScopeImpl());
        });
        for (int i = 0; i < 3; i++) {
            System.out.println(injector.getInstance(Service.class).hashCode());
        }
    }
}

運行程序後我們得到以下結果:

1574598287
1574598287
1574598287

我們通過結果得到運行了3次後的實例hashCode是一致的,這就說明我們的自定義Scope已經起作用了。如果新的實例構建後那麼hashCode將會被改變。

  • 綁定自定義Scope註解,我們通過實現Module進行注入
package com.edurt.sli.guice.seso;

import com.google.inject.AbstractModule;

public class CustomScopeModule
        extends AbstractModule
{
    @Override
    protected void configure()
    
{
        bindScope(CustomScope.classnew CustomScopeImpl());
    }
}

需要使用修改Module只需要在Guice.createInjector構建的時候添加該Module即可,代碼如下:

Injector injector = Guice.createInjector(new CustomScopeModule(), binder -> {
            binder.bind(Service.class).to(ScopeService.class).in(new CustomScopeImpl());
        });

ScopeService類上使用@CustomScope註解即可。

打包文件部署


  • 打包數據
mvn clean package -Dmaven.test.skip=true -X

運行打包後的文件即可

java -jar target/guice-binder-scope-1.0.0.jar

源碼地址


  • GitHub


本文分享自微信公衆號 - Spring中文網(china-spring-all)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章