关于Spring IoC的那些事( 四 )

常见问题单例和多例Spring默认Bean是单例的 。因为绝大多数Bean其实是“无状态的”,比如Controller、Service、Repository 。所以多个线程去使用同一个Bean不会造成什么问题 。本着节约成本的理念,使用单例Bean比较好 。
但是有时候我们可能会需要一个“有状态”的类,它内部又依赖其它Bean 。比如一个Context或者一个Processor之类的 。对于这种有状态有依赖其它Bean的类,有两种设计思路:

  • 不给Spring管理,如果要用到其它Bean,使用上面的applicationContext来直接获取Bean 。
  • 给Spring管理,做成多例Bean,每次都新建一个
第二种使用起来会更优雅一些,也比较好测试一点 。这里有一个小问题,我们来考虑以下这种情况:如果我们使用了一个多例Bean,它可能会依赖一些单例Bean,这个很好解决,在多例Bean中正常地注入单例Bean就行了 。但是,如果我们要在一个单例Bean中使用一个多例Bean,我们知道无论是构造器注入,还是方法注入,还是属性注入,都只会在Bean初始化的时候注入一次,那怎么能保证多个线程得到的是不同的多例Bean呢?
所以要在单例Bean中使用多例Bean,不能使用一般的自动注入 。Spring提供了@Lookup注解来帮我们做这个事 。它是方法级别的注解 。
// 定义一个多例Bean@Component@Scope("prototype")public class PrototypeBean {public void say() {System.out.println("say something...");}}@Componentpublic class SingletonBean {public void print() {// 单例Bean中用多例BeanPrototypeBean bean = methodInject();System.out.println("Bean SingletonBean's HashCode " + bean.hashCode());}@Lookuppublic PrototypeBean methodInject() {return null;}}复制代码需要注意的是,用@Lookup修饰的方法,不能是private的 。可以是包访问权限、protected或public的 。这里推荐写成public的,这样在单元测试的时候比较方便mock 。
循环依赖循环依赖其实很好理解,就是A依赖B,而B又依赖A 。这样就形成了循环依赖 。那Spring是如何解决循环依赖的呢?
聪明你的肯定能够马上想到,如果两个Bean都是使用构造器注入,那是不能解决循环依赖的,一旦有循环依赖只能报错 。而如果是属性注入或者方法注入,那可以先初始化两个Bean,然后分别延迟注入进去 。这样就可以解决循环依赖的问题 。
这也是为什么我们推荐使用构造器注入 。循环依赖不是一个好设计,构造器注入可以提早发现这种循环依赖 。
Spring使用了一个叫做三级缓存的东西来解决循环依赖,具体的实现细节本文不做讨论,感兴趣的读者可以自己去找找相关的文章 。
给不给Spring管理?又回到上面那个单例和多例的问题 。如果一个类是多例的,那它一般是有状态的,我们有必要把它交给Spring管理吗?或者说,有必要交给IoC容器管理吗?
在回答这个问题之前,我们先假设一下,如果不给IoC容器管理,会怎样?我们从三个角度来考虑:
  • 这个类依不依赖其它Bean
  • 这个类是不是单例的
如果这个类不依赖其它Bean,那其实不太需要交给IoC容器管理,POJO类就是一个很典型的例子 。但如果这个类是一个单例的,那其实推荐交给IoC容器管理,因为要自己保证单例是比较麻烦的,而且不优雅 。不信去看看单例模式的各种实现 。
如果这个类依赖其它Bean,那推荐交给IoC容器管理,不然还得使用上面的那种applicationContext的getBean方法来获取依赖的Bean,这就与IoC框架耦合了,不太划算 。
关于作者我是Yasin,一个有颜有料又有趣的程序员 。
微信公众号:编了个程
个人网站:yasinshaw.com




推荐阅读