1. 简介

本篇快速教程将深入探讨Spring框架中支持方法级依赖注入的@Lookup注解。对于需要动态获取bean实例的场景,这个小工具能派上大用场。

2. 为什么需要@Lookup?

被@Lookup注解的方法告诉Spring:调用该方法时,返回其返回类型的新实例。

本质上,Spring会重写这个注解方法,将方法的返回类型和参数作为BeanFactory.getBean()的参数来调用。

@Lookup主要适用于两种场景:

  • 将原型作用域bean注入单例bean(类似Provider的功能)
  • 程序化注入依赖(比Provider更灵活)

✅ 注意:@Lookup等同于XML配置中的<lookup-method>元素

3. 使用@Lookup

3.1. 将原型Bean注入单例Bean

当项目中存在原型bean时,单例bean如何访问它们就成了个常见问题。Provider是一种方案,但@Lookup在某些场景下更简单粗暴。

先创建一个原型bean:

@Component
@Scope("prototype")
public class SchoolNotification {
    // ... 原型作用域的状态
}

再创建使用@Lookup的单例bean:

@Component
public class StudentServices {

    // ... 成员变量等

    @Lookup
    public SchoolNotification getNotification() {
        return null; // Spring会重写这个方法
    }

    // ... getter和setter
}

通过@Lookup获取原型bean实例:

@Test
public void whenLookupMethodCalled_thenNewInstanceReturned() {
    // ... 初始化上下文
    StudentServices first = this.context.getBean(StudentServices.class);
    StudentServices second = this.context.getBean(StudentServices.class);
       
    assertEquals(first, second); // 单例bean相同
    assertNotEquals(first.getNotification(), second.getNotification()); // 原型bean不同
}

⚠️ 注意:getNotification()方法体可以留空,因为Spring会将其重写为beanFactory.getBean(SchoolNotification.class)

3.2. 程序化注入依赖

@Lookup更强大的地方在于支持程序化注入,这是Provider做不到的。我们给SchoolNotification增加状态:

@Component
@Scope("prototype")
public class SchoolNotification {
    @Autowired Grader grader;

    private String name;
    private Collection<Integer> marks;

    public SchoolNotification(String name) {
        // ... 设置字段
    }

    // ... getter和setter

    public String addMark(Integer mark) {
        this.marks.add(mark);
        return this.grader.grade(this.marks);
    }
}

现在这个bean既依赖Spring上下文,又需要我们程序化传入参数。在StudentServices中添加处理方法:

public abstract class StudentServices {
 
    private Map<String, SchoolNotification> notes = new HashMap<>();
 
    @Lookup
    protected abstract SchoolNotification getNotification(String name);

    public String appendMark(String name, Integer mark) {
        SchoolNotification notification
          = notes.computeIfAbsent(name, exists -> getNotification(name));
        return notification.addMark(mark);
    }
}

Spring在运行时会这样实现该方法:

  1. 调用复杂构造函数(带name参数)
  2. 自动注入其他Spring bean(如Grader)
  3. 相当于执行:beanFactory.getBean(SchoolNotification.class, name)

✅ 优雅技巧:可以将@Lookup方法声明为抽象方法(如上例),但前提是:

  • 外围类不能@ComponentScan扫描
  • 外围类不能通过@Bean方式管理

测试用例:

@Test
public void whenAbstractGetterMethodInjects_thenNewInstanceReturned() {
    // ... 初始化上下文

    StudentServices services = context.getBean(StudentServices.class);    
    assertEquals("PASS", services.appendMark("Alex", 89));
    assertEquals("FAIL", services.appendMark("Bethany", 78));
    assertEquals("PASS", services.appendMark("Claire", 96));
}

4. 局限性

尽管@Lookup很灵活,但存在几个明显限制:

  • ❌ 当外围类被@ComponentScan扫描时,@Lookup方法必须是具体方法(不能是抽象方法)
  • ❌ 当外围类通过@Bean管理时,@Lookup完全失效

遇到这些情况,改用Provider是更稳妥的选择。

5. 总结

本文深入讲解了Spring的@Lookup注解:

  • 如何将原型bean注入单例bean
  • 如何实现程序化依赖注入
  • 适用场景和限制条件

所有示例代码可在GitHub仓库中找到。


原始标题:@Lookup Annotation in Spring