Constructor Injection in Spring with Lombok – 用Lombok在Spring中注入构造函数

最后修改: 2017年 2月 4日

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

1. Introduction

1.介绍

Lombok is an extremely useful library overcoming boilerplate code. If you are not familiar with it yet, I highly recommend taking a look at the previous tutorial – Introduction to Project Lombok.

Lombok是一个非常有用的克服模板代码的库。如果你还不熟悉它,我强烈建议你看看以前的教程 – Project Lombok简介

In this article, we’ll demonstrate its usability when combined with Spring’s Constructor-Based Dependency Injection.

在这篇文章中,我们将展示它与Spring的基于构架的依赖注入相结合时的可用性。

2. Constructor-Based Dependency Injection

2.基于构造函数的依赖注入

A good way to wire dependencies in Spring using constructor-based Dependency Injection. This approach forces us to explicitly pass component’s dependencies to a constructor.

在Spring中使用constructor-based Dependency Injection来连接依赖关系的一个好方法。这种方法迫使我们明确地将组件的依赖关系传递给构造函数。

As opposed to Field-Based Dependency Injection, it also provides a number of advantages:

相对于基于字段的依赖注入,它还提供了一些优势。

  • no need to create a test-specific configuration component – dependencies are injected explicitly in a constructor
  • consistent design – all required dependencies are emphasized and looked after by constructor’s definition
  • simple unit tests – reduced Spring Framework’s overhead
  • reclaimed freedom of using final keywords

However, due to the need for writing a constructor, it uses to lead to a significantly larger code base. Consider the two examples of GreetingService and FarewellService:

然而,由于需要编写一个构造函数,它的使用会导致代码库明显增大。考虑一下GreetingServiceFarewellService的两个例子:

@Component
public class GreetingService {

    @Autowired
    private Translator translator;

    public String produce() {
        return translator.translate("hello");
    }
}
@Component
public class FarewellService {

    private final Translator translator;

    public FarewellService(Translator translator) {
        this.translator = translator;
    }

    public String produce() {
        return translator.translate("bye");
    }
}

Basically, both of the components do the same thing – they call a configurable Translator with a task-specific word.

基本上,这两个组件都做了同样的事情–它们用一个特定任务的词调用一个可配置的翻译器

The second variation, though, is much more obfuscated because of the constructor’s boilerplate which doesn’t really bring any value to the code.

不过,第二种变体由于构造函数的模板而更加混乱,并没有真正给代码带来任何价值。

In the newest Spring release, it’s constructor does not need to be annotated with @Autowired annotation.

在最新的Spring版本中,它的构造函数不需要用@Autowired注解来注释。

3. Constructor Injection With Lombok

3.用Lombok进行构造函数注入

With Lombok, it’s possible to generate a constructor for either all class’s fields (with @AllArgsConstructor) or all final class’s fields (with @RequiredArgsConstructor). Moreover, if you still need an empty constructor, you can append an additional @NoArgsConstructor annotation.

使用Lombok,我们可以为所有类的字段(使用@AllArgsConstructor)或所有final类的字段(使用@RequiredArgsConstructor)生成一个构造器。此外,如果你仍然需要一个空的构造函数,你可以附加一个额外的@NoArgsConstructor注解。

Let’s create a third component, analogous to the previous two:

让我们创建第三个组件,与前两个组件相类似。

@Component
@RequiredArgsConstructor
public class ThankingService {

    private final Translator translator;

    public String produce() {
        return translator.translate("thank you");
    }
}

The above annotation will cause Lombok to generate a constructor for us:

上述注解将使Lombok为我们生成一个构造函数。

@Component
public class ThankingService {

    private final Translator translator;

    public String thank() {
        return translator.translate("thank you");
    }

    /* Generated by Lombok */
    public ThankingService(Translator translator) {
        this.translator = translator;
    }
}

4. Multiple Constructors

4.多个构造器

A constructor doesn’t have to be annotated as long as there is only one in a component and Spring can unambiguously choose it as the right one to instantiate a new object. Once there are more, you also need to annotate the one that is to be used by IoC container.

只要组件中只有一个构造函数,并且Spring可以明确地选择它作为实例化新对象的正确构造函数,就不必对其进行注释。一旦有了更多的构造函数,你还需要对IoC容器使用的构造函数进行注释。

Consider the ApologizeService example:

考虑一下ApologizeService的例子。

@Component
@RequiredArgsConstructor
public class ApologizeService {

    private final Translator translator;
    private final String message;

    @Autowired
    public ApologizeService(Translator translator) {
        this(translator, "sorry");
    }

    public String produce() {
        return translator.translate(message);
    }
}

The above component is optionally configurable with the message field which cannot change after the component is created (hence the lack of a setter). It thus required us to provide two constructors – one with full configuration and the other with an implicit, default value of the message.

上述组件可选择配置message字段,该字段在组件创建后不能改变(因此缺少setter)。因此,我们需要提供两个构造函数–一个具有完整的配置,另一个具有隐含的message的默认值。

Unless one of the constructors is annotated with either @Autowired, @Inject or @Resource, Spring will throw an error:

除非其中一个构造函数被注释为@Autowired@Inject@Resource,否则Spring将抛出一个错误。

Failed to instantiate [...]: No default constructor found;

If we wanted to annotate the Lombok-generated constructor, we would have to pass the annotation with an onConstructor parameter of the @AllArgsConstructor:

如果我们想注释Lombok-生成的构造函数,我们必须用@AllArgsConstructoronConstructor参数传递注释。

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ApologizeService {
    // ...
}

The onConstructor parameter accepts an array of annotations (or a single annotation like in this specific example) that are to be put on a generated constructor. The double underscore idiom has been introduced because of the backward compatibility issues. According to the documentation:

onConstructor参数接受一个注释数组(或者像这个具体例子中的单个注释),这些注释将被放在生成的构造函数上。由于向后兼容的问题,我们引入了双下划线的习惯用语。根据文档的内容。

The reason of the weird syntax is to make this feature work in javac 7 compilers; the @__ type is an annotation reference to the annotation type __ (double underscore) which doesn’t actually exist; this makes javac 7 delay aborting the compilation process due to an error because it is possible an annotation processor will later create the __ type.

奇怪的语法的原因是为了使这个功能在javac 7编译器中工作;@__类型是对注解类型__(双下划线)的注解引用,而这个类型实际上并不存在;这使得javac 7延迟因错误而中止编译过程,因为有可能一个注解处理器后来会创建__类型。

5. Summary

5.总结

In this tutorial, we showed that there is no need to favor field-based DI over constructor-based DI in terms of increased boilerplate code.

在本教程中,我们表明,就增加的模板代码而言,没有必要偏向于基于字段的DI而不是基于构造函数的DI。

Thanks to Lombok, it’s possible to automate common code generation without a performance impact on runtime, abbreviating long, obscuring code to the use of a single-line annotation.

感谢Lombok,它可以在不影响运行时性能的情况下自动生成普通的代码,将长而不明显的代码缩写为使用单行注释。

The code used during the tutorial is available over on GitHub.

教程中使用的代码可在GitHub上找到