JAVA面试题分享四百一十四:明明定义了Bean,为什么就是依赖注入不了

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

public class DaduduRedisTemplate extends RedisTemplate {

    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,最终也可能进入本文所分析的场景中。

你可能感兴趣的:(JAVA,面试题分享,java,面试,开发语言)