Skip to content

面试官追问:不用 Spring 框架,你怎么实现 IoC 和 AOP

问题

面试官问了你一整套 Spring 原理之后,突然话锋一转:"如果现在限制你不能用 Spring 框架,让你自己实现 IoC(控制反转)和 AOP(面向切面编程),你打算怎么做?"

这不是在考你背源码,而是在考你对框架本质的理解。能回答好这道题,说明你真正理解 IoC 和 AOP 是什么,而不仅仅是知道怎么用 @Autowired。

拆解:Spring 到底做了什么

模块本质核心技术
IoC 容器集中管理对象创建和依赖关系的工厂包扫描 + 反射 + 注解
AOP 框架在不改目标类的前提下插入横切逻辑JDK 动态代理 / CGLIB
整合点容器创建 Bean 时自动为匹配切面的 Bean 生成代理BeanPostProcessor

所以问题简化成:用反射 + 注解 + 动态代理,写一个精简版 Spring 容器,并且让 IoC 和 AOP 自动整合

代码实现

1. 自定义注解

java
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 容器

java
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);
                }
            }
        }
    }
}

踩坑记录——三级缓存的误区

  1. 三级缓存存的是工厂,不是实例。很多人写三级缓存直接用 Map<Class<?>, Object> 存早期实例,这会导致循环依赖中 A 拿到的 B 引用是 B 的完整实例(注入完成),但 Spring 三级缓存存的是 ObjectFactory,目的是让 AOP 能在创建早期实例时就生成代理。如果你只是手写 IoC 不需要 AOP,二级缓存就够,三级缓存是留给 AOP 代理的。

  2. Supplier<Object> 不是 () -> Object。上面代码用了 java.util.function.Supplier,不是箭头函数当类型——合法的 Java 类型声明。

  3. prototype 不缓存。prototype 作用域的 Bean 不用放到三级缓存里,每次 getBean() 直接反射创建新实例。Spring 对 prototype 的注入依赖的单例 Bean 会正常注入,但 prototype 之间互相依赖不会做循环依赖处理——Spring 直接抛异常。

3. 手写 AOP 代理

java
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 ProxyDefaultAopProxyFactory 同样规则
无接口时CGLIB EnhancerCGLIB,但 Spring 4+ 可以强制 CGLIB
切面匹配简单前缀通配符AspectJ 的 execution() 表达式+Pointcut 解析
Around 通知简化版,未实现 ProceedingJoinPoint完整 joinpoint 链
自调用失效和 Spring 一样存在官方 known issue,引入 AopContext.currentProxy() 解决

自调用失效的坑:当一个类内部方法 A 调用方法 B 时,走的是 this 引用而非代理对象,所以 B 上的切面不会触发。你在生产遇到事务不生效、日志切面不打印,八成是这个原因。解决方案:

java
// Spring 方案
((UserService) AopContext.currentProxy()).methodB();

// 或者注入自己(自引用)
@Service
public class UserService {
    @Autowired
    private UserService self;  // 代理对象
    public void methodA() {
        self.methodB();  // 走代理,切面生效
    }
}

4. IoC + AOP 整合使用示例

java
@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 作用域?

java
@Component("prototype")  // 每次 getBean 返回新实例
public class TaskProcessor {
    // 每次调用 getBean() 都会创建新实例
}

Spring 对 prototype 的循环依赖不做处理——检测到直接抛 BeanCurrentlyInCreationException。为什么?因为 prototype 每次创建新实例,没法缓存早期引用,也就没法打破循环。

追问:自调用失效的根本原因

java
// 事务不生效的经典场景
@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 字段,从 EnvironmentProperties 读取,用 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 中间件连文档都不用看。

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。