Guice vs Spring – Dependency Injection – Guice vs Spring – 依赖性注入

最后修改: 2019年 2月 6日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Google Guice and Spring are two robust frameworks used for dependency injection. Both frameworks cover all the notions of dependency injection, but each one has its own way of implementing them.

Google GuiceSpring是两个用于依赖注入的强大框架。这两个框架都涵盖了依赖注入的所有概念,但每个框架都有自己的实现方式。

In this tutorial, we’ll discuss how the Guice and Spring frameworks differ in configuration and implementation.

在本教程中,我们将讨论Guice和Spring框架在配置和实现上有何不同。

2. Maven Dependencies

2.Maven的依赖性

Let’s start by adding the Guice and Spring Maven dependencies into our pom.xml file:

我们先把Guice和Spring Maven的依赖关系加入pom.xml文件。

<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>4.2.2</version>
</dependency>

We can always access the latest spring-context or guice dependencies from Maven Central.

我们可以随时从Maven中心访问最新的spring-contextguice依赖项。

3. Dependency Injection Configuration

3.依赖性注入配置

Dependency injection is a programming technique that we use to make our classes independent of their dependencies.

依赖注入是一种编程技术,我们用它来使我们的类独立于其依赖关系。

In this section, we’ll refer to several core features that differ between Spring and Guice in their ways of configuring dependency injection.

在这一节中,我们将提到Spring和Guice在配置依赖注入的方式上有所不同的几个核心特性。

3.1. Spring Wiring

3.1.Spring接线

Spring declares the dependency injection configurations in a special configuration class. This class must be annotated by the @Configuration annotation. The Spring container uses this class as a source of bean definitions.

Spring在一个特殊的配置类中声明了依赖注入配置。该类必须由@Configuration注解来注释。Spring容器使用这个类作为Bean定义的来源。

Classes managed by Spring are called Spring beans.

由Spring管理的类被称为 Spring beans

Spring uses the @Autowired annotation to wire the dependencies automatically. @Autowired is part of Spring’s built-in core annotations. We can use @Autowired on member variables, setter methods, and constructors.

Spring 使用 @Autowired 注释来自动连接依赖性@AutowiredSpring的内置核心注释的一部分。我们可以在成员变量、setter方法和构造函数上使用@Autowired

Spring also supports @Inject. @Inject is part of the Java CDI (Contexts and Dependency Injection) that defines a standard for dependency injection.

Spring还支持@Inject。@InjectJava CDI(Contexts and Dependency Injection)的一部分,定义了依赖注入的标准。

Let’s say that we want to automatically wire a dependency to a member variable. We can simply annotate it with @Autowired:

假设我们想把一个依赖关系自动连接到一个成员变量。我们可以简单地用@Autowired来注解它。

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

Secondly, let’s create a configuration class to use as the source of beans while loading our application context:

其次,让我们创建一个配置类,在加载我们的应用程序上下文时作为Bean的来源。

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

Note that we’ve also annotated UserService and AccountServiceImpl with @Component to register them as beans. It’s the @ComponentScan annotation that will tell Spring where to search for annotated components.

请注意,我们还用@Component注释了UserServiceAccountServiceImpl,以便将它们注册为Bean。@ComponentScan注解,它将告诉Spring在哪里搜索被注解的组件。

Even though we’ve annotated AccountServiceImpl, Spring can map it to the AccountService since it implements AccountService.

尽管我们已经注释了AccountServiceImplSpring可以将其映射到AccountService,因为它实现了AccountService

Then, we need to define an application context to access the beans. Let’s just note that we’ll refer to this context in all of our Spring unit tests:

然后,我们需要定义一个应用上下文来访问这些Bean。让我们注意一下,我们将在所有的Spring单元测试中引用这个上下文。

ApplicationContext context = new AnnotationConfigApplicationContext(SpringMainConfig.class);

Now at runtime, we can retrieve the AccountService instance from our UserService bean:

现在在运行时,我们可以从我们的UserServicebean中检索到AccountService实例。

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

3.2. Guice Binding

3.2.Guice绑定

Guice manages its dependencies in a special class called a module. A Guice module has to extend the AbstractModule class and override its configure() method.

