在Spring框架中,依赖注入主要有三种方式:构造函数注入(Constructor Injection)、Setter方法注入(Setter Injection)和字段注入(Field Injection)。每种方式各有优劣,下面对这三种依赖注入方式进行详细的对比。
1. 构造函数注入 (Constructor Injection)
优点
- 强制依赖:通过构造函数注入,确保所有的依赖在对象实例化时被提供,因此避免了对象处于未完全初始化状态。
- 不可变性:通过构造函数注入,可以创建不可变对象,因为依赖项只能在对象实例化时被设置,之后不能改变。
- 易于测试:构造函数注入使得bean更容易被单元测试,因为依赖项可以通过构造函数参数直接传递。
缺点
- 复杂性:当一个类有很多依赖时,其构造函数会变得非常复杂。
- 可读性:对于包含多个依赖的构造函数,代码的可读性可能会有所下降。
示例
public class MyService {
private final Dependency dependency;
@Autowired
public MyService(Dependency dependency) {
this.dependency = dependency;
}
}
2. Setter方法注入 (Setter Injection)
优点
- 灵活性:Setter方法注入允许在对象实例化后再注入依赖,提供了更大的灵活性。
- 可选依赖:适用于一些可选依赖,可以在需要的时候设置,而不是在对象创建时必须提供。
缺点
- 部分初始化状态:对象在依赖注入完成之前可能处于部分初始化状态,这可能导致潜在的NPE(NullPointerException)。
- 可变性:由于依赖可以在任何时候被修改,因此对象不再是不可变的,可能会影响线程安全。
示例
public class MyService {
private Dependency dependency;
@Autowired
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
3. 字段注入 (Field Injection)
优点
- 简洁性:字段注入不需要额外的setter方法或构造函数参数,使代码更加简洁。
- 方便性:对于简单的依赖关系,字段注入非常方便,无需修改现有构造函数。
缺点
- 难以测试:字段注入使得单元测试变得困难,因为依赖项通常是通过反射设置的,不像构造函数和setter方法那样可以直接传递。
- 隐藏依赖关系:字段注入将依赖关系隐藏在类内部,使得类的依赖关系不够明显,降低了代码的可读性和维护性。
示例
public class MyService {
@Autowired
private Dependency dependency;
}
总结
| 特性 | 构造函数注入 | Setter方法注入 | 字段注入 |
|---|---|---|---|
| 强制依赖 | 是 | 否 | 否 |
| 灵活性 | 低 | 高 | 中 |
| 代码简洁性 | 中等 | 低 | 高 |
| 可读性 | 较高 | 中等 | 低 |
| 易于测试 | 是 | 是 | 否 |
| 不可变性 | 是 | 否 | 否 |
在实际开发中,选择哪种依赖注入方式取决于具体的应用场景和需求:
- 如果依赖项是必需的且不会改变,推荐使用构造函数注入。
- 如果依赖项是可选的或需要较高的灵活性,Setter方法注入是一个不错的选择。
- 对于简单的、无需频繁变动的依赖,可以使用字段注入来简化代码。
依赖注入源码分析
1. IOC 容器的初始化
首先,通过ApplicationContext接口来启动Spring容器:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
或者使用XML配置文件:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
2. Bean 定义解析
在Spring中,Bean定义(包括其依赖关系)的解析和加载通常由BeanDefinitionReader来完成。例如,当使用基于注解的配置时,AnnotatedBeanDefinitionReader会被用来读取bean定义。
XML 配置方式
如果使用的是ClassPathXmlApplicationContext,则会通过XmlBeanDefinitionReader解析XML文件中的bean定义。
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
// 核心方法,用于加载并解析XML配置文件
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 实际上是委托给BeanDefinitionParserDelegate进行解析
Document doc = doLoadDocument(resource, inputSource);
return registerBeanDefinitions(doc, resource);
}
}
注解方式
使用AnnotationConfigApplicationContext时,它会注册一个AnnotatedBeanDefinitionReader,用于解析注解。
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
3. Bean 实例化与依赖注入
Spring IOC容器在实例化Bean时,主要分为三步:实例化、属性填充、初始化。
实例化
实例化是指创建Bean的实例对象。这一步通常是通过Java反射机制来实现的。Spring在实例化Bean时,通常使用InstantiationStrategy接口来统一处理,默认的实现类是SimpleInstantiationStrategy。
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
// 使用反射机制创建实例对象
return instantiate(beanClass.getDeclaredConstructor(), args);
}
属性填充
属性填充是指将Bean定义中的属性值注入到Bean实例中。在Spring中,这一步通常由AutowiredAnnotationBeanPostProcessor等后处理器完成。
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
// 获取所有需要注入的属性和值
PropertyValues pvs = mbd.getPropertyValues();
// 检查依赖注入,比如@Autowired注解标记的属性
if (mbd.isAutowireCandidate()) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
初始化
初始化是指在Bean实例化和属性填充之后的额外处理,包括调用初始化方法以及其他BeanPostProcessor的处理。
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable ex) {
throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
4. 依赖解析
Spring IOC容器会根据Bean定义中的依赖关系图来确定各个Bean之间的依赖关系,并按照依赖关系图来实例化和注入Bean。对于构造函数注入,Spring会使用ConstructorResolver来解析依赖关系。
下面是autowireConstructor方法的部分实现:
protected BeanWrapper autowireConstructor(
final String beanName, final RootBeanDefinition mbd, Constructor<?>[] ctors,
@Nullable Object[] explicitArgs) {
Constructor<?> constructorToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;
// 确定使用哪一个构造函数以及相应的参数
for (Constructor<?> candidate : ctors) {
ArgumentsHolder argsHolder = createArgumentArray(beanName, mbd, explicitArgs);
if (argsHolder != null) {
constructorToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
break;
}
}
if (constructorToUse == null) {
throw new BeanCreationException(beanName, "Could not resolve matching constructor");
}
BeanWrapperImpl bw = new BeanWrapperImpl();
bw.setBeanInstance(instantiate(constructorToUse, argsToUse));
return bw;
}
在此过程中,createArgumentArray方法负责根据bean定义和指定的参数创建一个适合构造函数调用的参数数组:
private ArgumentsHolder createArgumentArray(
String beanName, RootBeanDefinition mbd, Object[] explicitArgs) {
ArgumentsHolder argsHolder = new ArgumentsHolder(explicitArgs.length);
for (int i = 0; i < explicitArgs.length; i++) {
argsHolder.arguments[i] = resolveDependency(explicitArgs[i], beanName);
}
return argsHolder;
}
private Object resolveDependency(Object dependency, String beanName) {
// 实际上会调用BeanFactory的getBean方法来获取依赖
return this.beanFactory.getBean(dependency.toString());
}
属性填充
对于通过属性注入的依赖,Spring使用反射机制将依赖注入到bean实例中。主要通过AutowiredAnnotationBeanPostProcessor类来处理。
public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
// 查找并缓存注解元数据
InjectionMetadata metadata = buildAutowiringMetadata(clazz);
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
elements.add(new AutowiredFieldElement(field, true));
}
}
return new InjectionMetadata(clazz, elements);
}
}
初始化
在初始化阶段,Spring容器会调用bean的初始化方法,如@PostConstruct注解标记的方法,或实现了InitializingBean接口的afterPropertiesSet方法。以下是调用初始化方法的核心代码:
以下是invokeInitMethods方法的进一步说明:
protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) throws Throwable {
// 判断bean是否实现了InitializingBean接口
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
((InitializingBean) bean).afterPropertiesSet();
}
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName))) {
Method initMethod = bean.getClass().getMethod(initMethodName);
initMethod.invoke(bean);
}
}
}
BeanPostProcessor
在Spring的初始化过程中,还会调用Bean的后处理器,这些处理器可以在Bean初始化前后进行额外的处理。这个机制允许开发者在Bean生命周期的不同阶段插入自定义逻辑。
protected Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
result = processor.postProcessBeforeInitialization(result, beanName);
if (result == null) {
return null;
}
}
return result;
}
protected Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
result = processor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return null;
}
}
return result;
}
这些方法会遍历所有注册的BeanPostProcessor,并依次调用它们的postProcessBeforeInitialization和postProcessAfterInitialization方法。
完整的依赖注入流程
总结一下,Spring IOC容器的依赖注入流程大致如下:
-
Bean 定义解析:读取并解析配置文件或注解中的Bean定义。
-
实例化 Bean:使用构造函数或工厂方法创建Bean实例。
-
属性填充:通过反射机制将依赖项注入到Bean实例中。
-
初始化:
- 调用实现了
InitializingBean接口的afterPropertiesSet方法。 - 调用自定义的初始化方法。
- 调用实现了
-
后处理器处理:
- 调用所有注册的
BeanPostProcessor的postProcessBeforeInitialization方法。 - 调用所有注册的
BeanPostProcessor的postProcessAfterInitialization方法。
- 调用所有注册的
-
完成初始化:经过上述步骤后,Bean被完全初始化,并可以被IOC容器使用。
示例代码
最后,通过一个简单的示例可以debug查看整个流程:
配置类
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
服务类
public class MyService implements InitializingBean {
public void afterPropertiesSet() throws Exception {
System.out.println("MyService is initialized");
}
@PostConstruct
public void init() {
System.out.println("MyService PostConstruct method called");
}
}
主应用程序
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
}
}
运行以上代码时,控制台将输出:
MyService PostConstruct method called
MyService is initialized
这表明MyService的初始化方法已经被成功调用。这样,我们就完成了对Spring IOC容器依赖注入机制的全面解析,包括其源码实现及初始化流程。
依赖冲突解决源码分析
Spring IOC 解决依赖注入冲突的关键类:
AutowiredAnnotationBeanPostProcessor:负责处理带有@Autowired注解的字段、方法和构造函数。DefaultListableBeanFactory:是Spring IOC容器的默认实现,负责管理所有bean定义和实例化。AutowireCandidateResolver:用于确定一个bean是否可以作为自动装配候选者。
源码分析:
1. AutowiredAnnotationBeanPostProcessor
AutowiredAnnotationBeanPostProcessor 是处理 @Autowired 注解的核心,它在bean初始化过程中会自动调用以下方法:
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
// ... 其他代码 ...
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
metadata.inject(bean, beanName, pvs);
}
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// ... 省略缓存相关逻辑 ...
InjectionMetadata metadata = buildAutowiringMetadata(clazz);
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
// 遍历类的所有字段和方法,查找带有 @Autowired 的元素
ReflectionUtils.doWithLocalFields(clazz, field -> {
if (field.isAnnotationPresent(Autowired.class)) {
boolean required = field.getAnnotation(Autowired.class).required();
elements.add(new AutowiredFieldElement(field, required));
}
});
// 遍历类的所有方法
ReflectionUtils.doWithLocalMethods(clazz, method -> {
if (method.isAnnotationPresent(Autowired.class)) {
boolean required = method.getAnnotation(Autowired.class).required();
elements.add(new AutowiredMethodElement(method, required));
}
});
return new InjectionMetadata(clazz, elements);
}
// ... 其他代码 ...
}
2. AutowiredFieldElement 和 AutowiredMethodElement
这些内部类负责实际的依赖注入操作。在 inject 方法中,它们会调用 beanFactory.resolveDependency 方法来解析依赖:
private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
// ... 其他代码 ...
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
} else {
DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required);
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);
} catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
this.cachedFieldValue = descriptor;
this.cached = true;
}
}
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
// ... 其他代码 ...
}
3. DefaultListableBeanFactory 的 resolveDependency 方法
resolveDependency 方法是依赖解析的核心,它在候选者之间进行选择:
@Override
public Object resolveDependency(
DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// 获取所有符合类型要求的bean候选者
Map<String, Object> matchingBeans = findAutowireCandidates(requestingBeanName, descriptor.getDependencyType(), descriptor);
if (matchingBeans.isEmpty()) {
// 没有找到匹配的bean
if (descriptor.isRequired()) {
raiseNoMatchingBeanFound(descriptor.getDependencyType(), descriptor.getResolvableType());
}
return null;
}
// 如果找到多个候选者,通过 `@Primary` 和 `@Qualifier` 注解进行解析
if (matchingBeans.size() > 1) {
String primaryCandidate = determinePrimaryCandidate(matchingBeans, descriptor);
if (primaryCandidate != null) {
return matchingBeans.get(primaryCandidate);
}
String prioritizedCandidate = determineHighestPriorityCandidate(matchingBeans, descriptor);
if (prioritizedCandidate != null) {
return matchingBeans.get(prioritizedCandidate);
}
if (!descriptor.isEager()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
throw new NoUniqueBeanDefinitionException(descriptor.getDependencyType(), matchingBeans.size(), "Expected single matching bean but found " + matchingBeans.size() + ": " + matchingBeans.keySet());
}
// 找到一个候选者
Map.Entry<String, Object> candidateEntry = matchingBeans.entrySet().iterator().next();
if (autowiredBeanNames != null) {
autowiredBeanNames.add(candidateEntry.getKey());
}
return candidateEntry.getValue();
}
关键方法解释
findAutowireCandidates
这个方法用于查找符合自动装配条件的所有候选bean:
protected Map<String, Object> findAutowireCandidates(String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
// 获取所有符合类型的bean定义
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());
Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
for (Class<?> autowireType : descriptor.resolveCandidateTypes()) {
for (String candidate : candidateNames) {
if (!(candidate.equals(beanName) && isSelfReference(beanName, descriptor))) {
if (isAutowireCandidate(candidate, descriptor)) {
addCandidateEntry(result, candidate, descriptor, autowireType);
}
}
}
}
return result;
}
determinePrimaryCandidate
如果存在 @Primary 注解的bean,这个方法会优先选择该bean:
@Nullable
protected String determinePrimaryCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
String primaryBeanName = null;
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey();
Object beanInstance = entry.getValue();
if (isPrimary(beanInstance, candidateName)) {
if (primaryBeanName != null) {
boolean isPrimaryDefinition = (beanDefinition instanceof RootBeanDefinition && ((RootBeanDefinition) beanDefinition).isPrimary());
if (isPrimaryDefinition) {
if (primaryBeanName != null) {
throw new NoUniqueBeanDefinitionException(descriptor.getDependencyType(), candidates.size(),
"Multiple primary beans found among candidates: " + candidates.keySet());
}
primaryBeanName = candidateName;
}
} else {
primaryBeanName = candidateName;
}
}
}
return primaryBeanName;
}
determineHighestPriorityCandidate
该方法用于确定具有最高优先级的候选Bean:
@Nullable
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
String highestPriorityBeanName = null;
int highestPriority = Integer.MAX_VALUE;
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey();
Object beanInstance = entry.getValue();
int priority = getPriority(beanInstance);
if (highestPriorityBeanName == null || priority < highestPriority) {
highestPriorityBeanName = candidateName;
highestPriority = priority;
}
else if (priority == highestPriority) {
throw new NoUniqueBeanDefinitionException(descriptor.getDependencyType(), candidates.size(),
"Multiple beans found with the same priority ('" + highestPriority + "') among candidates: " + candidates.keySet());
}
}
return highestPriorityBeanName;
}
private int getPriority(Object beanInstance) {
// 获取bean的优先级,这里假设bean实现了PriorityOrdered接口
return (beanInstance instanceof PriorityOrdered ? ((PriorityOrdered) beanInstance).getOrder() :
(beanInstance instanceof Ordered ? ((Ordered) beanInstance).getOrder() : Ordered.LOWEST_PRECEDENCE));
}
具体流程总结
- 查找自动装配候选者:通过
findAutowireCandidates查找所有符合条件的bean,构建一个候选者列表。 - 优先选择 @Primary 注解:调用
determinePrimaryCandidate方法,如果存在标记为@Primary的bean,则优先选择这个bean。 - 选择最高优先级的bean:如果没有找到
@Primary标记的bean,则调用determineHighestPriorityCandidate方法,选择具有最高优先级的bean。优先级通常通过PriorityOrdered或Ordered接口来定义。 - 处理多重候选者的冲突:如果在上述步骤中都无法得到唯一的bean,则抛出
NoUniqueBeanDefinitionException异常,提示多个候选者冲突。
案例分析
假设以下依赖情况:
@Component
public class ServiceA implements MyService {}
@Component
@Primary
public class ServiceB implements MyService {}
@Component
public class ClientComponent {
private final MyService myService;
@Autowired
public ClientComponent(MyService myService) {
this.myService = myService;
}
}
在这个例子中,当Spring IOC 容器进行依赖注入时:
-
查找候选者:
- Spring 首先会查找所有实现
MyService接口的bean,找到ServiceA和ServiceB。
- Spring 首先会查找所有实现
-
优先选择 @Primary:
- 然后,Spring 会检查是否有
@Primary注解。在这里,ServiceB被标记为@Primary,因此它被选为注入的候选bean。
- 然后,Spring 会检查是否有
-
最终注入:
- 最终,
ClientComponent中的myService字段将被注入ServiceB实例。
- 最终,
@Autowired和@resource方法对比
@Autowired和@Resource是Spring框架中常用的注解,用于自动注入依赖。虽然它们都可以实现依赖注入,但在使用上有一些重要的区别。下面对这两个注解进行详细比较。
@Autowired
定义
@Autowired是 Spring 框架提供的注解,用于自动装配依赖。
主要特性
- 按类型注入:
@Autowired默认按类型(by type)进行注入。 - 可选性:可以使用
required属性来指定是否必须注入,默认为true。 - 与其他注解结合:通常与
@Qualifier注解一起使用,以解决多个候选者的问题。
示例
public class MyService {
// 按类型注入
@Autowired
private Dependency dependency;
// 设置required属性
@Autowired(required = false)
private OptionalDependency optionalDependency;
}
使用@Qualifier来区分多个同类型bean:
public class MyService {
@Autowired
@Qualifier("specificDependency")
private Dependency dependency;
}
@Resource
定义
@Resource是 Java EE 提供的注解,也是 JSR-250 标准的一部分,可以在 Spring 中使用。
主要特性
- 按名称注入:
@Resource默认按名称(by name)进行注入。如果没有指定名称,则使用字段名作为bean的名称。 - 灵活性:如果按名称没找到匹配的bean,再按类型进行匹配。
- 标准化:由于是JSR-250标准的一部分,
@Resource具有更好的跨框架和平台的兼容性。
示例
public class MyService {
// 按名称注入
@Resource(name = "myDependency")
private Dependency dependency;
// 如果name属性没有指定,则使用字段名作为bean的名称
@Resource
private AnotherDependency anotherDependency;
}
对比
| 特性 | @Autowired | @Resource |
|---|---|---|
| 提供方 | Spring 框架 | JSR-250 标准(Java EE) |
| 默认注入方式 | 按类型(by type) | 按名称(by name) |
| 可选属性 | required属性,默认为true | name和type属性 |
| 结合使用 | 可以与@Qualifier结合使用 | 不需要额外注解 |
| 灵活性 | 高,通过@Qualifier解决冲突 | 中等,按名称失败后按类型 |
| 适用性 | 主要用于Spring应用 | 跨框架、跨平台使用 |
选择建议
-
使用场景和习惯:
- 如果你主要在 Spring 框架中开发应用,且偏好通过类型注入依赖,推荐使用
@Autowired。 - 如果你希望你的代码能更具通用性,或符合 Java EE 标准,使用
@Resource可能更合适。
- 如果你主要在 Spring 框架中开发应用,且偏好通过类型注入依赖,推荐使用
-
控制注入行为:
- 如果你希望精确控制注入行为,特别是需要解决多个同类型bean的注入问题,
@Autowired与@Qualifier的组合会更加灵活。 - 如果你希望简单地按名称注入,或者希望避免引入Spring特有的注解,
@Resource会显得更加简洁和直观。
- 如果你希望精确控制注入行为,特别是需要解决多个同类型bean的注入问题,
-
团队规范和一致性:
- 在团队开发中,选择一种注解并统一使用会降低混乱,提高代码的一致性。
开发工具IDE为什么不推荐使用@Autowired
推荐使用 @Autowired 注解来进行依赖注入。这种建议通常基于以下几个原因:
1. 隐式依赖
问题
@Autowired注解会导致依赖关系隐式化,即依赖关系并没有明确地体现在构造方法参数或setter方法中,这样会使代码的可读性和可维护性降低。
影响
- 阅读困难:其他开发者在阅读代码时,很难一眼看出这个类都依赖于哪些外部组件。
- 调试困难:如果出现依赖注入问题,不容易定位问题所在。
例子
public class MyService {
@Autowired
private Dependency dependency;
// dependencies are not obvious in constructor or methods
}
2. 测试复杂性
问题
@Autowired注解使得单元测试变得复杂,因为在测试环境下,需要通过反射或其他方式来为私有字段注入依赖。
影响
- 准备工作多:为了注入依赖,必须设置 Spring 上下文或者使用反射工具,这增加了编写和维护测试代码的复杂性。
- 不易模拟:手动注入和模拟依赖项变得更加复杂和麻烦。
例子
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
public class MyServiceTest {
@Autowired
private MyService myService;
@Test
public void testMyService() {
// Complex setup to inject dependencies
}
}
3. 构造函数注入的优点
优点
- 强制依赖声明:通过构造函数注入,所有的依赖都是显式的,实例化对象时必须提供依赖,从而避免NPE等问题。
- 不可变性:依赖在对象创建时就被设置,不会在之后被更改,保证了对象状态的稳定。
- 便于测试:构造函数注入可以直接传递mock对象,使单元测试更加简单和直观。
例子
public class MyService {
private final Dependency dependency;
@Autowired
public MyService(Dependency dependency) {
this.dependency = dependency;
}
}
// Unit test becomes simpler
public class MyServiceTest {
@Test
public void testMyService() {
Dependency mockDependency = Mockito.mock(Dependency.class);
MyService myService = new MyService(mockDependency);
// Directly test the method without complex setup
}
}
4. IDE支持和代码提示
问题
IDE在处理@Autowired 注解时,可能无法提供全面的代码分析和提示。
影响
- 自动完成:IDE对通过构造函数或setter方法注入的依赖可以提供更好的自动完成、重构支持和代码导航。
- 静态分析:静态代码分析工具可以更准确地检测和报告可能的问题,例如未能正确注入依赖等。