关于@Autowired注入依赖失败的问题及解决

目录
  • @Autowired注入依赖失败的问题
    • 1、现象描述
    • 2、问题分析
    • 3、解决方案
  • @Autowired依赖注入为啥不推荐了
    • 警告内容
    • 依赖注入的方式
    • @Autowired是干啥的
    • @Inject是干啥的
    • @Resource是干啥的
    • spring建议

@Autowired注入依赖失败的问题

1、现象描述

在Spring Boot项目中使用@Autowired注解,程序启动时发现服务启动失败,提示:

Description:
 
Field metrics in com.be.fallback.servlet.FallbackServlet required a bean of type 'com.be.fallback.metrics.FallbackMetrics' that could not be found.
 
The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)
 
 
Action:
 
Consider defining a bean of type 'com.be.fallback.metrics.FallbackMetrics' in your configuration.

2、问题分析

这里的错误原因很好分析。结合报错信息及代码,报错处的代码为FallbackMetrics注解了@Autowired进行依赖注入,但是没有找到可以被用来注入的实例。即Spring Boot获取FallbackMetrics的实例失败。

3、解决方案

根据分析,需要检查可能导致Spring Boot依赖注入失败的因素。

1.检查扫描路径。

扫描路径是由@ComponentScan来指定的,默认为标注类当前包及当前包的子包。

也就是说,标注了@ComponentScan的启动类放在com.be.fallback包下面,只会扫描com.be.fallback包中的类,以及com.be.fallback.servlet、com.be.fallback.util等子包中的类,对于com.be.service等包中的类是不会扫描的。

  • 注意事项一:很多人没有使用@ComponentScan,但是使用了@SpringBootApplication。@SpringBootApplication是通过内部封装@ComponentScan注解来实现实例扫描的,所以使用@SpringBootApplication也是一样的。
  • 注意事项二:也可以通过为@ComponentScan或@SpringBootApplication注解指定参数来修改扫描路劲,示例:
// 如果使用@ComponentScan注解:
@ComponentScan(basePackages = "com.be.fallback")
 
// 如果使用@SpringBootApplication注解:
@SpringBootApplication(scanBasePackages = "com.be.fallback")

2.检查实例注册。

检查想要使用@Autowired注解自动注入依赖的类,是否标注了用来注册给Spring Boot的注解。这些注解包括@Component,@Service,@Repository,@Controller等。

3.其他问题。

如果上述步骤检查完成,服务启动又没有产生其他异常,这时候基本上已经排查代码的问题。这时候需要检查依赖、开发环境等是否有问题。检查依赖需要了解自己需要哪些依赖,看是否配置齐全;检查开发环境,可以通过将代码拷贝到其他机器上执行来判断。

@Autowired依赖注入为啥不推荐了

这几天更新升级了一下java编码神器IDEA,升级完进行日常开发,可能是以前用的IDEA版本比较老旧,升级之后发现之前的日常写法有了个warning提醒。来看图:

如上图,这就奇怪了,我们经常写的业务层就是service接口层和对应的实现类层进行属性注入的时候都是采用注解进行注入的。这也是springIOC给提供的比较方便的地方。我使用IDEA提供的自动修复提示修复了之后变成采用构造函数的形式进行注入了。

但是多年面向Spring开发的经验告诉我,使用@Autowired注解进行依赖注入,肯定是没有问题的。但是我的代码洁癖不允许我这么不明不白的留一个警告在这里。所以,带着我的洁癖,和我的好奇心,我开始研究起了这个警告。

警告内容

我们简单翻译一下自动提示的是啥意思:

不建议直接在字段上进行依赖注入。

Spring 开发团队建议:在Java Bean中永远使用构造方法进行依赖注入。对于必须的依赖,永远使用断言来确认。

我们说明上面的问题之前先回顾几个spring相关的问题:

依赖注入的方式

Spring 有三种依赖注入的方式

1.基于属性的注入

