一、开篇故事:单例老板与临时工的困境 👔
想象这样一个场景:
你是一家公司的老板(Singleton Bean),负责分配任务。公司有很多临时工(Prototype Bean),每个任务都需要一个新的临时工来完成。
问题来了:
@Component
@Scope("singleton") // 你是单例老板
public class Boss {
@Autowired
private Worker worker; // 注入临时工
public void assignTask() {
worker.doWork(); // 每次都是同一个临时工!😱
}
}
@Component
@Scope("prototype") // 临时工应该是多例的
public class Worker {
public void doWork() {
System.out.println("工人ID: " + this.hashCode());
}
}
期望: 每次assignTask()都用新的临时工
现实: 因为Boss是单例,注入的Worker也是同一个!
怎么办? 🤔
二、问题分析:单例依赖多例的悖论 💔
2.1 核心矛盾
单例Bean(Singleton)
├─ 只创建一次
├─ 依赖注入也只执行一次
└─ 注入的Prototype Bean被"固定"了!
多例Bean(Prototype)
├─ 每次获取都应该创建新实例
└─ 但被单例Bean持有,变成了"伪单例"
2.2 图解问题
时刻1: Boss创建
Boss (单例)
└─ worker: Worker@123 (第一次注入)
时刻2: assignTask()
Boss (单例)
└─ worker: Worker@123 (还是同一个!)
时刻3: assignTask()
Boss (单例)
└─ worker: Worker@123 (还是同一个!!)
❌ 期望每次都是新的Worker,但一直是Worker@123!
2.3 生活类比
场景: 你雇了一个管家(单例),管家需要每天找不同的快递员(多例)送货。
问题:
- 管家被雇佣时(注入依赖),找了一个快递员A
- 以后每次送货,管家都让快递员A去送
- 其他快递员B、C、D都失业了!😢
期望:
- 每次送货都换一个新快递员
三、传统解决方案(笨拙版)😅
方案1:注入ApplicationContext
@Component
@Scope("singleton")
public class Boss {
@Autowired
private ApplicationContext context;
public void assignTask() {
// 每次手动从容器获取新实例
Worker worker = context.getBean(Worker.class);
worker.doWork();
}
}
缺点:
- ❌ 侵入性强,代码耦合Spring容器
- ❌ 不优雅,违反依赖注入原则
- ❌ 测试困难
方案2:注入ObjectProvider
@Component
@Scope("singleton")
public class Boss {
@Autowired
private ObjectProvider<Worker> workerProvider;
public void assignTask() {
// 每次从Provider获取新实例
Worker worker = workerProvider.getObject();
worker.doWork();
}
}
优点:
- ✅ 比ApplicationContext优雅一些
- ✅ 解耦容器
缺点:
- ❌ 还是需要手动调用getObject()
四、@Lookup闪亮登场!⭐
4.1 什么是@Lookup?
@Lookup 是Spring提供的方法注入注解,可以让Spring动态重写你的方法,每次调用都从容器获取新的Bean实例。
4.2 基本用法
@Component
@Scope("singleton")
public class Boss {
// 抽象方法或空方法,Spring会自动实现
@Lookup
public Worker getWorker() {
return null; // 这里返回什么都无所谓,Spring会重写
}
public void assignTask() {
Worker worker = getWorker(); // 每次都是新实例!
worker.doWork();
}
}
@Component
@Scope("prototype")
public class Worker {
public void doWork() {
System.out.println("工人ID: " + this.hashCode());
}
}
输出:
工人ID: 123456
工人ID: 789012 ← 不同的实例!
工人ID: 345678 ← 每次都是新的!
4.3 生活类比
@Lookup就像给管家装了个"自动找人App":
管家:"需要快递员。"
App(Spring):"好的,给你找一个新的!"
→ 第1次:分配快递员A
→ 第2次:分配快递员B(新的)
→ 第3次:分配快递员C(新的)
五、@Lookup的实现原理 🔍
5.1 CGLIB动态代理
核心技术: Spring使用CGLIB生成Boss的子类,重写@Lookup方法。
原始类:Boss
└─ getWorker() { return null; }
CGLIB生成子类:Boss$$EnhancerBySpringCGLIB$$12345678
└─ getWorker() {
return beanFactory.getBean(Worker.class); // Spring实现的!
}
5.2 字节码生成
// 原始代码
public abstract class Boss {
@Lookup
public abstract Worker getWorker();
}
// CGLIB生成的子类(伪代码)
public class Boss$$CGLIB extends Boss {
private BeanFactory beanFactory; // Spring注入
@Override
public Worker getWorker() {
// 每次调用都从容器获取新实例
return (Worker) this.beanFactory.getBean(Worker.class);
}
}
5.3 图解过程
1. Spring扫描到@Lookup
↓
2. 使用CGLIB创建Boss的子类
↓
3. 重写getWorker()方法
↓
4. 方法内部调用beanFactory.getBean()
↓
5. 每次调用getWorker()都返回新的Worker实例
5.4 源码分析
// CglibSubclassingInstantiationStrategy.java
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
// 检查是否有@Lookup方法
if (bd.hasMethodOverrides()) {
// 使用CGLIB创建子类
return instantiateWithMethodInjection(bd, beanName, owner);
} else {
// 普通Bean,直接实例化
return super.instantiate(bd, beanName, owner);
}
}
// LookupOverride.java
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) {
// 拦截@Lookup方法
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
// 从容器获取Bean
Object bean = this.owner.getBean(lo.getBeanName());
return bean; // 返回新实例
}
六、@Lookup的多种用法 🛠️
6.1 抽象方法(推荐)
@Component
public abstract class CommandManager {
@Lookup
public abstract Command createCommand(); // 抽象方法
public void process() {
Command command = createCommand();
command.execute();
}
}
@Component
@Scope("prototype")
public class Command {
public void execute() {
System.out.println("执行命令:" + this.hashCode());
}
}
优点:
- ✅ 语义清晰,一看就知道是动态方法
- ✅ 强制子类实现(虽然Spring会代理)
6.2 具体方法(也可以)
@Component
public class CommandManager {
@Lookup
public Command createCommand() {
return null; // Spring会忽略这个返回值
}
public void process() {
Command command = createCommand();
command.execute();
}
}
6.3 指定Bean名称
@Component
public abstract class NotificationService {
@Lookup("emailSender") // 指定Bean名称
public abstract MessageSender getSender();
public void sendNotification(String message) {
MessageSender sender = getSender();
sender.send(message);
}
}
@Component("emailSender")
@Scope("prototype")
public class EmailSender implements MessageSender {
public void send(String message) {
System.out.println("发送邮件:" + message);
}
}
6.4 带参数的方法
@Component
public abstract class TaskExecutor {
@Lookup
public abstract Task createTask(String taskName); // 带参数
public void execute(String taskName) {
Task task = createTask(taskName);
task.run();
}
}
@Component
@Scope("prototype")
public class Task {
private String name;
// Spring会自动处理参数
public Task(String name) {
this.name = name;
}
public void run() {
System.out.println("执行任务:" + name);
}
}
七、实战案例:订单处理系统 📦
需求
每个订单需要一个独立的处理器(Processor),处理器包含订单的上下文信息,不能共享。
实现
// 订单服务(单例)
@Service
public abstract class OrderService {
@Lookup
protected abstract OrderProcessor createProcessor();
public void processOrder(Order order) {
// 每个订单都用新的处理器
OrderProcessor processor = createProcessor();
processor.setOrder(order);
processor.process();
}
}
// 订单处理器(多例)
@Component
@Scope("prototype")
public class OrderProcessor {
private Order order;
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
public void setOrder(Order order) {
this.order = order;
}
public void process() {
System.out.println("处理订单:" + order.getId() +
",处理器ID:" + this.hashCode());
// 1. 扣减库存
inventoryService.deduct(order);
// 2. 处理支付
paymentService.pay(order);
// 3. 更新订单状态
order.setStatus("PAID");
}
}
// Controller
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public String createOrder(@RequestBody Order order) {
orderService.processOrder(order);
return "订单处理成功";
}
}
测试
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testMultipleProcessors() {
Order order1 = new Order(1L);
Order order2 = new Order(2L);
Order order3 = new Order(3L);
orderService.processOrder(order1);
orderService.processOrder(order2);
orderService.processOrder(order3);
}
}
输出:
处理订单:1,处理器ID:123456
处理订单:2,处理器ID:789012 ← 不同的处理器
处理订单:3,处理器ID:345678 ← 每个订单都是新处理器
八、@Lookup vs 其他方案对比 📊
| 方案 | 优雅度 | 侵入性 | 性能 | 推荐度 |
|---|---|---|---|---|
| @Lookup | ⭐⭐⭐⭐⭐ | 低 | 高 | ⭐⭐⭐⭐⭐ |
| ObjectProvider | ⭐⭐⭐⭐ | 低 | 高 | ⭐⭐⭐⭐ |
| ApplicationContext | ⭐⭐ | 高 | 高 | ⭐⭐ |
| 手动工厂类 | ⭐⭐⭐ | 中 | 高 | ⭐⭐⭐ |
代码对比
// 方案1:@Lookup(最优雅)
@Lookup
public abstract Worker getWorker();
public void assignTask() {
Worker worker = getWorker(); // 简洁!
worker.doWork();
}
// 方案2:ObjectProvider(次优)
@Autowired
private ObjectProvider<Worker> workerProvider;
public void assignTask() {
Worker worker = workerProvider.getObject(); // 稍微啰嗦
worker.doWork();
}
// 方案3:ApplicationContext(不推荐)
@Autowired
private ApplicationContext context;
public void assignTask() {
Worker worker = context.getBean(Worker.class); // 耦合容器
worker.doWork();
}
九、注意事项与坑 ⚠️
坑1:类不能是final
// ❌ 错误:final类无法被CGLIB继承
@Component
public final class Boss { // final!
@Lookup
public Worker getWorker() {
return null;
}
}
// 报错:Cannot create CGLIB subclass of final class
解决: 去掉final关键字
坑2:方法不能是private
// ❌ 错误:private方法无法被重写
@Component
public class Boss {
@Lookup
private Worker getWorker() { // private!
return null;
}
}
// @Lookup不生效,每次都返回null
解决: 改成public或protected
坑3:方法不能是final
// ❌ 错误:final方法无法被重写
@Component
public class Boss {
@Lookup
public final Worker getWorker() { // final!
return null;
}
}
解决: 去掉final关键字
坑4:必须是Spring管理的Bean
// ❌ 错误:Boss不是Spring Bean
public class Boss { // 没有@Component
@Lookup
public Worker getWorker() {
return null;
}
}
// @Lookup不生效
解决: 加上@Component注解
坑5:返回类型必须匹配
@Component
public abstract class Boss {
@Lookup
public abstract Worker getWorker();
}
@Component
@Scope("prototype")
public class SpecialWorker extends Worker {
// ...
}
// 使用
Worker worker = getWorker(); // OK,返回SpecialWorker
SpecialWorker specialWorker = getWorker(); // ❌ 类型不匹配!
十、高级用法 🚀
10.1 结合@Scope实现请求级Bean
@Component
public abstract class UserService {
@Lookup
public abstract RequestContext getRequestContext();
public void processRequest() {
RequestContext context = getRequestContext();
context.setAttribute("user", getCurrentUser());
}
}
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private Map<String, Object> attributes = new HashMap<>();
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
}
10.2 策略模式实现
@Component
public abstract class PaymentService {
@Lookup("alipayStrategy")
public abstract PaymentStrategy getAlipayStrategy();
@Lookup("wechatStrategy")
public abstract PaymentStrategy getWechatStrategy();
public void pay(Order order, String paymentType) {
PaymentStrategy strategy;
if ("alipay".equals(paymentType)) {
strategy = getAlipayStrategy();
} else {
strategy = getWechatStrategy();
}
strategy.pay(order);
}
}
@Component("alipayStrategy")
@Scope("prototype")
public class AlipayStrategy implements PaymentStrategy {
public void pay(Order order) {
System.out.println("支付宝支付:" + order.getAmount());
}
}
@Component("wechatStrategy")
@Scope("prototype")
public class WechatStrategy implements PaymentStrategy {
public void pay(Order order) {
System.out.println("微信支付:" + order.getAmount());
}
}
10.3 对象池模式
@Component
public abstract class ConnectionPool {
@Lookup
protected abstract Connection createConnection();
private Queue<Connection> pool = new ConcurrentLinkedQueue<>();
private AtomicInteger count = new AtomicInteger(0);
public Connection getConnection() {
Connection conn = pool.poll();
if (conn == null && count.get() < 10) {
conn = createConnection(); // 创建新连接
count.incrementAndGet();
}
return conn;
}
public void releaseConnection(Connection conn) {
pool.offer(conn); // 归还连接池
}
}
@Component
@Scope("prototype")
public class Connection {
private final String id = UUID.randomUUID().toString();
public void execute(String sql) {
System.out.println("连接" + id + "执行SQL:" + sql);
}
}
十一、面试高频问题 🎤
Q1: @Lookup的作用是什么?
答: @Lookup用于解决单例Bean依赖多例Bean的问题。它通过CGLIB动态代理,重写标注的方法,使每次调用都从Spring容器获取新的Bean实例。
Q2: @Lookup的实现原理?
答:
Spring使用CGLIB生成当前类的子类,重写@Lookup标注的方法。重写后的方法内部调用beanFactory.getBean()获取Bean实例,从而保证每次调用都返回新对象。
Q3: @Lookup有哪些限制?
答:
- 类不能是final(无法被CGLIB继承)
- 方法不能是private(无法被重写)
- 方法不能是final(无法被重写)
- 必须是Spring管理的Bean
Q4: @Lookup和ObjectProvider的区别?
答:
- @Lookup:更优雅,无需手动调用getObject(),像普通方法一样使用
- ObjectProvider:需要注入Provider,手动调用getObject()获取实例
- 推荐优先使用@Lookup
Q5: 什么场景适合用@Lookup?
答:
- 单例Bean需要获取多例Bean
- 命令模式(每次执行都创建新命令对象)
- 策略模式(每次使用都创建新策略实例)
- 对象池(按需创建新对象)
十二、最佳实践 💡
1. 使用抽象方法
// ✅ 推荐
public abstract class Boss {
@Lookup
public abstract Worker getWorker();
}
// ❌ 不推荐(虽然可以)
public class Boss {
@Lookup
public Worker getWorker() {
return null; // 多余的返回值
}
}
2. 方法命名清晰
// ✅ 好的命名
@Lookup
public abstract Task createTask();
@Lookup
public abstract Command newCommand();
// ❌ 不好的命名
@Lookup
public abstract Task get(); // 太笼统
@Lookup
public abstract Task task(); // 不清楚是获取还是创建
3. 配合接口使用
public interface TaskFactory {
Task createTask();
}
@Component
public abstract class TaskFactoryImpl implements TaskFactory {
@Override
@Lookup
public abstract Task createTask();
}
十三、总结口诀 📝
单例依赖多例难,
@Lookup来帮忙。
CGLIB生成子类代,
方法重写藏玄机。
抽象方法最优雅,
每次调用新实例。
类不能final要记清,
方法private也不行。
命令策略对象池,
@Lookup都能搞定。
Spring黑魔法虽酷炫,
合理使用才是王道!
十四、完整示例项目 📁
// 1. 抽象命令
@Component
@Scope("prototype")
public class PrintCommand {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void execute() {
System.out.println("打印:" + message +
",命令ID:" + this.hashCode());
}
}
// 2. 命令管理器
@Component
public abstract class CommandManager {
@Lookup
public abstract PrintCommand createPrintCommand();
public void print(String message) {
PrintCommand command = createPrintCommand();
command.setMessage(message);
command.execute();
}
}
// 3. 控制器
@RestController
@RequestMapping("/commands")
public class CommandController {
@Autowired
private CommandManager commandManager;
@GetMapping("/print")
public String print(@RequestParam String message) {
commandManager.print(message);
return "打印成功";
}
}
// 4. 测试
@Test
public void testLookup() {
commandManager.print("Hello"); // 命令ID:123456
commandManager.print("World"); // 命令ID:789012
commandManager.print("Spring"); // 命令ID:345678
// 每次都是不同的命令实例!✅
}
参考资料 📚
系列完结! 🎉
从131到135,五大知识点全部搞定!
编写时间:2025年
作者:技术文档小助手 ✍️
版本:v1.0
愿你的代码像魔法一样优雅! 🪄✨