Guice在一个叫做模块的特殊类中管理它的依赖关系。Guice模块必须扩展AbstractModule类并重写其configure()方法。

Guice uses binding as the equivalent to wiring in Spring. Simply put, bindings allow us to define how dependencies are going to be injected into a class. Guice bindings are declared in our module’s configure() method.

Guice使用绑定作为相当于Spring中的布线。简单地说,绑定允许我们定义如何将依赖关系注入到一个类中。Guice的绑定是在我们模块的configure()方法中声明的。

Instead of @Autowired, Guice uses the @Inject annotation to inject the dependencies. 

Guice使用@Inject注解来注入依赖项,而不是@Autowired

Let’s create an equivalent Guice example:

让我们创建一个等效的Guice例子。

public class GuiceUserService {
    @Inject
    private AccountService accountService;
}

Secondly, we’ll create the module class which is a source of our binding definitions:

其次,我们将创建模块类,这是我们绑定定义的来源。

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

Normally, we expect Guice to instantiate each dependency object from their default constructors if there isn’t any binding defined explicitly in the configure() method. But since interfaces can’t be instantiated directly, we need to define bindings to tell Guice which interface will be paired with which implementation.

通常,如果configure()方法中没有明确定义任何绑定,我们希望Guice从它们的默认构造函数中实例化每个依赖对象。但是由于接口不能被直接实例化,我们需要定义绑定来告诉Guice哪个接口将与哪个实现配对。

Then, we need to define an Injector using GuiceModule to get instances of our classes. Let’s just note that all of our Guice tests will use this Injector:

然后,我们需要使用GuiceModule定义一个Injector来获得我们类的实例。让我们注意一下,我们所有的Guice测试将使用这个Injector

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

Finally, at runtime we retrieve a GuiceUserService instance with a non-null accountService dependency:

最后,在运行时,我们检索一个GuiceUserService实例,它具有非空的accountService依赖关系。

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

3.3. Spring’s @Bean Annotation

3.3.Spring的@Bean注解

Spring also provides a method level annotation @Bean to register beans as an alternative to its class level annotations like @Component. The return value of a @Bean annotated method is registered as a bean in the container.

Spring还提供了一个方法级注解@Bean来注册Bean,作为其类级注解(如@Component)的替代。@Bean 注释的方法的返回值被注册为容器中的一个Bean。

Let’s say that we have an instance of BookServiceImpl that we want to make available for injection. We could use @Bean to register our instance:

假设我们有一个BookServiceImpl的实例,我们想让它可用于注入。我们可以使用@Bean来注册我们的实例。

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

And now we can get a BookService bean:

现在我们可以得到一个BookService bean。

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

3.4. Guice’s @Provides Annotation

3.4.Guice的@Provides注释

As an equivalent of Spring’s @Bean annotation, Guice has a built-in annotation @Provides to do the same job. Like @Bean, @Provides is only applied to the methods.

作为Spring的@Bean注解的等价物,Guice有一个内置注解@Provides来做同样的工作。与@Bean一样,@Provides只应用于方法。

Now let’s implement the previous Spring bean example with Guice. All we need to do is to add the following code into our module class:

现在让我们用Guice实现前面的Spring Bean例子。我们所要做的就是在我们的模块类中添加以下代码。

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

And now, we can retrieve an instance of BookService:

现在,我们可以检索到一个BookService的实例。

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

3.5. Classpath Component Scanning in Spring

3.5.Spring中的Classpath组件扫描

Spring provides a @ComponentScan annotation detects and instantiates annotated components automatically by scanning pre-defined packages.

Spring提供了一个@ComponentScan注解,通过扫描预先定义的包,自动检测并实例化注解的组件

The @ComponentScan annotation tells Spring which packages will be scanned for annotated components. It is used with @Configuration annotation.

@ComponentScan注解告诉Spring哪些包将被扫描以获取注释的组件。它与@Configuration注解一起使用。

3.6. Classpath Component Scanning in Guice

3.6.Guice中的Classpath组件扫描

Unlike Spring, Guice doesn’t have such a component scanning feature. But it’s not difficult to simulate it. There are some plugins like Governator that can bring this feature into Guice.

