柏虎资源网

专注编程学习,Python、Java、C++ 教程、案例及资源

@Autowired 注解总报错?互联网大厂后端开发必知的原理与避坑指南

你是否曾在使用 Spring Boot 开发时,对着控制台抛出的
NoSuchBeanDefinitionException错误抓耳挠腮?当你自信满满地在代码里写下@Autowired注解,满心期待依赖注入顺利完成,结果却换来程序启动失败的报错提示,那一刻的崩溃,相信不少互联网大厂的后端开发人员都经历过。

在互联网技术飞速发展的当下,Spring Boot 凭借其高效便捷的特性,成为了后端开发的主流框架。而@Autowired注解作为 Spring Boot 中实现依赖注入的核心工具,就像一把 “瑞士军刀”,看似简单好用,实则暗藏玄机。如果不了解它的原理,在实际开发过程中,很容易陷入各种 “坑” 中,影响项目的进度和质量。今天,咱们就深入地探讨一下这个注解。

@Autowired注解的原理深度剖析

@Autowired注解是 Spring 框架提供的一种强大的依赖注入方式,它基于 Spring 的依赖注入(DI)机制,通过类型匹配来自动装配 Bean。从本质上讲,依赖注入是控制反转(IoC)的一种实现方式。IoC 就像是一个对象工厂,我们把对象的创建和依赖关系的管理都交给这个工厂。以往,对象的创建和依赖关系的维护都由开发者手动完成,而有了 IoC,对象无需自行创建或管理它的依赖关系,这些依赖关系会被自动注入到需要它们的对象当中。

在 Spring 的 IOC 容器初始化过程中,当遇到带有@Autowired注解的属性或构造函数时,容器会开启一段复杂但有序的 “寻找之旅”。容器首先会进入
AbstractApplicationContext#refresh方法中的
this.finishBeanFactoryInitialization(beanFactory)环节,该方法负责初始化所有非延迟加载的单例 bean 。其核心源码如下:

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // 注册默认的转换服务
    beanFactory.registerConversionService(ConversionServiceFactory.createDefaultConversionService());
    // 注册默认的属性编辑器
    registerDefaultEditorConfigurer(beanFactory);
    // 初始化所有非延迟加载的单例bean
    beanFactory.preInstantiateSingletons();
}

接着,进入
AbstractAutowireCapableBeanFactory#createBean()方法,其中的doCreateBean()方法是构建 bean 的核心,bean 的实例化及初始化逻辑都在这里。关键源码片段如下:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {

    // 实例化Bean
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

    // 进行属性填充
    populateBean(beanName, mbd, instanceWrapper);

    // 初始化Bean
    return initializeBean(beanName, exposedObject, mbd);
}

在doCreateBean()方法中,
applyMergedBeanDefinitionPostProcessors方法会被调用,该方法会触发所有
MergedBeanDefinitionPostProcessor的
postProcessMergedBeanDefinition方法。这里面有一个关键的实现类
AutowiredAnnotationBeanPostProcessor,它就是处理依赖注入的后置处理器。其核心处理逻辑如下:

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    metadata.checkConfigMembers(beanDefinition);
}


AutowiredAnnotationBeanPostProcessor会对 bean 进行注解扫描,如果扫描到了@Autowired和@Value注解,就会把对应的方法或者属性封装起来,最终封装成InjectionMetadata对象。之后,populateBean()方法登场,这个方法至关重要,它负责填充 Bean 实例属性,完成依赖对象的注入。其核心代码如下:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    PropertyValues pvs = mbd.getPropertyValues();

    if (mbd.hasPropertyValues()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                pvs = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
            }
        }
    }
    // 其他属性填充逻辑
}

