
聊Spring框架得先承认一件事这个生态大得让人容易迷路。光看最近大家搜索的词——Spring AI、Spring Boot 4.x、三级缓存原理、手写Spring、Spring Security、Spring Cloud Gateway、Actuator未授权访问……一个新入门的人看到这堆东西很容易陷入“怎么什么都叫Spring”的困惑里。我写这篇文章就是想把这些散落的关键词串成一条线从Spring的核心IoC容器讲起把Bean生命周期、三级缓存、MVC和Boot的自动配置、安全网关、再到AI扩展全部理一遍最后聊聊手写Spring和学习路线的思路。内容适合两类人一类是刚学完基础语法、想系统搞懂Spring的初中级Java开发者另一类是面试前突击原理、想把手里的“会用”升级成“懂为什么”的朋友。1. 先搞懂Spring到底解决了什么从JavaEE时代的痛点到IoC容器1.1 控制反转把“谁找谁”的问题彻底掉了个头早期Java服务端开发里Service要调DAO就得自己new一个DAO实例几十个类互相协作到处是new。这时候你改一个实现类可能牵连十几个文件。核心痛点不是“代码多”而是对象之间的耦合全靠程序员自觉——谁忘了初始化谁运行期就给你个空指针。Spring的解法看起来很朴素你只声明“我需要什么”容器负责把“需要的东西”给你。这个“声明依赖容器注入”的动作就是依赖注入DI而“控制权从代码手里交到容器手里”就是控制反转IoC。举个例子你写一个订单服务Service public class OrderService { private final UserService userService; public OrderService(UserService userService) { this.userService userService; } }代码里没有一次new。UserService从哪来Spring容器创建OrderService时发现构造器需要UserService就从容器里找到或先创建UserService再把它塞进来。控制权反转的本质是你不用管理依赖的创建和组装了你只写自己那部分逻辑。1.2 三种注入方式我的建议和理由依赖注入有三种常见姿势构造器注入、Setter注入、字段注入。注入方式优点缺点我的评价构造器注入依赖不可变、空指针在启动期暴露构造函数参数多时难看官方推荐Spring Boot自动装配大量使用Setter注入可选依赖灵活依赖可能被替换、漏注入难发现老XML时代用得多现在尽量不用字段注入代码最少隐藏依赖、不方便测试、容易造出循环依赖不推荐别为了少写两行牺牲可测性我个人强烈推荐构造器注入。Spring团队在官方文档里也是这个倾向Spring Boot的自动配置代码里构造器注入到处可见。字段注入最大的坑是把依赖关系隐藏了一个大类十个Autowired字段你根本不知道它到底依赖什么单元测试里你也只能靠反射硬塞。1.3 BeanFactory与ApplicationContext别再把它们当一回事BeanFactory是IoC容器的底层接口提供getBean()、containsBean()这些基础能力。ApplicationContext在它之上加了事件发布、国际化、环境配置、AOP整合等能力。你平时代码里看到的ClassPathXmlApplicationContext、AnnotationConfigApplicationContext本质都是经过增强的BeanFactory。这个区别很重要因为BeanFactory创建Bean是懒加载的调用getBean时才创建而ApplicationContext默认启动时就完成单例Bean的实例化和初始化——这也是为什么Spring Boot启动慢、但一启动你就能直接用的原因。2. Bean生命周期全链路拆解从定义到销毁每个扩展点都在干什么面试问Bean生命周期很多人都能背出“实例化、属性填充、初始化、销毁”这四步但一追问“BeanPostProcessor在哪一步介入”“Aware回调又是什么时候”就卡壳。这里我把整条链路按实际源码顺序拆开。2.1 生命周期从抽象定义开始BeanDefinition才是源头Spring不是直接用反射new一个对象就完事它先要把“怎么创建这个Bean”的描述性信息存下来这个描述就是BeanDefinition。// 简化版源码里是接口 class BeanDefinition { Class? beanClass; // 类全限定名 String scope; // singleton还是prototype boolean lazyInit; // 是否懒加载 String initMethodName; // 初始化方法 String destroyMethodName; // 销毁方法 List? propertyValues; // 属性值 }XML里的bean标签、Bean注解、Component扫描最终都会被解析成BeanDefinition放进容器。这部分是很多人忽略的重点Spring拿到的是Bean的“图纸”不是现成的零件。2.2 生命周期十个节点逐个过以单例Bean为例完整链路大致如下实例化通过构造器创建原始对象属性填充把依赖的其他Bean、配置项注入进来Aware回调如果Bean实现了BeanNameAware、BeanFactoryAware等接口容器会回调对应方法把上下文信息告诉BeanBeanPostProcessor前置处理postProcessBeforeInitialization()初始化方法调用PostConstruct注解方法 →InitializingBean.afterPropertiesSet()→ 自定义init-methodBeanPostProcessor后置处理postProcessAfterInitialization()到这里对象可能被AOP代理增强实际存进单例池的是代理对象就绪正常使用容器关闭时PreDestroy注解方法 →DisposableBean.destroy()→ 自定义destroy-method这里不少人有疑问PostConstruct和InitializingBean、init-method到底什么关系答案是顺序固定先PostConstruct再afterPropertiesSet最后init-method。同样销毁顺序是先PreDestroy再destroy()最后destroy-method。2.3 BeanPostProcessorSpring所有高级功能的“插槽”很多神秘高层API的本质都藏在BeanPostProcessor里。AOP代理怎么加上的就是某个BeanPostProcessor在postProcessAfterInitialization阶段给目标对象生成代理。Autowired怎么注入的AutowiredAnnotationBeanPostProcessor在属性填充阶段帮你干的活。Component public class LogBeanPostProcessor implements BeanPostProcessor { Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 这里可以对Bean做包装、代理、埋点 return bean; } }理解了这个插槽你就明白为什么框架里那么多魔法——Spring自己也是靠这些插槽实现扩展的。以后遇到什么奇怪需求比如给每个Controller方法加耗时日志第一反应就应该是起一个BeanPostProcessor或者BeanFactoryPostProcessor而不是改业务代码。2.4 一个实战教训别在构造器里玩花活我有一次排查线上问题某服务启动时偶发NullPointerException最后定位到是个Service的构造器里调用了另一个Bean的某个方法而那个Bean还没完成属性填充。构造器里能拿到的是“半成品”此时属性还没注入完。如果你必须依赖别的Bean正确的做法是用PostConstruct或InitializingBean因为此时所有依赖都就绪了。这条教训写进代码评审规范里能省很多事。3. 三级缓存不是缓存一步一步推导循环依赖的解决方案Spring三级缓存是面试题里出现频率最高的词但它其实是个名不副实的名字——这三个Map不是为了缓存性能而是为了解决单例Bean循环依赖问题。3.1 循环依赖是什么场景最简单的例子Service public class A { private final B b; public A(B b) { this.b b; } } Service public class B { private final A a; public B(A a) { this.a a; } }创建A需要B创建B需要A死循环。Spring的解决方式是先创建一个A的半成品暴露出去等属性填充阶段再拿B。3.2 三个Map各司其职Spring容器里维护了三个Map// 一级缓存最终的成品Bean也就是大家拿到的那个 MapString, Object singletonObjects; // 二级缓存提前暴露的半成品已经实例化但还没完成属性填充和初始化 MapString, Object earlySingletonObjects; // 三级缓存存储ObjectFactory提前暴露的工厂可以后续生成代理 MapString, ObjectFactory? singletonFactories;创建A时先把一个ObjectFactory放进三级缓存然后属性填充时发现需要B就转而创建BB填充属性时发现需要A此时从三级缓存拿到A的工厂调用getObject()得到一个A的早期引用early referenceB完成创建再回过来A拿到B完成自己的填充和初始化。3.3 为什么必须是三级二级不够吗这是面试里最刁钻的追问。二级缓存确实能解决提前暴露半成品的问题——把A放进二级缓存B直接拿就行。但AOP代理这关过不去。如果A被Transactional或Async增强Spring最终放进单例池的应该是个代理对象不是原始对象。早期暴露时如果只暴露原始对象B拿到的就是未经代理的裸Bean等A完成增强生成代理后B里的引用还是旧的——这不就出问题了吗。所以Spring用三级缓存装ObjectFactory工厂内部走getEarlyBeanReference()在暴露早期引用时就先检查需不需要代理需要就先把代理生成出来。这就保证了所有循环依赖的对象拿到的都是最终形态的引用。3.4 哪些情况下循环依赖照样报错三级缓存不是万能药。下面几类循环依赖Spring明确说没办法构造器循环依赖实例化阶段就卡死了没有半成品可言prototype作用域的循环依赖容器只对单例做循环依赖处理Transactional和Async组合的某些场景代理生成时机不对也会报错所以设计上最好的做法还是尽量避免循环依赖。重构方向很简单把A依赖B、B依赖A拆成A依赖B、B通过事件或ObjectProvider延迟获取A或者把公共部分抽到C里让AB都依赖C。4. 手写一个微型Spring容器你才能彻底理解框架的骨架老有人问我手写Spring到底有没有用我的回答是如果你能亲手写一个精简版容器框架在你眼里就不再是一坨黑魔法。下面我带你走一遍最核心的逻辑代码很短但五脏俱全。4.1 扫描、注册、刷新容器的三个动作手写容器的第一步是扫描包路径找到所有带Component注解的类注册成BeanDefinition。然后是刷新refresh实例化所有单例Bean执行依赖注入。public class MiniApplicationContext { private MapString, Object singletonObjects new ConcurrentHashMap(); private MapString, BeanDefinition beanDefinitionMap new ConcurrentHashMap(); // 1. 扫描解析带注解的类 public void scan(String basePackage) { // 遍历classpath下所有.class文件 // 判断是否带MiniComponent注解 // 把类信息封装成BeanDefinition注册进beanDefinitionMap } // 2. 刷新实例化单例 public void refresh() { for (String beanName : beanDefinitionMap.keySet()) { getBean(beanName); } } // 3. 获取Bean从单例池拿没有就创建 public Object getBean(String beanName) { Object bean singletonObjects.get(beanName); if (bean null) { bean createBean(beanName); singletonObjects.put(beanName, bean); } return bean; } }4.2 createBean里最关键的依赖注入创建实例以后遍历Bean的所有字段凡是带MiniAutowired的就创建或获取对应类型的Bean反射塞进去。private Object createBean(String beanName) { BeanDefinition bd beanDefinitionMap.get(beanName); Class? clazz bd.getBeanClass(); Object instance clazz.getDeclaredConstructor().newInstance(); // 遍历字段执行依赖注入 for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(MiniAutowired.class)) { Object dependency getBean(field.getName()); field.setAccessible(true); field.set(instance, dependency); } } // 支持Aware让Bean知道自己叫什么名字 if (instance instanceof BeanNameAware aware) { aware.setBeanName(beanName); } return instance; }能走到这一步说明你已经完全吃透IoC的核心扫描注解、注册定义、反射实例化、手动注入。Spring源码里AbstractAutowireCapableBeanFactory.doCreateBean做的事比这多几百倍但骨架子就是这套。4.3 手写是学习方法不是工程方案我明确建议手写容器练手就好别在生产项目里自己造轮子。生产环境要的是事务、AOP、分布式协调、监控、扩展点、生态整合这些不是几百行代码能替代的。但如果你能把手写代码和源码路径对应起来——getBean对应AbstractBeanFactory.doGetBeanrefresh对应AbstractApplicationContext.refresh——面试时聊原理就有底气得多。5. 请求进来了怎么走Spring MVC与Spring Boot自动配置的本质现在Spring Boot几乎成了Java Web开发的代名词但很多人直接把两者划等号。其实Boot的核心价值不是新的Web框架而是自动配置它把Spring的繁复配置工程化、约定化。5.1 DispatcherServlet从前端控制器到HandlerMapping的完整链路Spring MVC的核心是一个叫DispatcherServlet的前端控制器。一次请求的完整路径是请求到达DispatcherServletHandlerMapping根据URL找到对应的HandlerExecutionChain包含Controller方法和拦截器HandlerAdapter实际调用方法把参数绑定、校验、转换都做掉方法返回ModelAndView或ResponseBody结果视图解析器处理视图渲染如果返回的是页面各种HandlerInterceptor的afterCompletion收尾你写的GetMapping(/user/{id})只是第一步的一半——Spring启动时会把所有RequestMapping注解解析成URL到方法的映射表请求进来后通过查表定位。5.2 自动配置为什么你引入一个starter就“能用”Spring Boot最让人上瘾的特性就是加入spring-boot-starter-web依赖什么都不配一个RestController就活了。这背后是EnableAutoConfiguration做的事。Boot启动时会加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里面列了一堆自动配置类。每个配置类上都有一堆条件注解AutoConfiguration ConditionalOnClass(DispatcherServlet.class) ConditionalOnWebApplication(type Type.SERVLET) public class DispatcherServletAutoConfiguration { // 只有当classpath下有DispatcherServlet且是Web项目时才生效 }这套机制被称作条件装配没有DispatcherServlet类就不配置Web没有DataSource就不配置JdbcTemplate。你加依赖classpath变了条件成立配置自动生效。这就是约定优于配置的根本实现。5.3 Actuator未授权访问一个所有人都该知道的坑热搜词里出现spring boot actuator未授权访问说明这个隐患踩的人非常多。Actuator是Boot提供的生产监控端点能暴露/actuator/env、/actuator/heapdump等敏感信息一旦端口对外开放又没做鉴权等于把运行环境、内存快照、配置明文全送出去了。工程上最低限度的防御有这三板斧关掉不必要端点management.endpoints.web.exposure.includehealth,info把管理端口独立并禁止外网访问management.server.port9091实在要对外必须加Spring Security或网关认证我见过太多人图省事直接include*这种行为在正式环境等于裸奔。一个好习惯是写完配置顺手访问一下/actuator摸清自己到底暴露了什么。6. 防止框架裸奔Spring Security、Spring Cloud Gateway与Actuator的工程化配合6.1 Spring Security过滤链才是核心思维Spring Security是Spring生态里的安全认证框架核心是一个叫FilterChain的东西。请求进入后被一串过滤器依次处理先做认证再做授权最后放行到业务接口。你可以通过SecurityFilterChain配置链上每一环。Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/**).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); return http.build(); }初学者容易犯的错是只在方法上写PreAuthorize却忘了对静态资源和公开接口做放行配置最后被过滤器链拦得莫名其妙。理解Security最好的方式是先画出一条请求穿过过滤链的路径再逐个看拦截点。6.2 Spring Cloud Gateway网关层能做的不只是路由Gateway是Spring Cloud微服务体系的入口网关底层基于WebFlux。它最常用的能力是路由配置spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix1但实际工程里网关还承担了统一鉴权、接口限流、灰度路由、日志埋点的职责。把Spring Security的鉴权逻辑上移到网关业务服务可以变得更纯粹——只关注业务不用关心来的人是谁。我用下来的经验是网关层做粗粒度过滤有没有token、路径是否合法业务层做细粒度权限这个用户能不能删这条数据这样既不重复也不遗漏。6.3 框架与边界不是加得越多越好一条非常朴素的工程原则每个中间件、每个框架模块都有它要解决的问题边界。用Gateway是为了统一流量入口不是为了让你的单体应用绕一圈用Security是为了认证授权不是拿它当业务判断的替代品。我见过有些团队把网关里的过滤器写得比业务代码还长这种框架滥用比不用框架更危险——出了问题都找不到源头。7. Spring AI来了LLM时代框架的新扩界面和生态方向7.1 Spring AI 2.0的核心概念把LLM当作一个Bean注入“Spring AI”是搜索热词里最扎眼的一个热度上升很快。Spring官方推出它的逻辑其实很自然过去我们集成数据库用JdbcTemplate、集成缓存用CacheManager现在集成大模型为什么不能有个统一抽象Spring AI的用法有一种和IoC融为一体的感觉RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } PostMapping(/chat) public String chat(RequestBody String message) { return chatClient.prompt(message).call().content(); } }你不在代码里关心底层是GLM、通义千问还是OpenAI只要配置好Model的接入参数ChatClient就能像操作JDBC一样自然地对话。这种统一抽象正是Spring生态的一贯风格。7.2 AI Skill、MCP与Multi Agent从“掉接口”到“能力编排”搜索词里出现spring ai skill、spring ai mcp、spring ai multi agent说明大家已经不满足于简单对话了。Spring AI 2.0里三个新方向值得关注AI Skill把函数调用工具化。你写一个普通Java方法标注好描述模型在需要时会自动调用这个方法相当于把业务能力暴露给大模型。MCP模型上下文协议目标是让AI Agent能统一接入文件、数据库、浏览器等外部工具Spring AI封装了MCP客户端实现概念上看很高效。Multi Agent把一个大任务拆给多个不同角色的Agent协作完成Spring AI提供了编排框架支持。这套玩法落地到项目里典型场景就是客服工单系统一个Agent负责语义理解一个Skill负责查订单状态另一个Agent负责生成答复。代码模式和写多模块业务没本质区别但理解编排思维比记住API更持久。7.3 Spring AI Alibaba与Admin控制台国内落地的实际选择热词里反复出现spring ai alibaba和spring ai alibaba admin说明国内开发者对这个方向有多关注。阿里在Spring AI上做了自己的适配层接通通义千问等国产模型同时提供一个可视化的Admin控制台可以管理模型调用、查看Token消耗、调试Agent链路。实际项目里尤其考虑到模型合规、网络稳定和成本控制国内云厂商的适配通常比直接连海外API更省心。我建议的方式是先把Spring AI的ChatClient跑通一个Demo接入最简单的文本对话再逐步加Memory聊天记忆、Skill工具函数、Multi Agent任务编排。从简单到复杂每一层都能看到框架怎么帮你省事出了问题也好按层排查。7.4 Spring Boot 4.0的兼容性提醒搜索词里还有个很具体的spring boot 4.x where to find datasourceautoconfiguration以及spring boot 4.0.0 解决jackson的jsonmapper$builder问题。Spring Boot 4.x把一批自动配置的包路径做了调整旧项目升级时确实会遇到一些配置类找不到、Jackson序列化方式变化的问题。经验是升大版本前先看官方迁移指南启动不了先看自动配置条件哪里没满足用debugtrue开启自动配置报告比瞎猜快得多。8. 面试与学习的正确姿势以源码为纲以手写为验证8.1 高频题背后的考察意图进入过Spring面试高频题三级缓存原理、Bean生命周期、依赖注入、Spring中GetMapping、事务失效场景、工厂模式在框架里的应用。说句实在话这些题考的不是背诵而是考察你有没有源码级的理解能力。比如面试官问Spring怎么解决循环依赖他不关心你能背出三个Map叫什么他想听到的是你能否讲清楚单例Bean在实例化后就具备暴露条件、AOP代理如何影响早期引用、哪些边界情况框架处理不了。说白了面试考察的是你排查问题时能不能定位到容器层面而不是换个行为就一脸懵。分享一下我推荐的知识地图面试主题必看源码核心结论Bean生命周期AbstractAutowireCapableBeanFactory.doCreateBean实例化→填充→初始化中间穿插多个扩展点循环依赖DefaultSingletonBeanRegistry.getSingleton三级缓存逐级找最后一级存工厂AOP原理AnnotationAwareAspectJAutoProxyCreator本质上是一个BeanPostProcessor事务原理TransactionInterceptor通过AOP实现声明式事务自调用会失效自动配置SpringApplication.run/AutoConfiguration.imports条件装配驱动一切8.2 一条接地气的学习路线我看到很多新人一上来就想啃源码结果在refresh()里转晕几周后放弃。我的建议是三层递进会用阶段能手写一个带Controller/Service/Mapper的Boot项目理解注解的作用能跑通CRUD和事务。原理阶段跟着官方文档画Bean生命周期图调试看getBean的调用栈搞懂三级缓存和条件装配。这一步的目的不是记住每行代码而是搞懂设计意图。验证阶段手写一个微型IoC容器再用Spring的AOP做一个日志切面最后试着回答自己之前不会的面试题。这三层走下来基本就从一个注解摆渡人变成框架明白人了。遇到线上诡异问题你至少能判断“问题在容器里还是在业务里”这本身就是很大的进步。最后分享一个我自己的习惯每次升级依赖都把Spring Boot版本号和当前引用的Spring核心版本记下来。不同大版本之间包迁移、配置类路径变化很常见别指望升级了能跑就万事大吉——跑之前看一遍启动日志的自动配置条件跑起来看一眼/actuator/health和关键端点比任何文档都更能告诉你系统真实的状态。这套先理解再行动的思路从学框架到做架构长期看都值得坚持。