Skip to content

Spring 核心原理(IoC 与 AOP) ​


❓ 面试官:能谈谈你对 Spring IoC(控制反转)和 DI(依赖注入)的理解吗? ​

频率:🔥🔥🔥🔥🔥

💡 一句话总结(先抛结论):

  • IoC(控制反转)是一种设计思想。把创建和管理对象的控制权,从我们自己写的代码里,反转交给了 Spring 容器去管理。
  • DI(依赖注入)是 IoC 的具体实现方式。Spring 容器在运行期间,动态地将某个对象所依赖的其他对象(比如 Controller 依赖 Service)注入到该对象中。

📝 详细原理解析与实战:

  1. 没有 IoC 的痛点:
    • 以前我们写代码:UserService service = new UserServiceImpl();。
    • 耦合度极高:如果哪天实现类换成了 UserNewServiceImpl,我们需要满项目去全局搜索并修改 new 关键字。
  2. 有了 IoC 之后(解耦):
    • 我们只需要打个注解 @RestController / @Service。
    • Spring 启动时,会去扫描这些注解,利用反射机制自动帮我们 new 出这些对象(称为 Bean),并放进一个叫大 Map(单例池) 的容器里。
    • 当我们需要用 Service 时,只需加个 @Autowired,Spring 就会自动从 Map 里把建好的 Bean 拿出来塞给我们。
  3. DI 的三种注入方式:
    • @Autowired 字段注入(最常用,但不被官方推荐,容易产生循环依赖且不好写单元测试)。
    • 构造器注入(Spring 官方极力推荐):强制依赖,保证 Bean 初始化时所有依赖都已就绪。
    • Setter 方法注入。

❓ 面试官:Spring 的 AOP(面向切面编程)是什么?你们在项目里怎么用的? ​

频率:🔥🔥🔥🔥🔥

💡 一句话总结(先抛结论): AOP 的核心目的是“将非业务的通用逻辑(如日志、权限、事务)从核心业务代码中剥离出来,实现无侵入式的增强”。它的底层实现原理是动态代理。

📝 详细原理解析与应用场景:

  1. 通俗理解:

    • 你写了 100 个接口,老板突然要求:每个接口被调用时,都要打印一下入参、出参和接口耗时。
    • 如果不用 AOP:你需要修改这 100 个接口的源码,复制粘贴 100 遍日志代码(灾难)。
    • 如果用 AOP:你只需要写一个切面类(Aspect),定义一个切点(Pointcut,指定拦截哪些包下的方法),然后写一个环绕通知(@Around)。Spring 会自动在这些方法执行前后,帮你“织入”这段日志代码。
  2. 实战应用场景(高频考点):

    • 全局日志记录:记录接口操作人、时间、耗时、异常信息,存入数据库。
    • 权限校验:自定义一个 @RequireRole("admin") 注解,利用 AOP 拦截该注解,判断当前用户是否有权限。
    • 分布式锁:自定义 @RedisLock 注解,AOP 切面里实现 Redisson 加锁和释放锁的逻辑。
    • Spring 声明式事务(@Transactional):这是 Spring 官方自己写的最牛逼的 AOP 切面。

❓ 面试官:既然 AOP 底层是动态代理,能讲讲 JDK 动态代理和 CGLib 动态代理的区别吗?【中级】 ​

频率:🔥🔥🔥🔥

💡 一句话总结(先抛结论):

  • JDK 动态代理:基于接口实现。它通过反射,在内存里动态生成一个实现了目标接口的代理类。如果你的类没有实现任何接口,就不能用 JDK 代理。
  • CGLib 动态代理:基于继承实现。它通过底层字节码技术(ASM),动态生成一个目标类的子类。即使目标类没有实现接口也能代理,但如果目标方法是 final 或 private 的,则无法被重写代理。

📝 Spring 里的选择策略:

  • 在 Spring 5.x(Spring Boot 2.x)及以后的版本中,默认情况下全部使用 CGLib 动态代理(即使你实现了接口)。因为 CGLib 性能更好,且不需要强制开发者去写接口。如果你非要强制使用 JDK 代理,需要配置 spring.aop.proxy-target-class=false。

❓ 面试官:请简述一下 Spring Bean 的生命周期?【中高级】 ​

频率:🔥🔥🔥

💡 一句话总结(先抛结论): Spring Bean 的生命周期非常复杂,但核心骨架就 4 步:实例化(new) $\rightarrow$ 属性赋值(DI 注入) $\rightarrow$ 初始化(init) $\rightarrow$ 销毁(destroy)。

📝 详细的扩展点(背住这几个关键接口即可): 面试官其实不想听你背 10 几步的源码,他想听的是你知不知道怎么在 Bean 创建的过程中去“插手/干预”。

  1. 实例化(Instantiation):Spring 利用反射调用构造方法,在内存中 new 出一个空壳对象。
  2. 属性赋值(Populate):解析 @Autowired 等注解,把依赖的其他 Bean 注入进来。
  3. 初始化(Initialization,最重要,扩展点全在这里):
    • 如果实现了 Aware 接口(如 BeanNameAware),会把 Spring 容器的一些底层组件传给你。
    • 执行 BeanPostProcessor.postProcessBeforeInitialization(前置处理器):可以在初始化前修改 Bean 属性。
    • 执行自定义的初始化方法(被 @PostConstruct 注解修饰的方法,或者 InitializingBean 接口的 afterPropertiesSet)。
    • 执行 BeanPostProcessor.postProcessAfterInitialization(后置处理器):(重点!)Spring AOP 就是在这里发生的。如果是被切面拦截的类,Spring 会在这个阶段生成一个代理对象,并把代理对象扔进单例池,替换掉原来的原生对象。
  4. 使用阶段:对象静静地躺在单例池(大 Map)里供别人调用。
  5. 销毁(Destruction):Spring 容器关闭时,执行 @PreDestroy 标注的方法进行资源清理。