此时,Bean 中需要依赖注入的成员已经在之前的
applyMergedBeanDefinitionPostProcessors步骤中,被
AutowiredAnnotationBeanPostProcessor存储起来。接着调用metadata.inject()方法进行属性填充,遍历InjectedElement集合继续执行注入逻辑element.inject() 。这里有两个重要的实现类AutowiredFieldElement及AutowiredMethodElement,分别对应字段注入及方法注入实现。以AutowiredFieldElement为例,它利用 Java 的反射机制,通过
ReflectionUtils.makeAccessible(field)为属性进行赋值,核心的resolveFieldValue()方法则进一步完成值的解析和注入,源码如下:

private Object resolveFieldValue(Object bean, String beanName, @Nullable PropertyDescriptor pd) throws Throwable {
    Field field = (Field) this.member;
    Object value;
    if (this.cached) {
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
    }
    else {
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null, "No BeanFactory available");
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        synchronized (this) {
            if (!this.cached) {
                if (value != null || this.required) {
                    this.cachedFieldValue = desc;
                    registerDependentBeans(beanName, autowiredBeanNames);
                    if (autowiredBeanNames.size() == 1) {
                        String autowiredBeanName = autowiredBeanNames.iterator().next();
                        if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                            this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                    desc, autowiredBeanName, field.getType());
                        }
                    }
                }
                else {
                    this.cachedFieldValue = null;
                }
                this.cached = true;
            }
        }
    }
    if (value != null && pd != null && !ObjectUtils.isCompatibleValue(pd.getPropertyType(), value)) {
        try {
            value = typeConverter.convertIfNecessary(value, pd.getPropertyType(), pd);
        }
        catch (TypeMismatchException ex) {
            if (logger.isTraceEnabled()) {
                logger.trace("Failed to convert value of type [" + value.getClass().getName() +
                        "] to required type [" + pd.getPropertyType().getName() + "] for property '" +
                        pd.getName() + "': " + ex.toString());
            }
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "Injection of autowired dependencies failed; nested exception is " + ex.toString());
        }
    }
    return value;
}

至此,
AutowiredAnnotationBeanPostProcessor通过postProcessPropertyValues()方法完成了自动装配的关键流程。

@Autowired注解引发的常见问题及深度解决方案

多类型 Bean 的注入困境

当容器中存在多个同类型的 Bean 时,@Autowired就会陷入 “选择困难症”,不知道该注入哪一个,从而导致报错。例如,我们有一个UserService接口,同时存在UserServiceImpl1和UserServiceImpl2两个实现类,并且都被注册到了 Spring 容器中。当我们在其他类中使用@Autowired注入UserService时,Spring 容器就无法确定该注入哪一个实现类。

为了解决这个问题,我们可以结合@Qualifier注解使用。@Qualifier注解能够通过指定 Bean 的名称,明确告诉 Spring 容器该注入哪一个具体的 Bean。比如:

@Autowired
@Qualifier("userServiceImpl1")
private UserService userService;

除此之外,还可以使用@Primary注解。当使用自动配置的方式装配 Bean 时,如果存在多个候选者,被@Primary注解修饰的候选者会被选中,作为自动配置的值。我们只需要在UserServiceImpl1类上添加@Primary注解,那么在使用@Autowired注入UserService时,Spring 就会优先选择UserServiceImpl1 。

循环依赖的棘手难题

在遇到循环依赖的情况时,@Autowired也可能无法正常工作。比如,A类依赖B类,而B类又依赖A类,形成了一个循环依赖的闭环。在 Spring 框架中,虽然本身提供了三级缓存机制来解决大部分循环依赖情况,但对于一些复杂的循环依赖场景,仍然可能出现问题。

对于这种复杂场景,我们可以考虑使用@Lazy注解进行延迟加载。@Lazy注解会使依赖对象的创建延迟到实际使用时才进行,这样可以打破循环依赖的僵局。例如,在A类中对B类的依赖注入处添加@Lazy注解:

@Autowired
@Lazy
private B b;