与Spring不同,Guice没有这样的组件扫描功能。但要模拟它并不难。有一些插件,如Governator,可以把这个功能引入Guice。

3.7. Object Recognition in Spring

3.7.Spring里的物体识别

Spring recognizes objects by their names. Spring holds the objects in a structure which is roughly like a Map<String, Object>. This means that we cannot have two objects with the same name.

Spring通过对象的名字来识别它们。Spring将对象保存在一个结构中,这个结构大致类似于Map<String, Object>这意味着我们不能有两个名字相同的对象。

Bean collision due to having multiple beans of the same name is one common problem Spring developers hit. For example, let’s consider the following bean declarations:

由于有多个同名的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();
    }
}

As we remember, we already had a bean definition for BookService in SpringMainConfig class.

我们记得,在SpringMainConfig类中,我们已经有一个BookService的bean定义。

To create a bean collision here, we need to declare the bean methods with the same name. But we are not allowed to have two different methods with the same name in one class. For that reason, we declared the AudioBookService bean in another configuration class.

为了在这里创建一个Bean碰撞,我们需要用相同的名字声明Bean方法。但是我们不允许在一个类中有两个同名的不同方法。出于这个原因,我们在另一个配置类中声明了AudioBookService Bean。

Now, let’s refer these beans in a unit test:

现在,让我们在一个单元测试中引用这些Bean。

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

The unit test will fail with:

该单元测试将以失败告终。

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

First, Spring registered the AudioBookService bean with “bookServiceGenerator” name in its bean map. Then, it had to override it by the bean definition for BookService due to the “no duplicate names allowed” nature of the HashMap data structure.

首先,Spring在其bean map中用“bookServiceGenerator”名称注册了AudioBookServicebean。然后,由于HashMap数据结构的“不允许重复的名字”性质,它不得不通过BookService的bean定义来覆盖它。

Lastly, we can overcome this issue by making bean method names unique or setting the name attribute to a unique name for each @Bean.

最后,我们可以通过使Bean方法名称唯一化或将name属性设置为每个@Bean的唯一名称来克服这个问题。

3.8. Object Recognition in Guice

3.8.Guice中的物体识别

Unlike Spring, Guice basically has a Map<Class<?>, Object> structure. This means that we cannot have multiple bindings to the same type without using additional metadata.

与Spring不同,Guice基本上有一个Map<Class<?>, Object> 结构。这意味着,如果不使用额外的元数据,我们不能对同一类型有多个绑定。

Guice provides binding annotations to enable defining multiple bindings for the same type. Let’s see what happens if we have two different bindings for the same type in Guice.

Guice提供了绑定注释,以便能够为同一类型定义多个绑定。让我们看看如果我们在Guice中为同一类型有两个不同的绑定会发生什么。

public class Person {
}

Now, let’s declare two different binding for the Person class:

现在,让我们为Person类声明两个不同的绑定。

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

And here is how we can get an instance of Person class:

下面是我们如何获得Person类的一个实例。

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

This will fail with:

这将失败与。

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

We can overcome this issue by just simply discarding one of the bindings for the Person class.

我们可以通过简单地丢弃Person类的一个绑定来解决这个问题。

3.9. Optional Dependencies in Spring

3.9.Spring中的可选依赖关系

Optional dependencies are dependencies which are not required when autowiring or injecting beans.

可选的依赖性是在自动连接或注入Bean时不需要的依赖性。

For a field that has been annotated with @Autowired, if a bean with matching data type is not found in the context, Spring will throw NoSuchBeanDefinitionException.

对于被注解为@Autowired的字段,如果在上下文中没有找到匹配数据类型的Bean,Spring将抛出NoSuchBeanDefinitionException

However, sometimes we may want to skip autowiring for some dependencies and leave them as null without throwing an exception:

然而,有时我们可能想跳过某些依赖关系的自动布线,将其作为null而不抛出一个异常。

Now let’s take a look at the following example:

现在让我们看一下下面的例子。

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

As we can see from the code above, AuthorServiceImpl class hasn’t been annotated as a component. And we’ll assume that there isn’t a bean declaration method for it in our configuration files.

