面试官追问:不用 Spring 框架,你怎么实现 IoC 和 AOP
问题
面试官问了你一整套 Spring 原理之后,突然话锋一转:"如果现在限制你不能用 Spring 框架,让你自己实现 IoC(控制反转)和 AOP(面向切面编程),你打算怎么做?"
这不是在考你背源码,而是在考你对框架本质的理解。能回答好这道题,说明你真正理解 IoC 和 AOP 是什么,而不仅仅是知道怎么用 @Autowired。
拆解:Spring 到底做了什么
| 模块 | 本质 | 核心技术 |
|---|---|---|
| IoC 容器 | 集中管理对象创建和依赖关系的工厂 | 包扫描 + 反射 + 注解 |
| AOP 框架 | 在不改目标类的前提下插入横切逻辑 | JDK 动态代理 / CGLIB |
| 整合点 | 容器创建 Bean 时自动为匹配切面的 Bean 生成代理 | BeanPostProcessor |
所以问题简化成:用反射 + 注解 + 动态代理,写一个精简版 Spring 容器,并且让 IoC 和 AOP 自动整合。
代码实现
1. 自定义注解
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
String pointcut();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
String pointcut();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Around {
String pointcut();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
}2. 手写 IoC 容器
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
public class SimpleIoCContainer {
// 一级缓存:完整 Bean(已完成实例化 + 注入 + 代理)
private final Map<Class<?>, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存:早期 Bean(已实例化但未完成注入)
private final Map<Class<?>, Object> earlySingletonObjects = new ConcurrentHashMap<>();
// 三级缓存:ObjectFactory,用于提前暴露半成品
private final Map<Class<?>, Supplier<Object>> singletonFactories = new ConcurrentHashMap<>();
// Bean 定义:记录每个类的作用域
private final Map<Class<?>, String> scopeDefinitions = new HashMap<>();
// AOP 代理工厂
private SimpleAopProxy aopProxy = new SimpleAopProxy();
public void scan(String basePackage) throws Exception {
List<Class<?>> componentClasses = scanPackage(basePackage);
// 第一阶段:注册 BeanDefinition
for (Class<?> clazz : componentClasses) {
Component comp = clazz.getAnnotation(Component.class);
String scope = "singleton"; // 默认单例
// 允许 @Component("prototype") 指定作用域
if (comp != null && !comp.value().isEmpty()) {
scope = comp.value();
}
scopeDefinitions.put(clazz, scope);
if ("singleton".equals(scope)) {
// 三级缓存放的是 Supplier,不是 Bean 实例
singletonFactories.put(clazz, () -> {
try {
// 选择无参构造器实例化(后面可以扩展支持 @Autowired 构造器)
Constructor<?> ctor = clazz.getDeclaredConstructor();
ctor.setAccessible(true);
return ctor.newInstance();
} catch (Exception e) {
throw new RuntimeException("实例化失败: " + clazz.getName(), e);
}
});
}
}
// 第二阶段:实例化所有单例 Bean 并注入依赖
// 注意:这里可能会触发循环依赖时的三级缓存提前暴露
for (Class<?> clazz : componentClasses) {
if ("singleton".equals(scopeDefinitions.get(clazz))) {
getBean(clazz);
}
}
// 第三阶段:扫描切面,为匹配的 Bean 生成代理
// 这个阶段在注入完成后执行,避免代理对象混入三级缓存导致混乱
for (Class<?> clazz : componentClasses) {
if (clazz.isAnnotationPresent(Aspect.class)) {
Object aspectInstance = getBean(clazz);
aopProxy.registerAspect(aspectInstance);
}
}
// 为所有已创建的单例 Bean 创建代理(如果匹配切面)
for (Map.Entry<Class<?>, Object> entry : singletonObjects.entrySet()) {
Object proxy = aopProxy.createProxy(entry.getValue());
singletonObjects.put(entry.getKey(), proxy);
}
}
@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> clazz) throws Exception {
// 一级缓存命中直接返回
if (singletonObjects.containsKey(clazz)) {
return (T) singletonObjects.get(clazz);
}
// 二级缓存命中(处理循环依赖时提前暴露的)
if (earlySingletonObjects.containsKey(clazz)) {
return (T) earlySingletonObjects.get(clazz);
}
// 三级缓存取出 ObjectFactory,创建实例
if (singletonFactories.containsKey(clazz)) {
Object instance = singletonFactories.get(clazz).get();
// 放入二级缓存,标记为"半成品"——这一步是关键
earlySingletonObjects.put(clazz, instance);
// 注入依赖
injectDependencies(instance, clazz);
// 注入完成,从二级移到一级
singletonObjects.put(clazz, instance);
earlySingletonObjects.remove(clazz);
singletonFactories.remove(clazz);
return (T) instance;
}
// prototype 作用域:每次创建新实例
String scope = scopeDefinitions.get(clazz);
if ("prototype".equals(scope)) {
Object instance = clazz.getDeclaredConstructor().newInstance();
injectDependencies(instance, clazz);
return (T) instance;
}
throw new RuntimeException("Bean not found: " + clazz.getName());
}
private void injectDependencies(Object bean, Class<?> clazz) throws Exception {
// 先处理字段注入
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
Class<?> fieldType = field.getType();
Object dependency = getBean(fieldType);
field.set(bean, dependency);
}
}
// 如果有 @Autowired 构造器注入,在这里处理
// 为简化,本实现只处理字段注入
}
private List<Class<?>> scanPackage(String basePackage) throws Exception {
List<Class<?>> classes = new ArrayList<>();
String path = basePackage.replace('.', '/');
URL resource = Thread.currentThread().getContextClassLoader().getResource(path);
if (resource == null) return classes;
File dir = new File(resource.toURI());
scanDirectory(dir, basePackage, classes);
return classes;
}
private void scanDirectory(File dir, String packageName, List<Class<?>> classes) throws Exception {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
scanDirectory(file, packageName + "." + file.getName(), classes);
} else if (file.getName().endsWith(".class")) {
String className = packageName + "." +
file.getName().replace(".class", "");
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
classes.add(clazz);
}
}
}
}
}踩坑记录——三级缓存的误区:
三级缓存存的是工厂,不是实例。很多人写三级缓存直接用
Map<Class<?>, Object>存早期实例,这会导致循环依赖中 A 拿到的 B 引用是 B 的完整实例(注入完成),但 Spring 三级缓存存的是ObjectFactory,目的是让 AOP 能在创建早期实例时就生成代理。如果你只是手写 IoC 不需要 AOP,二级缓存就够,三级缓存是留给 AOP 代理的。Supplier<Object>不是() -> Object。上面代码用了java.util.function.Supplier,不是箭头函数当类型——合法的 Java 类型声明。prototype 不缓存。prototype 作用域的 Bean 不用放到三级缓存里,每次
getBean()直接反射创建新实例。Spring 对 prototype 的注入依赖的单例 Bean 会正常注入,但 prototype 之间互相依赖不会做循环依赖处理——Spring 直接抛异常。
3. 手写 AOP 代理
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class SimpleAopProxy {
private final Map<String, List<Method>> beforeAdvices = new HashMap<>();
private final Map<String, List<Method>> afterAdvices = new HashMap<>();
private final Map<String, List<Method>> aroundAdvices = new HashMap<>();
private Object aspectInstance;
public void registerAspect(Object aspectInstance) {
this.aspectInstance = aspectInstance;
Class<?> clazz = aspectInstance.getClass();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Before.class)) {
String pointcut = method.getAnnotation(Before.class).pointcut();
beforeAdvices.computeIfAbsent(pointcut, k -> new ArrayList<>()).add(method);
}
if (method.isAnnotationPresent(After.class)) {
String pointcut = method.getAnnotation(After.class).pointcut();
afterAdvices.computeIfAbsent(pointcut, k -> new ArrayList<>()).add(method);
}
if (method.isAnnotationPresent(Around.class)) {
String pointcut = method.getAnnotation(Around.class).pointcut();
aroundAdvices.computeIfAbsent(pointcut, k -> new ArrayList<>()).add(method);
}
}
}
public boolean hasAspectFor(Class<?> targetClass) {
String className = targetClass.getName();
return matchAny(className, beforeAdvices)
|| matchAny(className, afterAdvices)
|| matchAny(className, aroundAdvices);
}
private boolean matchAny(String className, Map<String, List<Method>> adviceMap) {
for (String pattern : adviceMap.keySet()) {
if (matchPointcut(className, pattern)) return true;
}
return false;
}
private boolean matchPointcut(String className, String pattern) {
// 简单通配符匹配:com.example.* 匹配 com.example 下的所有类
// 生产级需支持 AspectJ execution 表达式
if (pattern.endsWith("*")) {
String prefix = pattern.substring(0, pattern.length() - 1);
return className.startsWith(prefix);
}
return className.equals(pattern);
}
@SuppressWarnings("unchecked")
public <T> T createProxy(T target) {
if (!hasAspectFor(target.getClass())) {
return target; // 不匹配切面,直接返回原对象
}
if (target.getClass().getInterfaces().length > 0) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
// 先执行 @Around(如果存在)
Object result = invokeAroundAdvices(target, method, args);
if (result != null) return result;
// 执行 Before 切面
invokeAdvices(beforeAdvices, target.getClass().getName(), method, args);
// 执行目标方法
result = method.invoke(target, args);
// 执行 After 切面
invokeAdvices(afterAdvices, target.getClass().getName(), method, args);
return result;
}
);
} else {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxyMethod) -> {
Object result = invokeAroundAdvices(target, method, args);
if (result != null) return result;
invokeAdvices(beforeAdvices, target.getClass().getName(), method, args);
// CGLIB MethodProxy.invoke 走的是原始方法,不走代理链
result = proxyMethod.invoke(target, args);
invokeAdvices(afterAdvices, target.getClass().getName(), method, args);
return result;
});
return (T) enhancer.create();
}
}
private Object invokeAroundAdvices(Object target, Method method, Object[] args) throws Exception {
String className = target.getClass().getName();
List<Method> advices = aroundAdvices.get(className);
if (advices == null || advices.isEmpty()) return null;
for (Method advice : advices) {
// 简单实现:@Around 方法如果返回非 null 则替代目标方法执行
// 更完整的实现需要 ProceedingJoinPoint.proceed()
if (advice.getParameterCount() == 0) {
advice.invoke(aspectInstance);
}
}
return null; // 继续执行原方法
}
private void invokeAdvices(Map<String, List<Method>> adviceMap,
String className, Method method, Object[] args) throws Exception {
for (Map.Entry<String, List<Method>> entry : adviceMap.entrySet()) {
if (matchPointcut(className, entry.getKey())) {
// 可以进一步对方法名做匹配,这里简化
for (Method advice : entry.getValue()) {
advice.invoke(aspectInstance);
}
}
}
}
}AOP 实现的关键细节:
| 决策点 | 手写做法 | Spring 的做法 |
|---|---|---|
| 接口存在时 | JDK Proxy | DefaultAopProxyFactory 同样规则 |
| 无接口时 | CGLIB Enhancer | CGLIB,但 Spring 4+ 可以强制 CGLIB |
| 切面匹配 | 简单前缀通配符 | AspectJ 的 execution() 表达式+Pointcut 解析 |
| Around 通知 | 简化版,未实现 ProceedingJoinPoint | 完整 joinpoint 链 |
| 自调用失效 | 和 Spring 一样存在 | 官方 known issue,引入 AopContext.currentProxy() 解决 |
自调用失效的坑:当一个类内部方法 A 调用方法 B 时,走的是 this 引用而非代理对象,所以 B 上的切面不会触发。你在生产遇到事务不生效、日志切面不打印,八成是这个原因。解决方案:
// Spring 方案
((UserService) AopContext.currentProxy()).methodB();
// 或者注入自己(自引用)
@Service
public class UserService {
@Autowired
private UserService self; // 代理对象
public void methodA() {
self.methodB(); // 走代理,切面生效
}
}4. IoC + AOP 整合使用示例
@Component
public class UserService {
@Autowired
private EmailService emailService;
public void register(String username) {
System.out.println("注册用户: " + username);
emailService.sendWelcome(username);
}
public void registerWithPoints(String username, int points) {
System.out.println("注册用户: " + username + ", 赠送积分: " + points);
emailService.sendWelcome(username);
}
}
@Component
public class EmailService {
public void sendWelcome(String username) {
System.out.println("发送欢迎邮件给: " + username);
}
}
@Aspect
@Component("singleton") // 注意:切面本身也是单例 Bean
public class LogAspect {
@Before(pointcut = "com.example.*")
public void logBefore() {
System.out.println("[日志] 方法开始执行 —— " + System.currentTimeMillis());
}
@After(pointcut = "com.example.*")
public void logAfter() {
System.out.println("[日志] 方法执行完毕 —— " + System.currentTimeMillis());
}
@Around(pointcut = "com.example.*")
public void logAround() {
System.out.println("[日志] Around 环绕");
}
}
// 启动类
public class App {
public static void main(String[] args) throws Exception {
SimpleIoCContainer container = new SimpleIoCContainer();
container.scan("com.example");
// 直接从容器拿 UserService,已经是代理对象
UserService userService = container.getBean(UserService.class);
userService.register("Alice");
// 输出:
// [日志] Around 环绕
// [日志] 方法开始执行 —— 1742720800000
// 注册用户: Alice
// 发送欢迎邮件给: Alice
// [日志] 方法执行完毕 —— 1742720800001
// 验证是代理对象
System.out.println("Is proxy: " + (userService.getClass().getName().contains("$Proxy")));
}
}追问:怎么处理 prototype 作用域?
@Component("prototype") // 每次 getBean 返回新实例
public class TaskProcessor {
// 每次调用 getBean() 都会创建新实例
}Spring 对 prototype 的循环依赖不做处理——检测到直接抛 BeanCurrentlyInCreationException。为什么?因为 prototype 每次创建新实例,没法缓存早期引用,也就没法打破循环。
追问:自调用失效的根本原因
// 事务不生效的经典场景
@Service
public class OrderService {
public void placeOrder(Order order) {
saveOrder(order); // 非代理调用
deductStock(order); // 非代理调用
sendNotification(order); // 非代理调用
}
@Transactional
public void saveOrder(Order order) { ... }
@Transactional
public void deductStock(Order order) { ... }
}三个 @Transactional 方法全部失效,因为 placeOrder 内部调用走的是 this.saveOrder(),不是代理对象。Spring 事务通过 TransactionInterceptor 在代理层拦截,自调用跳过了代理层。
在你转去做 Agent 开发时,这个坑同样存在:如果 Agent 框架用动态代理做 Tool 调用拦截、状态管理、链路追踪,自调用都会让这些能力失效。所以设计 Agent 框架时,要么用 CGLIB 强制代理所有方法,要么在文档里明确标注自调用问题。
进阶思考
- 怎么支持
@Value注入配置值? 扫描@Value字段,从Environment或Properties读取,用TypeConverter做类型转换(String → int/boolean 等)。 - AOP 性能怎么优化? 缓存
Method对象到ReflectiveMethodInvocation,避免每次调用都反射查找;@Around切面里如果不需要反射,可以将ProceedingJoinPoint参数缓存起来。 - 为什么不直接用 AspectJ 编译期织入? Spring 需要运行时热部署和动态代理替换,编译期织入不支持运行时变更。但 AspectJ 织入性能更好(无反射调用),Netflix 的 Hystrix 就是在编译期用 AspectJ 织入的。
- Agent 框架里怎么用这套思路? LangChain 的 Tool 调用其实就是 AOP 的变体——每个 Tool 的
_run方法被代理拦截,做输入校验、日志记录、重试、限流。理解 AOP 本质后,你写 Agent 框架里的 middleware 或 interceptor 会顺手很多。
总结
不用 Spring 实现 IoC 和 AOP,核心就三样东西:反射 + 注解 + 动态代理。
IoC 容器做三件事:包扫描找到带 @Component 的类 → 反射实例化 → 递归注入依赖。循环依赖用三级缓存解决,但只对 singleton 生效。
AOP 框架做两件事:为 Bean 生成 JDK 或 CGLIB 代理 → 在代理方法中按顺序执行切面链。自调用失效是动态代理的天然缺陷,没有银弹。
面试官真正想听的不是"你能写一个 Spring 吗",而是你理解框架的设计哲学:IoC 的核心是把"对象创建和依赖管理"从业务代码中剥离出去,AOP 的核心是把"横切关注点"模块化。理解了这两点,你写出来的代码就不会跟 Spring 强耦合,哪天换框架也只是换一个容器而已。
最后说一句跟面试无关的话:Agent 开发中的 Tool 编排、LLM 调用拦截、日志 trace 注入,底层用的都是同一套思路——代理模式 + 拦截器链。你如果现在把 IoC/AOP 的源码啃透,将来写 Agent 中间件连文档都不用看。