likes
comments
collection
share

明明定义了Bean,为什么就是依赖注入不了,奇了个大怪!

作者站长头像
站长
· 阅读数 4

情况是这样的,我最近在扩展RedisTemplate的实现,大概是这样的:

public class DaduduRedisTemplate extends RedisTemplate<String, String> {

    private final String prefix = "dadudu";

    public DaduduRedisTemplate(RedisConnectionFactory connectionFactory) {
        setConnectionFactory(connectionFactory);
    }

    @Override
    public void convertAndSend(String channel, Object message) {
        message = prefix + message;
        super.convertAndSend(channel, message);
    }
}

DaduduRedisTemplate继承了RedisTemplate,并重写了convertAndSend()方法,逻辑比较简单,统一给消息加个前缀,实际工作中更复杂一点,DaduduRedisTemplate要生效就需要把它定义为Bean,所以有了如下Bean定义代码:

@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
	return new DaduduRedisTemplate(redisConnectionFactory);
}

然后在Service中注入它:

@Autowired
private DaduduRedisTemplate redisTemplate;

然后启动SpringBoot,竟然报错了:

a bean of type 'com.dadudu.DaduduRedisTemplate' that could not be found.

说找不到DaduduRedisTemplate这个类型的Bean,可是我明明定义了呀...,为什么会这样?关键是我如果把属性类型改为RedisTemplate就不报错了,也就是这样:

@Autowired
private RedisTemplate redisTemplate;

而且我debug了确实找到就是DaduduRedisTemplate对象,那为什么上面那么写就找不到Bean呢?情况就是这么个情况,不知道各位大佬想到原因了没,暂时没想到的,那就听我来给大家分析分析。

首先,抛一个问题给大家:Spring在根据属性进行依赖注入时,所需的Bean对象是否已经存在了?

答案是不一定,得看Bean的创建顺序,比如顺序是A—>B,A里面依赖了B,就算A和B都是非懒加载的单例Bean,Spring也会按顺序进行创建,那么在创建A时就会进行依赖注入,而这个时候B对象是不存在的,所以A在进行依赖注入时需要判断:Spring容器中有没有B类型的Bean对象,如果没有则判断有没有B类型的Bean定义,如果有Bean定义,那就此时此刻根据Bean定义把B对象创建出来,如果没有,则报上述根据类型找不到Bean的错误

回到我们的场景,其实类似,原因就是在创建Service的Bean对象时会针对属性进行依赖注入,会根据DaduduRedisTemplate类型去Spring容器找Bean对象,不过这时Spring容器中是没有DaduduRedisTemplate类型的Bean对象的,所以Spring会去找DaduduRedisTemplate类型的Bean定义,那为什么找不到DaduduRedisTemplate类型的Bean定义呢?我们再来看看我们定义Bean的方式:

@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
	return new DaduduRedisTemplate(redisConnectionFactory);
}

这个Bean的类型到底是RedisTemplate,还是DaduduRedisTemplate呢?不一样吗?它两不是父子关系吗?

来来来,重点来了,当Spring解析@Bean注解时,也就是在生成Bean定义时,会把方法的返回值类型RedisTemplate当做Bean类型,而不会把方法中真正返回的对象类型当做Bean类型,因为在解析@Bean注解时并不会真正执行该方法,所以这个Bean的类型一开始只能是RedisTemplate,只有真正执行了该方法之后才知道它具体的类型是DaduduRedisTemplate。

所以Service中在进行依赖注入时,只能找到RedisTemplate类型的Bean,而找不到DaduduRedisTemplate类型的Bean,除非!在进行本次依赖注入之前,DaduduRedisTemplate这个Bean对象已经被创建出来了,这样在进行依赖注入的时候,就能根据RedisTemplate类型找到DaduduRedisTemplate这个Bean对象了(根据父类找到子类对象)。

所以,我们再来看一下依赖注入的代码:

@Autowired
private DaduduRedisTemplate redisTemplate;

如果属性类型是DaduduRedisTemplate,那么就有可能根据DaduduRedisTemplate类型即找不到Bean对象,也找不到Bean定义,从而报错。

而如果改成:

@Autowired
private RedisTemplate redisTemplate;

就可以了,因为就算根据RedisTemplate类型找不到Bean对象,也能根据RedisTemplate类型找到Bean定义,最终也能根据Bean定义创建出来DaduduRedisTemplate对象完成依赖注入。

当然,最好的方式是改Bean的定义:

@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
	return new DaduduRedisTemplate(redisConnectionFactory);
}

改为:

@Bean
public DaduduRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
	return new DaduduRedisTemplate(redisConnectionFactory);
}

这样不管是根据DaduduRedisTemplate类型找Bean定义,还是根据RedisTemplate类型找Bean定义,都能找到。

也许有读者会想到,改成@Resource行不行,只能说可能行,也可能不行,因为@Resource会先根据名字找Bean,找到了自然没问题,但是如果找不到仍然会再根据类型找Bean,最终也可能进入本文所分析的场景中。

好啦,如果你学到了,希望能给我一个赞,如果有不理解的,欢迎在评论区留言讨论!

我是大都督周瑜,欢迎关注我的公众号:Hoeller。这是我的个人号,非机构号,已从机构离职,重回一线了。

现在网络上有很多的文章和视频,但是其中大部分都是一些八股文和理论知识,由不同的人翻来覆去,重复写、重复发,这种做法对作者本身是有用的,但是对于大部分读者来说可能是没有意义的,对于读者来说,不管是面试还是实际工作,我相信实战经验才是真正有价值的,所以我现在的分享都来源于我的实际工作,再结合我多年讲课的经验,希望做到把实战经验、理论知识、授课技巧融合起来,让读者能轻轻松松的从我的文章或视频中学到真正有价值的知识。

我希望成为:自媒体圈中技术最好、实战经验最丰富的达人,技术圈中最会分享的架构师。加油!