@lookup 注解用于让单例 bean 每次调用方法时获取全新原型 bean 实例,解决原型 bean 被注入单例后“变相单例”的问题;其通过 cglib 动态生成子类重写方法实现,要求方法为 public/protected、无参、返回原型类型,且类不可为 final。

Spring 使用 @Lookup 注解,是为了让单例 Bean 每次调用方法时都能拿到一个**全新的原型 Bean 实例**,而不是复用注入时缓存的那一个。它不走常规依赖注入流程,而是靠 Spring 在运行时动态生成子类并重写方法来实现。
为什么需要 @Lookup?
单例 Bean 在容器启动时只创建一次,它的字段(比如 @Autowired private PrototypeBean bean)也只注入一次。即使 PrototypeBean 声明了 @Scope("prototype"),被注入到单例中后,实际就“变相单例”了——多次使用都是同一个对象。@Lookup 就是为了解决这个“注入即固化”的问题。
@Lookup 方法的写法要求
被 @Lookup 标记的方法必须满足以下条件:
- 访问修饰符是 public 或 protected(不能是 private)
- 返回类型必须和你要获取的原型 Bean 类型一致(或其父类/接口)
- 不能有参数(签名必须是空括号)
- 可以是抽象方法(推荐),也可以是非抽象方法(但方法体会被 Spring 忽略,直接覆盖)
- 方法体里写不写
return null;都没关系,Spring 不会执行它
具体使用步骤
假设你有一个原型 Bean Task,想在单例 Processor 中每次调用都获得新实例:
- 给
Task加上@Scope("prototype")和组件注解 - 在
Processor中定义一个符合要求的 lookup 方法,并加上@Lookup - 在业务逻辑里调用该方法,而非直接持有字段
示例代码:
@Component
@Scope("prototype")
public class Task {
private final long id = System.nanoTime();
public void execute() { System.out.println("Task id: " + id); }
}
@Component
public class Processor {
@Lookup
protected Task createTask() {
return null; // 这行不会执行,仅占位
}
public void handle() {
Task t1 = createTask(); // 拿到新实例
Task t2 = createTask(); // 拿到另一个新实例
System.out.println(t1 == t2); // false
}
}
注意事项
该机制底层依赖 CGLIB 动态代理(对类代理),所以:
- 目标类不能是 final 的,否则无法生成子类
- 方法不能是 final 或 static
- 如果类本身已用 JDK 动态代理(如加了
@Transactional),可能与 CGLIB 冲突,需注意代理模式配置 - 不支持接口代理,只适用于类 Bean(因为要继承重写方法)











