spring中单例Bean引用原型Bean产生的问题及解决办法

问题描述

spring里Bean都有一个作用域,用注解@Scope表示,容器默认使用的是单例,即“singleton”,顾名思义就是指容器每次只为我们创建一次Bean,以后使用都是拿的同一个。虽然平时开发的时候基本都是使用单例的,但不免有时候会使用到另一种类型,即原型模式,这时候就会产生一个问题:当我们单例Bean注入了原型Bean,并且调用了原型Bean的某个方法,我们希望的是单例Bean只初始化一次,而原型Bean每次调用应该都是重新生成的,然而,结果确与我们想的并不一样,无论是单例还是原型都只会初始化一次。

为了更直观的发现问题,下面我们用代码演示一遍。

首先新建一个SpringConfig配置类:
@Configuration
@ComponentScan("com.whb")
public class AppConfig {
}

这个只是一个配置类,并且指定了容器扫描“com.whb”下面的类。

接着我们在com.whb下新建2个类:
@Component("dao")
@Scope("prototype")
public class IndexDao {
}

@Component
public  class IndexService  {
    @Autowired
    IndexDao indexDao;
    public void test() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+indexDao.hashCode());
    }
}

IndexDao 指定原型模式,而IndexService则是默认的单例模式,我们往IndexSerevice里注入IndexDao,并且在test方法里打印出2个类的哈希值,来验证是否一致。

新建一个Test来测试
public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext an = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexService indexService = an.getBean(IndexService.class);
        indexService.test();
        IndexService indexService1 = an.getBean(IndexService.class);
        indexService1.test();
        IndexService indexService2 = an.getBean(IndexService.class);
        indexService2.test();
    }
}

这里我们直接从容器中取3次IndexService ,同时调用3次test()方法,结果如下:
spring中单例Bean引用原型Bean产生的问题及解决办法_第1张图片
问题来了,IndexService是单例哈希值不变可以理解,那为什么设置了原型的IndexDao也不变呢?

问题分析

其实仔细想一下,这问题很好理解。当我们第一次从容器中拿IndexService时,他会为我们创建一个实例,创建过程中发现IndexService还依赖着另一个属性,那么此时容器便会先去生成IndexDao的实例并且注入到IndexService中,然后创建出IndexService实例并且返回。此时容器中有着一个IndexService和一个IndexDao。当我们第二次去拿IndexService时,容器发现已经有了一个实例,并且IndexService是单例的所有它直接就返回了那个存在着的IndexService实例。虽然IndexDao设置了原型,但由于IndexService只有一次机会设置属性所以从到尾容器并没有生成第二个IndexDao实例。这也就解释了为什么哈希值每次都是一样的。

真实情况下我们肯定不希望是这个结果,不然的话我们设置原型还有个毛线作用。

其实spring官方文档给出了2中解决方案,点击我查看文档说明

下面我直接贴出代码演示一遍:

方法一:实现ApplicationContextAware

我们来修改下IndexService的内容:

public  class IndexService implements ApplicationContextAware{
    private ApplicationContext applicationContext;
    public void print() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+applicationContext.getBean("dao").hashCode());
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

我们通过实现ApplicationContextAware这个接口并重写setApplicationContext()这个方法可以拿到ApplicationContext对象,从而用这个对象获取容器中的Bean.
结果如下:
spring中单例Bean引用原型Bean产生的问题及解决办法_第2张图片
可以看到service还是每次都一样,符合单例设置,但是dao确每次都改变了,说明原型设置也生效了。

上面虽然解决了这个问题,但是这种方法还是有一定的弊端:

ApplicationContextAware是spring提供给我们的接口,IndexService是我们业务的类,我们直接实现可以说是增大了和spring框架的耦合程度。因此spring还提供了第二种方法:

方法二:使用@LookUp注解

我们继续修改IndexService的内容:

@Component
public  abstract class IndexService {
    public void print() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+getIndexDao().hashCode());
    }
    @Lookup("dao")
    public abstract IndexDao getIndexDao();
}

可以看到使用这种方式代码简洁了很多,我们只需要声明一个抽象方法,并在该方法上面添加@Lookup(“dao”)注解,“dao”表示要注入的类名,spring容器就会自动帮我们注入IndexDao实例。
结果如下:
spring中单例Bean引用原型Bean产生的问题及解决办法_第3张图片

总结

在spring开发中可能会遇到各种各样的问题,但其实最好的解决办法就是查阅文档,毕竟这可是第一手资料!!

你可能感兴趣的:(spring)