这种注入方式就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到field。这是我平常开发中看的最多也是最熟悉的一种方式。

@Autowired
PushTaskService pushTaskService;

2.基于setter方法的注入

通过对应变量的setXXX()方法以及在方法上面使用注解,来完成依赖注入。比如:

private static TaskGroupTemplateRepository taskGroupTemplateRepository;
private static TaskGroupService taskGroupService;

@Autowired
public void setTaskGroupTemplateRepository(TaskGroupTemplateRepository taskGroupTemplateRepository,TaskGroupService taskGroupService){
    ExcelListener2.taskGroupTemplateRepository = taskGroupTemplateRepository;
    ExcelListener2.taskGroupService = taskGroupService;
}

说明:在 Spring 4.5 及更高的版本中,setXXX 上面的 @Autowired 注解是可以不写的。

3.基于构造方法的注入

将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。比如:

@Autowired
public ExcelListener(@Qualifier("taskGroupService") TaskGroupService taskGroupService) {
    this.taskGroupService = taskGroupService;
}

@Autowired是干啥的

我们一般开发需要注入属性的时候都会使用的这个注解@Autowired,跟这个注解类似的还有2个,@Resource, @Inject。Spring 支持使用@Autowired, @Resource, @Inject 三个注解进行依赖注入。我们先看一下有啥区别:

@Autowired为Spring框架提供的注解,可以理解是Spring的亲儿子。这里先给出一个示例代码

public interface IndexService {

    void sayHello();
}

@Service
public class IndexServiceImpl implements IndexService {

    @Override
    public void sayHello() {
        System.out.println("hello, this is IndexServiceImpl");
    }

}

@Service
public class IndexServiceImpl2 implements IndexService {

    @Override
    public void sayHello() {
        System.out.println("hello, this is IndexServiceImpl2");
    }

}

测试方法

@SpringBootTest
public class Stest {

    @Autowired
    // @Qualifier("indexServiceImpl2")
    IndexService indexService;

    @Test
    void gooo() {
        Assertions.assertNotNull(indexService);
        indexService.sayHello();
    }
}

按照type在上下文中查找匹配,查找type为IndexService的bean

  • 如果有多个bean,则按照name进行匹配

如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配,查找name为indexServiceImpl2的bean

如果没有,则按照变量名进行匹配。查找name为indexService的bean

  • 匹配不到,则报错。(@Autowired(required=false),如果设置required为false(默认为true),则注入失败时不会抛出异常)

@Inject是干啥的

在Spring 的环境下,@Inject和@Autowired 是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor这个后置处理器来处理的。

这两个的区别,首先@Inject是Java EE包里的,在SE环境需要单独引入。另一个区别在于@Autowired可以设置required=false而@Inject并没有这个属性。也有的说@Inject是spring的干儿子。

@Resource是干啥的

@Resource是JSR-250定义的注解。Spring 在 CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource。

这个@Resource有2个属性name和type。在spring中name属性定义为bean的名字,type这是bean的类型。

如果属性上加@Resource注解那么他的注入流程是:

  • 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了name,则从上下文中查找名称匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
  • 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。

上面的内容都了解了之后我们接下来看为啥IDEA会有个warning提醒。IDEA 提示 Field injection is not recommended。warning提醒的注入方式就是第一种使用属性注解的方式进行注入。

属性注入优点

代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired扔到变量之上就好了方便开发人员进行代码的编写。

属性注入可能出现的问题

  • 问题1

基于 field 的注入,虽然不是绝对禁止使用,但是它可能会带来一些隐含的问题。来我们举个例子:

@Autowired
private Person person;

private String company;

public UserServiceImpl(){
    this.company = person.getCompany();
}

初看起来好像没有什么问题,Person 类会被作为一个依赖被注入到当前类中,同时这个类的 company 属性将在初始化时通过person.getCompany() 方法来获得值。我们尝试运行一下就会发现报错了。其实类似这个问题有人在stackoverflow上提问过,点我跳转

Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Constructor threw exception; nested exception is java.lang.NullPointerException