从上面的代码中我们可以看到,AuthorServiceImpl类并没有被注释为一个组件。而且我们将假设在我们的配置文件中没有它的bean声明方法。

Now, let’s run the following test to see what happens:

现在,让我们运行以下测试,看看会发生什么。

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

Not surprisingly, it will fail with:

毫不奇怪,它将以失败告终。

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

We can make authorService dependency optional by using Java 8’s Optional type to avoid this exception.

我们可以通过使用Java 8的Optional类型来使authorService依赖性成为可选项,以避免这种异常。

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

Now, our authorService dependency is more like a container that may or may not contain a bean of AuthorService type. Even though there isn’t a bean for AuthorService in our application context, our authorService field will still be non-null empty container. Hence, Spring won’t have any reason to throw NoSuchBeanDefinitionException.

现在,我们的authorService依赖关系更像是一个容器,它可能包含AuthorService类型的bean,也可能不包含。即使在我们的应用程序上下文中没有AuthorService的bean,我们的authorService字段仍将是非null的空容器。因此,Spring没有任何理由抛出NoSuchBeanDefinitionException

As an alternative to Optional, we can use @Autowired‘s required attribute, which is set to true by default, to make a dependency optional.  We can set the required attribute to false to make a dependency optional for autowiring.

作为 Optional 的替代方法,我们可以使用 @Autowiredrequired 属性(默认设置为 true)来使依赖成为可选的。 我们可以将required属性设置为false,以使自动布线的依赖性成为可选项。

Hence, Spring will skip injecting the dependency if a bean for its data type is not available in the context. The dependency will remain set to null:

因此,如果上下文中没有适合其数据类型的Bean,Spring将跳过注入该依赖。依赖关系将保持设置为null:

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

Sometimes marking dependencies optional can be useful since not all the dependencies are always required.

有时标记依赖关系是有用的,因为并非所有的依赖关系都是必需的。

With this in mind, we should remember that we’ll need to use extra caution and null-checks during development to avoid any NullPointerException due to the null dependencies.

考虑到这一点,我们应该记住,我们需要在开发过程中使用额外的谨慎和null检查,以避免由于null依赖关系而导致的任何NullPointerException

3.10. Optional Dependencies in Guice

3.10.Guice中的可选依赖关系

Just like Spring, Guice can also use Java 8’s Optional type to make a dependency optional.

就像Spring一样,Guice也可以使用Java 8的Optional类型来使一个依赖关系成为可选项。

Let’s say that we want to create a class and with a Foo dependency:

假设我们想创建一个类,并且有一个Foo依赖关系。

public class FooProcessor {
    @Inject
    private Foo foo;
}

Now, let’s define a binding for the Foo class:

现在,让我们为Foo类定义一个绑定。

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

Now let’s try to get an instance of FooProcessor in a unit test:

现在让我们试着在单元测试中获得一个FooProcessor的实例。

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

Our unit test will fail with:

我们的单元测试将以失败告终。

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

In order to skip this exception, we can make the foo dependency optional with a simple update:

为了跳过这个异常,我们可以通过简单的更新使foo依赖性成为可选项。

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

@Inject doesn’t have a required attribute to mark the dependency optional. An alternative approach to make a dependency optional in Guice is to use the @Nullable annotation.

@Inject没有一个required属性来标记依赖的可选性。在Guice中使依赖关系可选的另一种方法是使用@Nullable注解。

Guice tolerates injecting null values in case of using @Nullable as expressed in the exception message above. Let’s apply the @Nullable annotation:

Guice容忍在使用@Nullable的情况下注入null值,正如上面的异常信息所表达的。让我们应用@Nullable注解。

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

4. Implementations of Dependency Injection Types

4.依赖性注入类型的实现

In this section, we’ll take a look at the dependency injection types and compare the implementations provided by Spring and Guice by going through several examples.

在本节中,我们将看看依赖注入类型,并通过几个例子来比较Spring和Guice所提供的实现。

4.1. Constructor Injection in Spring

4.1.Spring中的构造函数注入

In constructor-based dependency injection, we pass the required dependencies into a class at the time of instantiation.

