前言
今天我们看下Spring的Lookup注解,这个注解可以使Spring替换一个bean原有的,获取其它对象具体的方法,并自动返回在容器中的查找结果。
正文
在了解它之前,我们先来看下一个例子。
我们有一个Bean,TestClassB,它是多例的。大致如下:
1 |
|
现在一个单例Bean,TestClassA,使用到了TestClassB,代码大致如下:
1 |
|
我们进行下测试,可以发现一些问题。
1 | (SpringRunner.class) |
可以看到输出结果:
1 | This is TestClass A: com.zwt.demo.util.TestClassA@2c768ada |
对于TestClassA,它因为是单例,所以一直是一个实例,我们是可以理解的,但是对于TestClassB,我们明明设置了多例,但是我们发现它仍是一个实例,相当于单例。
对于这种情况的产生,很好理解,因为TestClassA为单例,因此TestClassB只有一次注入的机会,即在生成单例TestClassA的时候,因此导致了TestClassB的多例不体现,仍相当于个单例模式。
如果我们要求TestClassB必须为多例的,那么上面这种情况是会出现问题的。
如何解决这种问题呢?
其实我们最常用的一种方法是拿到SpringContext,然后手动获取Bean。代码大致如下:
1 |
|
同时TestClassA里的方法如下:
1 |
|
可以看到输出结果,TestClassB已经是多例的了。
1 | This is TestClass A: com.zwt.demo.util.TestClassA@7c447c76 |
当然也可以让TestClassA继承ApplicationContextAware直接拿到ApplicationContext,然后获取TestClassB。
这种方式在项目使用中还是比较多的,也是很方便的。
还有其他方法吗?
下面我们来说另一种方式,当然就是我们今天的主角,Lookup注解。那具体如何使用呢,我们来看下。
1 |
|
我们运行测试类,可以看到输出结果:
1 | This is TestClass A: com.zwt.demo.util.TestClassA$$EnhancerBySpringCGLIB$$a967ae38@5a00eb1e |
发现TestClassB是多例的,可是我们根据上面的代码,感觉TestClassB返回应该不是null么。
当然,这就要来了解下Lookup注解了,我们分析一下它的源码:
1 | (ElementType.METHOD) |
可以看到该注解作用于方法上,有一个参数value,这个值可以指定要look up的Bean的名字。如果不指定,就会默认方法返回的类型寻找Bean并进行Look up。
我们在Spring源码中寻找下该注解 @Lookup,会发现只有一个地方使用到了该注解。
在AutowiredAnnotationBeanPostProcessor类的determineCandidateConstructors方法里。该方法部分内容如下:
1 | public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) |
可以看到Spring会首先判断该Bean是否有Lookup注解的方法,现在缓存里看,缓存没有的话会尝试获取方法上的Lookup注解,如果存在,拿到需要重写(覆盖)的方法信息放入LookupOverride,最后为RootBeanDefinition添加LookupOverride的属性。这个方法最终会被AbstractAutowireCapableBeanFactory类中的createBeanInstance方法调用,去生成新的Bean并重写,实现改变Bean的效果。
因此原理大致为:方法执行返回的对象,使用 Spring 内原有的这类对象替换,通过改变方法返回值来动态改变方法。内部实现为使用 cglib 方法,重新生成子类,重写配置的方法和返回对象,达到动态改变的效果。因此Bean的多列特性也被体现了。
总结
通过解决一个单例Bean(无状态Bean)调用多例Bean(有状态Bean)的问题,我们了解了Lookup注解的一些简单用法,对Spring也有了一些深入的认识。