背景知识
- Spring的作用范围在
BeanFactory#getBean(java.lang.String, java.lang.Object...)被调用时,才会生效。如果是singleton,产生的Bean会存入缓存,下次取直接从缓存中取;如果是prototype,则每次都重新产生一个Bean - 通过
@Autowired的方式,相当于调用了一次BeanFactory#getBean()获取Bean。所以即使是单例(scope=singleton)Bean,注入scope=prototype的Bean,因为Spring仅调用一次BeanFactory#getBean(),在该单例Bean中的prototype不会随调用次数发生改变。 - 那么问题来了,如何实现每次访问这个单例Bean,都能访问到一个新的
scope=prototype的Bean?这时就需要用到@Lookup注解
实现示例
这里以spring-framework-5.3.10的源码的单侧作为示例:
实体类
public static abstract class AbstractBean {
//...省略...
@Lookup
public abstract TestBean getOneArgument(String name);
//...省略...
}
- TestBean:可以理解为一个POJO类,仅用于填充数据
测试类
public class LookupAnnotationTests {
private DefaultListableBeanFactory beanFactory;
// 在单测执行前,注册BeanDefinition到BeanFactory
@BeforeEach
public void setup() {
beanFactory = new DefaultListableBeanFactory();
AutowiredAnnotationBeanPostProcessor aabpp = new AutowiredAnnotationBeanPostProcessor();
aabpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(aabpp);
// 注册AbstractBean的BeanDefinition
beanFactory.registerBeanDefinition("abstractBean", new RootBeanDefinition(AbstractBean.class));
// 注册TestBean的BeanDefinition
RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class);
// TestBean的作用域是prototype
tbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
beanFactory.registerBeanDefinition("testBean", tbd);
}
@Test
public void testWithOneConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull();
// <x>获取TestBean,这里AbstractBean#getOneArgument()已经被CGLIB动态代理增强,实际上会走`AbstractBeanFactory#getBean()`的逻辑,获取TestBean
TestBean expected = bean.getOneArgument("haha");
assertThat(expected.getClass()).isEqualTo(TestBean.class);
assertThat(expected.getName()).isEqualTo("haha");
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
}
}
- #setup处:注册AbstractBean的BeanDefinition,默认scope=singleton;注册TestBean的BeanDefinition,
scope=prototype <x>处,我们多次调用AbstractBean#getOneArgument()将获取到多个对象
可以看到两次获取的TestBean不是一个对象,第一次是TestBean@4240,第二次是TestBean@4263。
原因分析
- 当使用了
@Lookup注释方法,Spring会通过CGLIB产生动态代理类,通过LookupOverrideMethodInterceptor#intercept()增强AbstractBean#getOneArgument()的逻辑,最终通过BeanFactory#getBean()获取TestBean对象,因为TestBean的BeanDefinition的scope=prototype,所以每次都能获取到新的TestBean对象
参考
- spring-framework-5.3.10源码
- www.cnblogs.com/wl20200316/…