在执行this.company = person.getCompany();这段代码的时候报了空指针。

出现这个问题的原因是,Java 在初始化一个类时,是按照静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired 的顺序。所以在执行这个类的构造方法时,person 对象尚未被注入,它的值还是 null。

  • 问题2

使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,如果你一个类注入非常多的其它的对象,拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则。顺便我看了一下我们现在的业务代码这个问题在我们的项目代码中真的很常见。

  • 问题3

这种注入形式就会造成你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化。也就是类和依赖容器强耦合,不能在容器外使用。

spring建议

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.

其实大概的意思就是2句话

  • 强制依赖就用构造器方式
  • 可选、可变的依赖就用setter注入

spring对采用构造方法注入的说明

Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码结构问题,这个类可能承担了过多的责任。

spring对采用setter方法注入的说明

基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Spring依赖注入:@Autowired,@Resource和@Inject区别与实现原理

    一.spring依赖注入使用方式 @Autowired是spring框架提供的实现依赖注入的注解,主要支持在set方法,field,构造函数中完成bean注入,注入方式为通过类型查找bean,即byType的,如果存在多个同一类型的bean,则使用@Qualifier来指定注入哪个beanName的bean. 与JDK的@Resource的区别:@Resource是基于bean的名字,即beanName,来从spring的IOC容器查找bean注入的,而@Autowried是基于类型byType

  • 关于Spring的@Autowired依赖注入常见错误的总结

    做不到雨露均沾 经常会遇到,required a single bean, but 2 were found. 根据ID移除学生 DataService是个接口,其实现依赖Oracle: 现在期望把部分非核心业务从Oracle迁移到Cassandra,自然会先添加上一个新的DataService实现: @Repository @Slf4j public class CassandraDataService implements DataService{ @Override public void

  • 因Spring AOP导致@Autowired依赖注入失败的解决方法

    发现问题: 之前用springAOP做了个操作日志记录,这次在往其他类上使用的时候,service一直注入失败,找了网上好多内容,发现大家都有类似的情况出现,但是又和自己的情况不太符合.后来总结自己的情况发现:方法为private修饰的,在AOP适配的时候会导致service注入失败,并且同一个service在其他的public方法中就没有这种情况,十分诡异. 解决过程: 结合查阅的资料进行了分析:在org.springframework.aop.support.AopUtils中: publi

  • Spring使用@Autowired为抽象父类注入依赖代码实例

    这篇文章主要介绍了Spring使用@Autowired为抽象父类注入依赖代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 有时候为了管理或者避免不一致性,希望具体服务统一继承抽象父类,同时使用@Autowired为抽象父类注入依赖.搜了了网上,有些解决方法实现实在不敢恭维,靠子类去注入依赖,那还有什么意义,如下: 父类: public abstract class BaseCar { Settings settings; } @Compo

  • SpringBoot使用@Autowired为多实现的接口注入依赖

    目录 使用@Autowired为多实现的接口注入依赖 问题描述 方法一:使用@Qualifier限定 方法二:利用@Autowired可以byName匹配Bean的特性 方法三:使用@Primay 一个接口多个实现类的Spring注入 1. 首先, Interface1 接口有两个实现类 2. 通过 @Autowired 和 @Qualifier 配合注入 3. 使用@Resource注入,根据默认类名区分 4. 使用@Resource注入,根据@Service指定的名称区分 使用@Autowi

  • 关于@Autowired注入依赖失败的问题及解决

    目录 @Autowired注入依赖失败的问题 1.现象描述 2.问题分析 3.解决方案 @Autowired依赖注入为啥不推荐了 警告内容 依赖注入的方式 @Autowired是干啥的 @Inject是干啥的 @Resource是干啥的 spring建议 @Autowired注入依赖失败的问题 1.现象描述 在Spring Boot项目中使用@Autowired注解,程序启动时发现服务启动失败,提示: Description: Field metrics in com.be.fallback.s

  • @Autowired 自动注入接口失败的原因及解决

    目录 @Autowired自动注入接口失败 可以这样做 @Autowired自动注入失败报空指针异常 今天就遇到了这个问题 @Autowired 自动注入接口失败 有个自动注入不能注入的时候, 可以这样做 然后解决问题. @Repository // * @Repository 它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean. //* 同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型 @Autowired 自动注入失败报空指针异常 同一

  • 解决Callable的对象中,用@Autowired注入别的对象失败问题

    实现Callable的对象中,用@Autowired注入别的对象失败 场景是这样: 我需要在一个实现类A中写一个拿到返回值的多线程,于是用的Callable,在这个实现类A外我又写了一个专门实现Callable的实现类B,在B中用spring注解@Autowired注入另外一个实现类C,当代码运行时发现,C怎么也不能注入进B中,脑袋瓜疼. 目前的解决路线: 把@Autowired写到了A类中,并且把B对象直接放到A中,作为了内部类,这样发现用C好使了,但是还不清楚为什么单独在B中用@Autowi

  • 解决@Autowired注入static接口的问题

    目录 @Autowired注入static接口问题 @Autowired自动注入普通service很方便 但是如果注入static修饰的service则注入不了 后来网上百度了一下,看到了一个方法 标签解释 导致@Autowired注入失败的问题 背景 原因 @Autowired注入static接口问题 @Autowired自动注入普通service很方便 如: @Component public class WarningMatterUtil { //报警表 @Autowired privat

  • Spring处理@Async导致的循环依赖失败问题的方案详解

    目录 简介 问题复现 原因分析 解决方案 方案1:懒加载 方案2:不让@Async的类有循环依赖 方案3:allowRawInjectionDespiteWrapping设置为true 为什么@Transactional不会导致失败 简介 说明 本文介绍SpringBoot中的@Async导致循环依赖失败的原因及其解决方案. 概述 我们知道,Spring解决了循环依赖问题,但Spring的异步(@Async)会使得循环依赖失败.本文将用实例来介绍其原因和解决方案. 问题复现 启动类 启动类添加@

  • Intellij IDEA如何去掉@Autowired 注入警告的方法

    问题 在Service层注入Mybatis的Mapper我们通常会使用@Autowired 自动注入 @Autowired private ProductMapper productMapper; 但是这样Intellij IDEA会显示红色告警,提示不能自动注入. 当我们在Controller层注入Service时我们也经常直接在Filed上使用@Autowired 注解,这时候不显示红色警告,但是也显示Field injection is not recommended 的建议 原因 第一种

  • 在zuulFilter中注入bean失败的解决方案

    zuulFilter注入bean失败 一.为什么要用到这个 上周想实现在网关层 zuul 实现用户认证操作,即需要在网关过滤器中调用其他的微服务,按常规做法在 filter 中用 @Autowired 注解一个feign 接口,启动 一直失败,用度娘谷歌查了又查,只找到一些类似[在过滤器中注入bean]失败,但说的都是springMVC 并不是springcloud中的网关层 二.解决方法 查了很久,最终发现问题所在,其实在启动报错就提示很明显了,找不到相关实例,没错feign接口的实现类事实上

  • springboot使用@value注入配置失败的解决

    目录 springboot使用@value注入配置文件失败 问题解决方向一 问题解决方向二 @Value注入失败,注入值为null的问题 大概就是下面这样 结果不知道为什么,@Value注入一直为空?? 原因如下 解决办法 springboot使用@value注入配置文件失败 遇到的问题原因是:类中注入对象不能用static. 问题解决方向一 1.改为如图示,去掉static 问题解决方向二 1.仍然定义静态变量,但在其set方法上使用@Value进行赋值 2.仍然定义静态变量,同时定义一个普通

随机推荐