Spring中@Lookup注解的作用

405 阅读2分钟

背景知识

  • 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.png

第二次获取TestBean.png

可以看到两次获取的TestBean不是一个对象,第一次是TestBean@4240,第二次是TestBean@4263

原因分析

  • 当使用了@Lookup注释方法,Spring会通过CGLIB产生动态代理类,通过LookupOverrideMethodInterceptor#intercept()增强AbstractBean#getOneArgument()的逻辑,最终通过BeanFactory#getBean()获取TestBean对象,因为TestBean的BeanDefinition的scope=prototype,所以每次都能获取到新的TestBean对象

参考