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在运行时会这样实现该方法:
- 调用复杂构造函数(带name参数)
- 自动注入其他Spring bean(如Grader)
- 相当于执行:
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仓库中找到。