spring里Bean都有一个作用域,用注解@Scope表示,容器默认使用的是单例,即“singleton”,顾名思义就是指容器每次只为我们创建一次Bean,以后使用都是拿的同一个。虽然平时开发的时候基本都是使用单例的,但不免有时候会使用到另一种类型,即原型模式,这时候就会产生一个问题:当我们单例Bean注入了原型Bean,并且调用了原型Bean的某个方法,我们希望的是单例Bean只初始化一次,而原型Bean每次调用应该都是重新生成的,然而,结果确与我们想的并不一样,无论是单例还是原型都只会初始化一次。
@Configuration
@ComponentScan("com.whb")
public class AppConfig {
}
这个只是一个配置类,并且指定了容器扫描“com.whb”下面的类。
@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个类的哈希值,来验证是否一致。
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()方法,结果如下:
问题来了,IndexService是单例哈希值不变可以理解,那为什么设置了原型的IndexDao也不变呢?
其实仔细想一下,这问题很好理解。当我们第一次从容器中拿IndexService时,他会为我们创建一个实例,创建过程中发现IndexService还依赖着另一个属性,那么此时容器便会先去生成IndexDao的实例并且注入到IndexService中,然后创建出IndexService实例并且返回。此时容器中有着一个IndexService和一个IndexDao。当我们第二次去拿IndexService时,容器发现已经有了一个实例,并且IndexService是单例的所有它直接就返回了那个存在着的IndexService实例。虽然IndexDao设置了原型,但由于IndexService只有一次机会设置属性所以从到尾容器并没有生成第二个IndexDao实例。这也就解释了为什么哈希值每次都是一样的。
其实spring官方文档给出了2中解决方案,点击我查看文档说明
下面我直接贴出代码演示一遍:
我们来修改下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.
结果如下:
可以看到service还是每次都一样,符合单例设置,但是dao确每次都改变了,说明原型设置也生效了。
ApplicationContextAware是spring提供给我们的接口,IndexService是我们业务的类,我们直接实现可以说是增大了和spring框架的耦合程度。因此spring还提供了第二种方法:
我们继续修改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开发中可能会遇到各种各样的问题,但其实最好的解决办法就是查阅文档,毕竟这可是第一手资料!!