知識庫 / Spring RSS 訂閱

Guice 與 Spring – 依賴注入

Spring
HongKong
9
01:28 PM · Dec 06 ,2025

1. 引言

Google GuiceSpring 是兩種強大的框架,用於依賴注入。這兩個框架都涵蓋了依賴注入的所有概念,但各自採用不同的實現方式。

在本教程中,我們將討論 Guice 和 Spring 框架在配置和實現方面的差異。

2. Maven 依賴

讓我們先將 Guice 和 Spring Maven 依賴添加到我們的 `pom.xml</em/> 文件中:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>7.0.0</version>
</dependency>

我們始終可以從 Maven Central 訪問最新的 spring-contextguice 依賴項。

3. 依賴注入配置

依賴注入是一種編程技術,我們使用它來使我們的類與它們的依賴項分離。

在本節中,我們將討論 Spring 和 Guice 在配置依賴注入方式上的一些核心功能差異。

3.1. Spring 注入

Spring 聲明依賴注入配置在特殊的配置類中。該類必須被  註解。Spring 容器使用該類作為 Bean 定義的來源。

Spring 容器管理的所有類稱為Spring Bean。

Spring 使用註解自動注入依賴。是 Spring 內置核心註解的一部分。我們可以將應用於成員變量、setter 方法和構造函數。

Spring 還支持 是 Java CDI (Contexts and Dependency Injection) 的標準,用於定義依賴注入的標準。

假設我們想要將依賴自動注入到成員變量中,我們可以簡單地用 註解標記它:

@Component
public class UserService {
    @Autowired
    private AccountService accountService;
}
@Component
public class AccountServiceImpl implements AccountService {
}

其次,讓我們創建一個配置類,用作在加載應用程序上下文時作為 Bean 的來源:

@Configuration
@ComponentScan("com.baeldung.di.spring")
public class SpringMainConfig {
}

請注意,我們還為 UserServiceAccountServiceImpl 註釋了 @Component 註解,以將它們註冊為 Bean。

@ComponentScan 註解將告訴 Spring 在哪裏搜索帶有註釋的組件。

即使我們已為 AccountServiceImpl 註釋了 @Component,Spring 仍然可以將其映射到 AccountService,因為它實現了 AccountService

然後,我們需要定義一個應用程序上下文來訪問 Bean。 請注意,我們將在此處的所有 Spring 單元測試中引用此上下文。

ApplicationContext context = new AnnotationConfigApplicationContext(SpringMainConfig.class);

現在在運行時,我們可以從我們的 UserService Bean 中檢索 AccountService 實例:

UserService userService = context.getBean(UserService.class);
assertNotNull(userService.getAccountService());

3.2. Guice 綁定

Guice 通過一個名為模塊的特殊類來管理其依賴關係。一個 Guice 模塊必須擴展 AbstractModule 類並覆蓋其 configure() 方法。

Guice 使用綁定作為 Spring 中“wiring”的等效方式。 簡單來説,綁定允許我們定義依賴關係將如何注入到類中。 Guice 綁定在模塊的 configure() 方法中聲明。

與 @Autowired 相比,Guice 使用 @Inject 註解來注入依賴關係。

讓我們創建一個等效的 Guice 示例:

public class GuiceUserService {
    @Inject
    private AccountService accountService;
}

其次,我們將創建一個模塊類,該類將作為我們綁定定義的來源:

public class GuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AccountService.class).to(AccountServiceImpl.class);
    }
}

通常情況下,我們期望 Guice 從它們的默認構造函數中實例化每個依賴對象,除非在 configure() 方法中明確定義了綁定。 但是,由於接口不能直接實例化,因此我們需要定義綁定,以告訴 Guice 哪個接口將與哪個實現配對。

然後,我們需要使用 GuiceModule 定義一個 Injector 以獲取我們類中的實例。 讓我們簡單地注意到,所有我們的 Guice 測試都將使用這個 Injector

Injector injector = Guice.createInjector(new GuiceModule());

最後,在運行時,我們檢索到一個非空GuiceUserService實例,並帶有非空accountService依賴項:

GuiceUserService guiceUserService = injector.getInstance(GuiceUserService.class);
assertNotNull(guiceUserService.getAccountService());

3.3. Spring 中的 @Bean 註解

Spring 還提供了一個方法級別的註解 <em @Bean</em>>,用於註冊 Bean,作為其類級別的註解(如 <em @Component</em>>)的替代方案。一個 <em @Bean</em>> 註解的方法返回值將被註冊為容器中的 Bean。

假設我們有一個 <em BookServiceImpl</em>> 實例,並且希望將其提供用於注入。我們可以使用 <em @Bean</em>> 來註冊我們的實例:

@Bean 
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

現在我們可以獲取一個 BookService Bean:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);

3.4. Guice 的 @Provides 註解

作為 Spring 的 @Bean 註解的等效項,Guice 具有內置的註解 @Provides ,用於完成相同的功能。 類似於 @Bean@Provides 僅應用於方法。

現在,讓我們使用 Guice 實現之前的 Spring bean 示例。 我們只需要將以下代碼添加到我們的模塊類中:

@Provides
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

現在,我們可以檢索一個 BookService 的實例:

BookService bookService = injector.getInstance(BookService.class);
assertNotNull(bookService);

3.5. 類路徑組件掃描(Spring)

Spring 提供了一個 <em @ComponentScan</em> 註解,該註解能夠自動檢測和實例化帶有註解的組件,通過掃描預定義的包。

<em @ComponentScan</em> 註解告訴 Spring 應該掃描哪些包以查找帶有註解的組件。它通常與 <em @Configuration</em> 註解一起使用。

3.6. Guice 中的 classpath 組件掃描

與 Spring 不同,Guice 不具備類似組件掃描的功能。 但通過一些插件,如 Governator,可以輕鬆模擬該功能。

3.7. 在 Spring 中進行對象識別

Spring 通過對象的名稱進行識別。Spring 將對象存儲在一個類似於 Map<String, Object> 的結構中。這意味着我們不能有同名的兩個對象。

由於具有同名的多個 Bean 導致 Bean 衝突是 Spring 開發者經常遇到的問題。例如,考慮以下 Bean 聲明:

@Configuration
@Import({SpringBeansConfig.class})
@ComponentScan("com.baeldung.di.spring")
public class SpringMainConfig {
    @Bean
    public BookService bookServiceGenerator() {
        return new BookServiceImpl();
    }
}
@Configuration
public class SpringBeansConfig {
    @Bean
    public AudioBookService bookServiceGenerator() {
        return new AudioBookServiceImpl();
    }
}

如我們所知,我們已經在 SpringMainConfig 類中已經定義了一個 BookService 的 Bean 定義。

為了在此處避免 Bean 衝突,我們需要聲明具有相同名稱的 Bean 方法。但是,在一個類中不能有具有相同名稱的兩個不同方法。因此,我們已經在另一個配置類中聲明瞭 AudioBookService Bean。

現在,讓我們在單元測試中引用這些 Bean:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService); 
AudioBookService audioBookService = context.getBean(AudioBookService.class);
assertNotNull(audioBookService);

以下是翻譯後的內容:

單元測試將失敗時,

org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'AudioBookService' available

首先,Spring 註冊了 AudioBookService Bean 並將其命名為 “bookServiceGenerator”,存入其 Bean 映射中。隨後,由於 HashMap 數據結構不允許重複名稱,Spring 又通過 Bean 定義覆蓋了 BookService Bean。

最後,name attribute to a unique name for each <em title="@Bean">@Bean。

3.8. 在 Guice 中進行對象識別

與 Spring 相比,Guice 基本上採用 Map<Class<?>, Object> 的結構。這意味着我們不能在沒有使用額外元數據的情況下擁有相同類型的多個綁定。

Guice 提供 綁定註解,以啓用對相同類型的多個綁定。 讓我們看看在 Guice 中,如果對於相同類型的綁定有兩條定義,會發生什麼。

public class Person {
}

現在,讓我們聲明對Person類兩種不同的綁定:

bind(Person.class).toConstructor(Person.class.getConstructor());
bind(Person.class).toProvider(new Provider<Person>() {
    public Person get() {
        Person p = new Person();
        return p;
    }
});

以下是如何獲取 Person 類的一個實例:

Person person = injector.getInstance(Person.class);
assertNotNull(person);

這將導致失敗:

com.google.inject.CreationException: A binding to Person was already configured at GuiceModule.configure()

我們可以通過簡單地丟棄對 Person 類的其中一個綁定來解決這個問題。

3.9. 選定的依賴項在 Spring 中

選定的依賴項是指在自動裝配或注入 Bean 時並非必需的依賴項。

對於被標註為使用 <em @Autowired</em> 的字段,如果上下文環境中找不到具有匹配的數據類型的 Bean,Spring 將拋出 <em NoSuchBeanDefinitionException</em> 異常。

然而,有時我們可能希望跳過某些依賴項的自動裝配,並將它們留為 <em null,而無需拋出異常:

現在讓我們來看以下示例:

@Component
public class BookServiceImpl implements BookService {
    @Autowired
    private AuthorService authorService;
}
public class AuthorServiceImpl implements AuthorService {
}

如上代碼所示,AuthorServiceImpl 類尚未被標註為組件。並且我們假設它在我們的配置文件中沒有 bean 聲明方法。

現在,讓我們運行以下測試以查看結果:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);

unsurprisingly, 它會失敗,使用:

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'AuthorService' available

我們可以通過使用 Java 8 的 Optional 類型,使 authorService 依賴項成為可選的,從而避免此異常。

public class BookServiceImpl implements BookService {
    @Autowired
    private Optional<AuthorService> authorService;
}

現在,我們的 authorService 依賴更像是一個容器,可能包含或不包含一個類型的 AuthorService Bean。 即使在我們的應用程序上下文中沒有 AuthorService Bean,我們的 authorService 字段仍然是一個非空的容器。 因此,Spring 將沒有理由拋出 NoSuchBeanDefinitionException

作為 Optional 的替代方案,我們可以使用 @Autowired 的 required 屬性,該屬性默認設置為 true,以使依賴項可選。 我們可以將 required 屬性設置為 false 以使依賴項可選用於自動注入。

因此,Spring 如果上下文中沒有其數據類型的 Bean,則會跳過注入依賴項。 依賴項將保持為 null:

@Component
public class BookServiceImpl implements BookService {
    @Autowired(required = false)
    private AuthorService authorService;
}

有時標記依賴項為可選可能是有用的,因為並非所有依賴項在任何時候都需要。

考慮到這一點,在開發過程中,我們應該特別注意並使用 null 檢查,以避免因 NullPointerException 導致 null 依賴項引起的錯誤。

3.10. Guice 中可選依賴項

Spring 類似,Guice 也可以使用 Java 8 的 Optional 類型來使依賴項成為可選的。

假設我們想要創建一個類並帶有 Foo 依賴項:

public class FooProcessor {
    @Inject
    private Foo foo;
}

現在,讓我們為 Foo 類定義一個綁定:

bind(Foo.class).toProvider(new Provider<Foo>() {
    public Foo get() {
        return null;
    }
});

現在我們嘗試在單元測試中獲取 FooProcessor 的實例:

FooProcessor fooProcessor = injector.getInstance(FooProcessor.class);
assertNotNull(fooProcessor);

我們的單元測試將失敗時,會產生以下結果:

com.google.inject.ProvisionException:
null returned by binding at GuiceModule.configure(..)
but the 1st parameter of FooProcessor.[...] is not @Nullable

為了避免此異常,我們可以通過簡單的更新使 foo 依賴項成為可選的:

public class FooProcessor {
    @Inject
    private Optional<Foo> foo;
}

@Inject 不具備 required 屬性來標記依賴項為可選。 在 Guice 中,要使依賴項可選的另一種方法是使用 @Nullable 註解。

Guice 容忍在使用了 @Nullable 註解時注入 null 值,如上方的異常消息所示。 讓我們應用 @Nullable 註解:

public class FooProcessor {
    @Inject
    @Nullable
    private Foo foo;
}

4. 依賴注入類型的實現

本節將探討依賴注入類型的實現,並通過幾個示例對比 Spring 和 Guice 提供的實現。

4.1. Spring 中構造器注入

在構造器注入中,我們在實例化類時通過構造函數傳遞所需的依賴項。

例如,我們想要創建一個 Spring 組件並通過其構造函數添加依賴項。我們可以使用 <em @Autowired</em> 標註該構造函數:

@Component
public class SpringPersonService {

    private PersonDao personDao;

    @Autowired
    public SpringPersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

從 Spring 4 開始,如果類只有一個構造函數,則 @Autowired 依賴注入對於此類注入來説不是必需的。

讓我們在一個測試中檢索一個 SpringPersonService Bean:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);

4.2. 在 Guice 中使用構造函數注入

我們可以將之前的示例重新排列以在 Guice 中實現構造函數注入。請注意,Guice 使用 @Inject 而不是 @Autowired

public class GuicePersonService {

    private PersonDao personDao;

    @Inject
    public GuicePersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

以下是如何在測試中從 GuicePersonService 類中獲取 injector 中的實例:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);

4.3. 使用 Spring 中的 Setter 或方法注入

在基於 Setter 的依賴注入中,容器會在調用構造方法實例化組件後,調用類中的 Setter 方法。

假設我們希望 Spring 使用 Setter 方法自動注入依賴關係。我們可以使用 @Autowired 註解標記該 Setter 方法。

@Component
public class SpringPersonService {

    private PersonDao personDao;

    @Autowired
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

當我們需要一個 SpringPersonService 類實例時,Spring 會通過調用 setPersonDao() 方法自動注入 personDao 字段。

在測試中,我們可以獲取一個 SpringPersonService Bean,並像下面這樣訪問其 personDao 字段:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());

4.4. 在 Guice 中使用 Setter 或方法注入

我們將稍微修改我們的示例,以實現 Guice 中的 Setter 注入。

public class GuicePersonService {

    private PersonDao personDao;

    @Inject
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

每次從注入器中獲取 GuicePersonService 類的一個實例時,我們都會將 personDao 字段傳遞到上面的 setter 方法中。

以下是如何在測試中創建 GuicePersonService 類的一個實例並訪問其 personDao 字段:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());

4.5. Spring 中字段注入

我們已經看到了如何應用字段注入,既用於 Spring 又是用於 Guice 在我們所有示例中。因此,這對於我們來説並不是一個全新的概念。我們簡單地列出一遍,以確保完整性。

在基於字段的依賴注入的情況下,我們通過使用 @Autowired@Inject 標記它們來注入依賴項。

4.6. Guice 中字段注入

正如我們在上一節中提到的,我們已經涵蓋了使用 @InjectGuice 字段注入

5. 結論

在本教程中,我們探討了 Guice 和 Spring 框架在實現依賴注入方式上的幾個核心差異。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.