@Autowired 注解针对父类注入时加载方式分析

本文深入探讨了SpringBoot环境下@Autowired注解的工作原理,特别是在处理类继承关系时的bean命名规则。通过实验证明,当父类与子类同时存在时,@Autowired会先尝试按类型匹配,若类型匹配不唯一,则根据变量名作为bean名称进行精确匹配。文章还对比了@Resource注解的使用方式,揭示了两者在依赖注入上的细微差别。

问题:

1.springBoot 类 BService 继承 类 AService ,在写CLassA单元测试的时候

如果写成

@Autowired
private AService service;
或者写成:

@Autowired
private AService wefsd;  //一个乱七八糟的字符组成

启动容器的时候会报错,显示:

NoUniqueBeanDefinitionException , X.X.X.XAService这个bean,发现了两个一个是 aService,一个是bService,

 

分析:

@Autowired 默认按照类型加载,如果存在多个类型相同的bean,类似这种父子关系,导致无法确认唯一结果的时候,会接着按照名称查找,我本来以为这个名称指的是bean的默认名字(目前案例中没有显示指定),但是这个默认名字和我自定的变量名有什么关系呢,我的理解是 默认bean名字是 类名首字母小写(aService),我恰好定义的变量名称(service,wefsd,)不相同,此时报错,推测原因是 容器平台把这个变量名称定义为bean名称,发现按照名称也找不到,所以报错,如果我写成下面的这个:

@Autowired
private AService aService;

就不会报错了,这个时候,bean名称默认就是变量名称。。。待定

 

//=============================第二次测试如下 ==========================================

父子类如下:

@Service
@Slf4j
public class ParentService {

    void parentPrint() {
        log.info("this is ParentService parentPrint function");
    }

}


@Service
@Slf4j
public class ChildService extends ParentService {

    void childPrint() {
        log.info("this is ChildService childPrint function");
    }

}

子类测试1 :private ChildService childService;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class ChildServiceTest1 {

    @Autowired
    private ChildService childService;

    @Test
    public void test(){
        childService.childPrint();
    }

}

结果显示正常

子类测试2:private ChildService sssss;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class ChildServiceTest2 {

    @Autowired
    private ChildService sssss;

    @Test
    public void test(){
        sssss.childPrint();
    }
}

结果显示正常

父类测试1 :private ParentService parentService;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class ParentServiceTest1 {

    @Autowired
    private ParentService parentService;

    @Test
    public void test(){
        parentService.parentPrint();
    }
}

结果显示正常

父类测试2 : private ParentService ssss;

ExtendWith(SpringExtension.class)
@SpringBootTest
@Slf4j
public class ParentServiceTest2 {

    @Autowired
    private ParentService ssss;

    @Test
    public void test(){
        ssss.parentPrint();
    }
}

结果异常



org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.demo.test1.service.ParentServiceTest2': Unsatisfied dependency expressed through field 'ssss'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.test1.service.ParentService' available: expected single matching bean but found 2: childService,parentService

	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
	

 

日志里面显示:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.demo.test1.service.ParentServiceTest2': Unsatisfied dependency expressed through field 'ssss'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.test1.service.ParentService' available: expected single matching bean but found 2: childService,parentService

 

首先,我们知道 @Autowired 默认按照类型装配,如果类型不能唯一确定那么就按照名称装配

根据 上面四个实验可以得出结论:

1. 子类注入的时候是按照类型选择的,否则 子类测试2实验应该就会失败,也就是说在注入子类的时候,根据类型就可以唯一确认,子类是特殊的父类,这个应该没问题;

2.父类是按照名称装配的,否则父类测试2 不应该报错,相对来说,父类较为宽泛,所以注入父类会发现有两个bean,那么这个时候就会开始按照名称查找,这个名称spring会把变量名作为bean名称来查询,这也就是父类测试2失败以及父类测试1成功的的原因了。

3.补充:@Resource 按照名称查找,这个名称就是变量名称。

 

 

 

 

 

 

 

 

Spring框架中,抽象类(abstract class)本身不能直接实例化,因此其依赖注入的实现方式与普通类有所不同。如果尝试在抽象类中使用 `@Autowired` 注解进行字段注入,可能会遇到注入失败、属性值为null的问题。 ### 依赖注入的限制 由于Spring容器只能对具体实例化的Bean执行依赖注入,而抽象类无法被实例化,因此直接在抽象类字段上使用 `@Autowired` 注解会导致注入失败。此外,若子类继承了该抽象类并尝试通过父类字段访问注入的依赖,实际获取到的对象可能为null[^2]。 ### 构造器注入作为解决方案 为了在抽象类中正确注入依赖,推荐使用构造器注入方式Spring支持通过构造器完成依赖注入,即使是在抽象类中也能正常工作。子类在调用父类构造器,需传入所需的Bean实例,确保依赖能够正确传递和初始化。 ```java public abstract class AbstractService { protected final Dependency dependency; public AbstractService(Dependency dependency) { this.dependency = dependency; } } @Service public class ConcreteService extends AbstractService { public ConcreteService(Dependency dependency) { super(dependency); } public void doSomething() { dependency.perform(); } } ``` 通过这种方式Spring会自动将匹配的Bean作为参数传递给子类构造器,并最终传递给抽象父类的字段[^2]。 ### 使用 `@PostConstruct` 初始化逻辑 在某些情况下,需要在抽象类中执行一些初始化操作。可以利用 `@PostConstruct` 注解来定义初始化方法,但需要注意此类方法应在构造器之后执行,并且所有依赖已经完成注入。此注解适用于非静态方法,并能确保在Bean完全初始化后执行特定逻辑。 ```java public abstract class AbstractService { protected Dependency dependency; public AbstractService(Dependency dependency) { this.dependency = dependency; } @PostConstruct private void init() { // 执行初始化逻辑,如配置加载等 } } ``` 上述代码中的 `init()` 方法将在依赖注入完成后执行,适合用于准备运行所需的资源[^2]。 ### 避免在构造器中使用注入的属性 虽然构造器注入是可行的,但在构造器内部直接使用注入的属性仍需谨慎。如果子类在调用 `super(...)` 之前执行某些依赖于这些属性的操作,可能导致未预期的行为。因此,在构造器中应尽量避免复杂的逻辑处理,仅用于赋值操作即可[^2]。 ### 单元测试中的注意事项 在编写单元测试,若希望Spring管理抽象类的子类实例,则必须启用完整的Spring上下文。可以通过添加 `@ExtendWith(SpringExtension.class)` 和 `@SpringBootTest` 注解来实现,确保测试环境中所有依赖都能被正确注入。 ```java @ExtendWith(SpringExtension.class) @SpringBootTest public class ConcreteServiceTest { @Autowired private ConcreteService concreteService; @Test public void testDoSomething() { concreteService.doSomething(); // 正常执行,依赖已注入 } } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值