概述
自Spring 2.5版本开始,框架引入了基于注解的依赖注入(Dependency Injection)。主要的注解是@Autowired
。它使得Spring能够自动识别并注入协作的bean到我们的bean中。
在这个教程中,我们将首先了解如何启用自动注入以及注入bean的各种方式。随后,我们将讨论如何使用@Qualifier
注解解决bean冲突,以及可能的异常情况。
启用@Autowired
注解
Spring框架支持自动依赖注入。换句话说,通过在Spring配置文件中声明所有bean依赖,Spring容器可以自动管理协作bean之间的关系。这就是所谓的Spring bean自动注入。
要在应用程序中使用基于Java的配置,我们可以启用注解驱动的注入来加载Spring配置:
@Configuration
@ComponentScan("com.baeldung.autowire.sample")
public class AppConfig {}
另外,<context:annotation-config>
注解主要用于激活Spring XML文件中的依赖注入注解。
此外,Spring Boot引入了@SpringBootApplication
注解。这个单一注解等同于使用@Configuration
、@EnableAutoConfiguration
和@ComponentScan
。
让我们在应用程序的主类中使用这个注解:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
因此,当运行Spring Boot应用时,它会自动扫描当前包及其子包中的组件,并将它们注册到Spring的应用上下文中,允许我们使用@Autowired
进行bean注入。
使用@Autowired
启用注解注入后,我们可以在属性、setter方法和构造函数上使用自动注入。
@Autowired
在属性上的使用
让我们看看如何使用@Autowired
注解标记属性,从而消除getter和setter的需要。首先,定义一个fooFormatter
bean:
@Component("fooFormatter")
public class FooFormatter {
public String format() {
return "foo";
}
}
然后,我们使用@Autowired
在字段定义上将此bean注入到FooService
bean中:
@Component
public class FooService {
@Autowired
private FooFormatter fooFormatter;
}
结果,当FooService
创建时,Spring会注入fooFormatter
。
@Autowired
在setter方法上的使用
现在尝试在setter方法上添加@Autowired
注解。
在下面的例子中,当FooService
创建时,setter方法会接收到FooFormatter
实例:
public class FooService {
private FooFormatter fooFormatter;
@Autowired
public void setFormatter(FooFormatter fooFormatter) {
this.fooFormatter = fooFormatter;
}
}
@Autowired
在构造函数上的使用
最后,我们在构造函数上使用@Autowired
。
我们会看到Spring作为参数注入一个FooFormatter
实例到FooService
的构造函数中:
public class FooService {
private FooFormatter fooFormatter;
@Autowired
public FooService(FooFormatter fooFormatter) {
this.fooFormatter = fooFormatter;
}
}
@Autowired
与可选依赖
在bean构建时,@Autowired
依赖应该是可用的。否则,如果Spring无法为注入找到bean,它会抛出异常。
因此,这会导致Spring容器无法成功启动,抛出类似以下形式的异常:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [com.autowire.sample.FooDAO] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
要解决这个问题,我们需要声明所需类型的bean:
public class FooService {
@Autowired(required = false)
private FooDAO dataAccessor;
}
自动注入歧义解决
默认情况下,Spring通过类型来解析@Autowired
条目。如果容器中有多个相同类型的bean,框架会抛出致命异常。
要解决这种冲突,我们需要明确告诉Spring我们想要注入哪个bean。
使用@Qualifier
进行注入
例如,让我们看看如何使用@Qualifier
注解来指示所需的bean。
首先,我们定义两个类型为Formatter
的bean:
@Component("fooFormatter")
public class FooFormatter implements Formatter {
public String format() {
return "foo";
}
}
@Component("barFormatter")
public class BarFormatter implements Formatter {
public String format() {
return "bar";
}
}
现在尝试将Formatter
bean注入到FooService
类中:
public class FooService {
@Autowired
private Formatter formatter;
}
在本例中,Spring容器有两份Formatter
的具体实现。因此,当构建FooService
时,Spring会抛出NoUniqueBeanDefinitionException
异常:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [com.autowire.sample.Formatter] is defined:
expected single matching bean but found 2: barFormatter,fooFormatter
我们可以通过@Qualifier
注解来避免这种情况,具体做法是:
public class FooService {
@Autowired
@Qualifier("fooFormatter")
private Formatter formatter;
}
当存在多个相同类型的bean时,使用@Qualifier
来避免歧义是一个好主意。
请注意,@Qualifier
注解的值应与我们在@Component
注解中为FooFormatter
实现声明的名称匹配。
使用自定义@Qualifier
进行注入
Spring还允许我们创建自己的自定义@Qualifier
注解。为此,我们需要提供@Qualifier
注解的定义:
@Qualifier
@Target({
ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FormatterType {
String value();
}
然后,我们可以在各种实现中使用FormatterType
指定一个自定义值:
@FormatterType("Foo")
@Component
public class FooFormatter implements Formatter {
public String format() {
return "foo";
}
}
@FormatterType("Bar")
@Component
public class BarFormatter implements Formatter {
public String format() {
return "bar";
}
}
最后,我们的自定义@Qualifier
注解准备好用于自动注入:
@Component
public class FooService {
@Autowired
@FormatterType("Foo")
private Formatter formatter;
}
@Target 注解中的值限制了注解的适用范围,在我们的例子中,它适用于字段、方法、类型和参数。
使用名称进行注入
Spring使用bean的名称作为默认的注解值。它会检查容器,寻找与属性名完全匹配的bean,然后注入它。
因此,在我们的示例中,Spring会将fooFormatter
属性名与FooFormatter
实现匹配。因此,在构建FooService
时,它会注入特定的实现:
public class FooService {
@Autowired
private Formatter fooFormatter;
}
总结
本文讨论了自动注入及其使用方式。我们还探讨了解决由缺失bean或模糊的bean注入引发的两种常见自动注入异常的方法。
这篇文章的源代码可以在GitHub项目中获取。