正文共:2488 字 19 图
预计阅读时间:7 分钟
最近在网上看到一个面试题,说如果一个单例的 Bean 依赖了一个原型的 Bean,那这个原型的 Bean 到底是单例的还是原型的?
也许你知道答案,但是你知道为什么吗?接下来我会通过例子来演示结果,并通过源码来告诉你为什么?并且还有一个你可能不知道的小知识点。
废话少说,来吧,直接上图。整个项目结构如下图。
俗话说,没有对比就没有伤害,首先我们先看看,如果一个单例的 Bean 依赖一个单例的 Bean,结果是怎样的。
可以看到,文件结构视图中,我的 UserServiceImpl 类,依赖了 UserDao 接口,并自动注入了 userDaoImpl0,我在 SpringTest 类中,调用了两次 getUser 方法,让我们看看结果。
应该和你猜想的一样吧,UserDaoImpl0 的 hashCode,两次打印的结果是一模一样的,那就说明,单例 Bean 依赖单例 Bean,那么被依赖的 Bean 作用域还是单例的。
那么接下来,我们看看自动注入 userDaoImpl1 的文件结构视图。
同样是运行两次,我们来看看这次的结果是什么样的。
和你想的结果一样吗,UserDaoImpl1 的 hashCode,两次打印的结果也是一模一样的。如果你了解 Spring Bean 的生命周期你就会明白,为什么是这样的。
接下来看一幅图,奥妙全在这里。
注意我用红框圈着的部分,其实 Spring 在启动的时候,会实例化所有的 Bean,懒加载除外哦,实例化完毕的 Bean,会被 Spring 缓存起来,等到我们在调用 Spring 容器 API 获取(getBean) Bean 的时候,就会检查这个 Bean 是否已经实例化过,也就是在缓存中查找,如果找到,就直接返回。
我画了一幅 Spring 容器启动实例化 Bean 和调用 Spring 容器 API 获取 Bean 的流程图,你看看就明白了,第一次画图,多多包涵。
注意我用双向箭头连接的部分,可以看到整个流程是重合的,最后一步都是调用 DefaultSingletonBeanRegistry.getSingleton 方法,首先查看是否有缓存,如果有就直接返回,如果没有则继续实例化。
所以说,单例(懒加载除外) Bean,在 Spring 启动的时候就已经实例化完成,接下来只会从缓存中读取,而我们单例 Bean 中,不管是依赖了单例 Bean,还是原型 Bean,都是在 Spring 容器启动时实例化好了的,所以,我们看到两次打印 hashCode 的结果是一模一样的。
那如果我想依赖一个原型的 Bean,并且想让每次调用都返回新的实例应该怎么办呢?有以下两种方法,其实有三种,只不过最后一种不太好用。我们先看第一种。
我在 UserServiceImpl 中自动注入了 ApplicationContext,然后在 getUser 方法中,通过 getBean 方法获取 userDaoImpl1 的实例,这样每次拿到的都是一个新的实例。
我们来看看,运行结果。是不是 userDaoImpl1 的 hashCode 两次的结果是不一样的。其实还有一个变种,就是我们可以实现 ApplicationContextAware 接口,然后重写 setApplicationContext 方法,里面会有一个参数 ApplicationContext,通过这个参数,我们也可以通过调用 getBean 方法获取实例。
我们再来看看第二种方法。
我在 UserServiceImpl 类中增加了一个抽象方法,并增加了一个注解 @Lookup 同时把类改为抽象类,因为这个方法不用我们自己实现。Spring 会通过 cglib 代理帮我们把对象实例化,并返回给我们。而我只用在方法中调用 getUserDao 方法就可以了。其中 @Lookup 中的参数就是我们定义的原型 Bean 的 beanName。之所以指定 beanName 是因为我们的接口有多个实现类,如果不声明就会报错。
我们可以看到,最终打印的 hashCode 也是不一样的。那 Spring 是怎么帮我们做到的呢?
先看红框部分的判断,混个脸熟,大概意思是如果没有提供覆盖方法,通过构造函数实例化,如果提供了覆盖方法,则通过 cglib 代理实例化。
而我们需要关注的就是 AutowiredAnnotationBeanPostProcessor 这个类,来看一下它的实现。你会发现它会检查类里面是否有方法添加了 @Lookup 这个注解,如果有,则添加一个 LookupOverride 对象到列表中去,如果你还记得上面图中的判断。
(!if(bd.hasMethodOverrides()))
这里自然就不满足条件了,所以就走了 else,用 cglib 实例化对象。那我们就来看看,cglib 是怎么做的吧。