基于构造器的依赖注入中,我们在实例化时将所需的依赖性传递给类。

Let’s say that we want to have a Spring component and we want to add dependencies through its constructor. We can annotate that constructor with @Autowired:

假设我们想拥有一个Spring组件,并且我们想通过它的构造函数来添加依赖关系。我们可以用@Autowired来注解该构造函数。

@Component
public class SpringPersonService {

    private PersonDao personDao;

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

Starting with Spring 4, the @Autowired dependency is not required for this type of injection if the class has only one constructor.

从Spring 4开始,如果类只有一个构造函数,这种类型的注入就不需要@Autowired依赖。

Let’s retrieve a SpringPersonService bean in a test:

让我们在测试中检索一个SpringPersonServicebean。

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

4.2. Constructor Injection in Guice

4.2.Guice中的构造函数注入

We can rearrange the previous example to implement constructor injection in Guice. Note that Guice uses @Inject instead of @Autowired.

我们可以重新安排前面的例子来在Guice中实现构造函数注入。注意Guice使用@Inject而不是@Autowired

public class GuicePersonService {

    private PersonDao personDao;

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

Here is how we can get an instance of GuicePersonService class from the injector in a test:

下面是我们如何在测试中从injector获得GuicePersonService类的一个实例。

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

4.3. Setter or Method Injection in Spring

4.3.Spring中的设置器或方法注入

In setter-based dependency injection, the container will call setter methods of the class, after invoking the constructor to instantiate the component.

在基于setter的依赖注入中,容器将在调用构造函数来实例化组件后,调用类的setter方法。

Let’s say that we want Spring to autowire a dependency using a setter method. We can annotate that setter method with @Autowired:

假设我们想让Spring用一个setter方法来自动连接一个依赖关系。我们可以用@Autowired来注释该setter方法。

@Component
public class SpringPersonService {

    private PersonDao personDao;

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

Whenever we need an instance of SpringPersonService class, Spring will autowire the personDao field by invoking the setPersonDao() method.

每当我们需要一个SpringPersonService类的实例时,Spring将通过调用setPersonDao()方法自动连接personDao字段。

We can get a SpringPersonService bean and access its personDao field in a test as below:

我们可以获得一个SpringPersonService Bean并在测试中访问其personDao字段,如下所示。

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

4.4. Setter or Method Injection in Guice

4.4.Guice中的设置器或方法注入

We’ll simply change our example a bit to achieve setter injection in Guice.

我们将简单地改变一下我们的例子,以实现Guice中的setter injection

public class GuicePersonService {

    private PersonDao personDao;

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

Every time we get an instance of GuicePersonService class from the injector, we’ll have the personDao field passed to the setter method above.

每次我们从注入器获得GuicePersonService类的实例时,我们都会将personDao字段传递给上面的setter方法。

Here is how we can create an instance of GuicePersonService class and access its personDao field in a test:

下面是我们如何在测试中创建一个GuicePersonService类的实例并访问其personDao字段

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

4.5. Field Injection in Spring

4.5.Spring中的字段注入

We already saw how to apply field injection both for Spring and Guice in all of our examples. So, it’s not a new concept for us. But let’s just list it again for completeness.

我们已经看到了如何在我们所有的例子中为Spring和Guice应用字段注入。所以,这对我们来说不是一个新概念。但为了完整起见,让我们再次列出它。

In the case of field-based dependency injection, we inject the dependencies by marking them with @Autowired or @Inject.

在基于字段的依赖注入的情况下,我们通过用@Autowired@Inject标记它们来注入依赖关系。

4.6. Field Injection in Guice

4.6.Guice中的字段注入

As we mentioned in the section above, we already covered the field injection for Guice using @Inject.

正如我们在上面一节中提到的,我们已经使用@Inject涵盖了Guice的域注入。

5. Conclusion

5.总结

In this tutorial, we explored the several core differences between Guice and Spring frameworks in their ways of implementing dependency injection. As always, Guice and Spring code samples are over on GitHub.

在本教程中,我们探讨了Guice和Spring框架在实现依赖注入的方式上的几个核心差异。一如既往,GuiceSpring代码样本都在GitHub上。