另外,重新设计代码架构也是一种有效的解决办法。我们需要审视代码结构,合理拆分或调整类之间的依赖关系,打破这种不合理的循环依赖。例如,可以将A类和B类中相互依赖的部分提取出来,形成一个独立的C类,让A类和B类共同依赖C类,从而避免循环依赖。

使用@Autowired注解的最佳实践与深度建议

优先采用构造函数注入

在使用@Autowired注解时,遵循一些最佳实践能有效减少问题的发生。优先使用构造函数注入就是一个重要的原则。构造函数注入可以确保依赖的 Bean 在对象创建时就被注入,避免出现NullPointerException 。从依赖的特性来看,通过构造函数注入,依赖具有不可变性,一旦对象创建成功,依赖就无法修改;同时,会自动检查注入的对象是否为空,只有不为空才会注入成功,保证了依赖不为空;而且由于获取到的是初始化之后的依赖对象,并且调用了要初始化组件的构造方法,最终能拿到完全初始化的对象。例如:

public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
}

在 Spring4.x 之后,官方也更推荐构造器注入方式。如果在使用构造函数注入时,发现参数过多导致代码臃肿,那么就需要审视这个类的设计是否合理,是否做到了单一职责。

灵活运用@Autowired(required = false)

对于非必需的依赖,可以使用@Autowired(required = false),这样即使对应的 Bean 不存在,也不会导致程序启动失败。比如,在一些扩展功能或者可插拔的模块中,某些依赖可能不是系统正常运行所必需的。使用@Autowired(required = false)可以让系统更加灵活,在这些依赖缺失的情况下也能正常启动,并且在需要时可以方便地添加相应的功能。例如:

@Autowired(required = false)
private Optional<SomeOptionalService> someOptionalService;

通过Optional类来包装非必需依赖,在使用时可以先判断是否存在,避免空指针异常。

警惕属性注入的局限性

属性注入虽然使用起来简洁,代码量少,但也存在明显的局限性。属性注入是基于 Java 的反射机制实现的,它可以对private成员进行注入。然而,这种方式使得类只能在 IOC 容器中使用,如果在容器外自己new一个对象,相关的依赖无法完成注入。而且,从代码的可维护性和可读性角度来看,大量的属性注入会使类的依赖关系不够清晰。例如:

public class SomeService {
    @Autowired
    private AnotherService anotherService;
    // 其他代码
}

相比之下,构造函数注入和 setter 方法注入能更清晰地展示类的依赖关系。在实际开发中,我们应该尽量避免过度使用属性注入,除非在一些简单场景或者有特殊需求的情况下。

理解不同注入方式的适用场景

Spring 中除了属性注入和构造函数注入,还有 setter 方法注入。setter 方法注入通过调用成员变量的 set 方法来注入依赖对象。它的好处是可以使类的属性在以后重新配置或重新注入,比较灵活。例如:

public class SomeClass {
    private SomeDependency someDependency;

    @Autowired
    public void setSomeDependency(SomeDependency someDependency) {
        this.someDependency = someDependency;
    }
}

在实际项目中,我们可以根据具体需求选择合适的注入方式。如果依赖是必需的,并且希望在对象创建时就完成注入,保证依赖的不可变和完整性,那么构造函数注入是较好的选择;如果依赖是可选的,或者需要在后续动态调整依赖,setter 方法注入更为合适;而属性注入则应谨慎使用,避免其带来的潜在问题。

互联网大厂的后端开发工作节奏快、要求高,每一个技术细节都可能影响到整个项目的成败。@Autowired注解虽然只是 Spring Boot 开发中的一个小工具,但掌握它的原理和正确使用方法,能让我们在开发过程中少走很多弯路。希望通过今天的深度分享,大家在面对@Autowired相关问题时,不再感到迷茫和无助。如果你在实际开发中还有其他关于@Autowired注解的问题或经验,欢迎在评论区留言分享,让我们一起交流进步,打造更高效、稳定的后端系统!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言