那些年背过的题:ApplicationContext-依赖注入

267 阅读16分钟

在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,并依次调用它们的postProcessBeforeInitializationpostProcessAfterInitialization方法。

完整的依赖注入流程

总结一下,Spring IOC容器的依赖注入流程大致如下:

  1. Bean 定义解析:读取并解析配置文件或注解中的Bean定义。

  2. 实例化 Bean:使用构造函数或工厂方法创建Bean实例。

  3. 属性填充:通过反射机制将依赖项注入到Bean实例中。

  4. 初始化

    • 调用实现了InitializingBean接口的afterPropertiesSet方法。
    • 调用自定义的初始化方法。
  5. 后处理器处理

    • 调用所有注册的BeanPostProcessorpostProcessBeforeInitialization方法。
    • 调用所有注册的BeanPostProcessorpostProcessAfterInitialization方法。
  6. 完成初始化:经过上述步骤后,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 解决依赖注入冲突的关键类:

  1. AutowiredAnnotationBeanPostProcessor:负责处理带有 @Autowired 注解的字段、方法和构造函数。
  2. DefaultListableBeanFactory:是Spring IOC容器的默认实现,负责管理所有bean定义和实例化。
  3. 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));
}

具体流程总结

  1. 查找自动装配候选者:通过 findAutowireCandidates 查找所有符合条件的bean,构建一个候选者列表。
  2. 优先选择 @Primary 注解:调用 determinePrimaryCandidate 方法,如果存在标记为 @Primary 的bean,则优先选择这个bean。
  3. 选择最高优先级的bean:如果没有找到 @Primary 标记的bean,则调用 determineHighestPriorityCandidate 方法,选择具有最高优先级的bean。优先级通常通过 PriorityOrdered 或 Ordered 接口来定义。
  4. 处理多重候选者的冲突:如果在上述步骤中都无法得到唯一的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 容器进行依赖注入时:

  1. 查找候选者

    • Spring 首先会查找所有实现 MyService 接口的bean,找到 ServiceA 和 ServiceB
  2. 优先选择 @Primary

    • 然后,Spring 会检查是否有 @Primary 注解。在这里,ServiceB 被标记为 @Primary,因此它被选为注入的候选bean。
  3. 最终注入

    • 最终,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属性,默认为truenametype属性
结合使用可以与@Qualifier结合使用不需要额外注解
灵活性高,通过@Qualifier解决冲突中等,按名称失败后按类型
适用性主要用于Spring应用跨框架、跨平台使用

选择建议

  1. 使用场景和习惯

    • 如果你主要在 Spring 框架中开发应用,且偏好通过类型注入依赖,推荐使用@Autowired
    • 如果你希望你的代码能更具通用性,或符合 Java EE 标准,使用@Resource可能更合适。
  2. 控制注入行为

    • 如果你希望精确控制注入行为,特别是需要解决多个同类型bean的注入问题,@Autowired@Qualifier的组合会更加灵活。
    • 如果你希望简单地按名称注入,或者希望避免引入Spring特有的注解,@Resource会显得更加简洁和直观。
  3. 团队规范和一致性

    • 在团队开发中,选择一种注解并统一使用会降低混乱,提高代码的一致性。

开发工具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方法注入的依赖可以提供更好的自动完成、重构支持和代码导航。
  • 静态分析:静态代码分析工具可以更准确地检测和报告可能的问题,例如未能正确注入依赖等。