资讯动态

Spring核心原理与生态应用:从IoC到Spring Boot的认知地图

发布时间:2026/10/8 3:52:23 来源:尧图企业网站定制
先聊个现象。你在技术群里抛一句“Spring”下面能接上一堆关键词Bean、IoC、AOP、Spring Boot、微服务、Spring Security、Spring AI……但你要是追问“这些词之间到底是什么关系”很多人反而卡壳了。我最早接触 Spring 时也把 Spring 和 Spring Boot 混着叫以为 Spring 就是个写接口的框架。后来自己带项目、啃源码、面试别人才慢慢把这条线理清楚。这篇不是官方手册的中文复述而是我从实际开发里攒出来的一张 Spring 认知地图先讲容器原理再看生态版图然后把三级缓存、AOP 日志、自定义校验这些高频核心点串起来最后落到项目模块、监控需求、开发环境这些能直接上手的操作上。不管你是刚学 Java 的学生还是想把基础补扎实的老手按这条线走一遍至少以后面试和选型不会心里发虚。1. 谈 Spring 之前先把 IoC 容器这件事讲透1.1 控制反转不只是“不用 new”而是把协作流程交给容器很多教程告诉你“Spring 最大的特点是 IoC也就是控制反转”但光记住这句话没用。得理解反转的是什么、交给谁了。我们写代码经常要 new 对象比如订单服务要用到用户服务以前的做法是直接在构造方法里new UserService()。一旦 UserService 又依赖别的服务你就得一层层手动拼起来。这个“组装依赖”的事传统写法里由程序员自己控制所以叫“正转”。Spring 做的事是把“谁来创建对象、谁来管理对象之间的依赖”这件事从你的代码里拿走交到容器手里——这就是控制反转。依赖注入DI是 IoC 最常见的落地方式。你不是主动去 new而是容器在创建 Bean 的时候把依赖“塞”进你的构造器或属性。举个例子Service public class OrderService { private final UserService userService; public OrderService(UserService userService) { this.userService userService; } }容器启动时扫描到这个类发现构造器要一个 UserService就会先从容器里把 UserService 的实例找出来再创建 OrderService。你不需要知道 UserService 在哪儿、怎么初始化只需要声明“我需要它”。这个机制听起来简单但实际价值巨大。它带来的是可测试性和模块解耦单元测试的时候你甚至可以传一个 mock 对象进来完全绕开 Spring。你可以把一个系统拆成几百个互相协作的 Bean各自只关心自身逻辑组装交给容器。我第一次写手写 Spring 的时候对这句“交给容器”才有了真正的体感。容器本质上就是一个 Map存 beanName 到 Bean 实例的映射扫描类、解析注解、反射创建对象、处理依赖注入。Spring 做得更庞大但起点就是这么回事。1.2 容器眼中的 Bean 与你自己 new 出来的对象差在哪里普通对象和 Bean 的区别很多初学者分不清。你在代码里new UserService()这只是一个对象活多久、由谁维护没人管。而 Spring 容器里的 Bean有明确的生命周期管理。Spring 默认的 Bean 作用域是 singleton也就是同一个 beanName 只会创建一个实例所有依赖它的地方共享这个实例。好处是省内存、效率高坏处是内部状态得注意线程安全。除了 singleton还有 prototype每次获取都会新建一个对象适合保存复杂状态的场景。Bean 的生命周期大致经过实例化 → 属性填充 → 初始化InitializingBean、PostConstruct→ 使用 → 销毁。Spring 还允许通过 BeanPostProcessor 在初始化前后插入逻辑像 AOP 代理、注解处理很多都是靠它实现的。我记得有次排查线上问题某个 Bean 的静态变量被改得乱七八糟一开始怀疑是并发后来发现是被实例化了多次。查到最后是有人在代码里直接new了那个 Bean而不是从容器里拿。这件事教训很深在 Spring 项目里凡是需要被容器管理的类就别绕开容器手动 new否则你会得到一个游离于生命周期之外的“野对象”脱管、没代理、没拦截器问题特别隐蔽。1.3 面试连环问为什么三级缓存和代理总绑在一起Spring 面试题里出镜率最高的就是“三级缓存为什么能解决循环依赖以及为什么必须是三级”。这个问题单独放一节都嫌不够我后面也会从源码层面展开。这里先建立最朴素的认知。循环依赖就是 A 依赖 BB 又依赖 A。如果两个 Bean 都靠构造器注入容器谁先创建都会卡住——A 实例化时要 B但 B 还没有反过来也一样。Spring 默认只能兜住“setter/字段注入 单例”的循环依赖靠的是提前暴露一个半成品 Bean让另一个 Bean 先拿着用等属性填充完再换成品。这里的关键是“半成品”能不能直接给出去。如果这个 Bean 后续要加 AOP 代理你提前暴露的是原始对象而不是代理对象后面替换的时候依赖方拿到的引用就错了。所以三级缓存里存的是 ObjectFactory一个工厂等真正需要的时候才决定返回原始对象还是返回代理对象。这就是它存在的意义延迟决策避免“暴露早了代理还没做好暴露晚了循环又解不了”的两难。每次有人问我怎么理解循环依赖我都会让他先想清楚一件事Spring 是个容器它得想尽办法把对象创建这件事自动化。循环依赖是创建过程中的一个死结三级缓存就是解开这个死结的落点。明白这一点面试题背起来就不是死记而是推理。2. 走出初学者认知Spring 早已不是框架而是一整张生态版图2.1 用一张表看清 Spring 家族各成员的定位刚入门的人最容易困惑的点Spring、Spring Boot、Spring Cloud、Spring MVC、Spring Security名字都带 Spring到底谁是谁实际上 Spring 官方早就不只维护一个框架而是一套项目体系。项目定位核心解决什么问题Spring Framework基础框架IoC 容器、AOP、事务管理、模块化Spring Boot快速开发框架自动化配置、内嵌服务器、起步依赖让应用直接跑起来Spring MVCWeb 模块基于 MVC 设计模式的请求处理、参数绑定、视图渲染Spring Cloud微服务套件服务发现、配置中心、网关、负载均衡、熔断Spring Security安全认证身份认证、权限授权、CSRF 防护、OAuth2 等Spring Data数据访问统一多种数据库操作模式JPA、Redis、MongoDB 等Spring AIAI 应用开发统一接入大模型、向量库、Agent 工作流的基础设施这些项目不是平级的。Spring Framework 是最底层的基石Spring Boot 建立在它上面把繁琐配置自动化。Spring Cloud 又依赖 Spring Boot 提供的基础去解决分布式场景下的协作问题。Spring AI 是这两年新扩充的板块解决 Java 应用接大模型时的重复劳动。在实际项目里大多数单体业务系统用到的组合就是 Spring Boot Spring MVC Spring Security Spring Data/MyBatis。只有到了微服务规模才需要引入 Spring Cloud 那套组件。我见过不少团队一个只有几个接口的小系统硬要上 Nacos、Gateway、Sentinel 全套微服务结果运维成本远超收益。选型的第一步是认清边界。2.2 实际项目里最常见的组合Spring Boot MyBatis 够不够用回到我经常被问的问题“一个商城系统、就业推荐系统这种项目用 Spring Boot MyBatis 够了没有”答案是非常够。比如网上流传的仿天猫购物系统、多商户跨境商城源码核心模块无非是登录注册、用户管理、商品、订单、购物车、支付对接。这些场景用 Spring Boot 做接口层MyBatis 做持久化再配 MySQL 和 Redis已经能跑得很顺。一个典型分层大概是controller/ 接收请求、参数校验、返回结果 service/ 业务逻辑、事务控制 mapper/ 数据库访问MyBatis 接口 entity/ 数据实体 config/ 配置类Redis、拦截器、跨域等 common/ 统一返回结果、异常处理、工具类为什么 Spring Boot MyBatis 组合在中文技术社区里一直很受欢迎因为 MyBatis 把 SQL 写在 XML 里对复杂查询、多表关联、动态 SQL 的支持非常直白排查问题也好定位。Spring Boot 则负责把环境搭建的成本压到最低一个依赖加一个启动类项目就能跑起来。像大学生就业推荐系统这类毕设级项目还需要加一个推荐算法的模块和 Spring 本身的关系不大Spring 负责把数据拿给你、把你算好的结果存回去。这个组合真正容易踩坑的地方在于事务。Service 方法加Transactional很多人以为只要加了就万事大吉结果事务没生效一查是方法内部 this 调用把代理绕过了。这类问题我在后面讲 AOP 的时候还会再提。2.3 Spring AI接入大模型生态版图又扩了一块Spring AI 不是某个小插件而是 Sping 官方对“AI 应用开发”这件事给出的整体方案。它做的事情很像 JDBC 之于数据库你不需要关心底层是 MySQL 还是 PostgreSQL只要用统一 API 操作。Spring AI 就是你接入大模型时的统一入口。比如大家都关心的“连接通义千问这类开源大模型”。以前你要自己去写 HTTP 调用、处理鉴权、管理对话上下文麻烦且容易出错。有了 Spring AI配置里指定模型提供方、API Key、模型名称然后用一个 ChatClient 就能发起对话。看个意思spring: ai: model: chat: options: model: qwen-plus再配合 Spring AI 的 ChatMemory、向量存储、Prompt Template可以做记忆型对话、知识库问答、工作流编排。现在 GitHub 上有人把 Dify 这类低代码平台里画好的工作流转成 Spring AI 的 Java 代码本质就是承认 Spring AI 正在成为 Java 世界做 AI 应用的标准壳。作为一个老 Spring 开发者我的看法是不用急着追每个新版本但 Spring AI 值得你花一个周末跑个 Demo。Java 在 AI 应用层一直被认为“没存在感”Spring AI 至少把工程化的那摊事接住了。它不会让 Python 生态里的模型训练、数据分析能力转移到 Java但你如果在 Java 团队里做业务集成它就是最顺手的那把扳手。3. 源码与核心原理三级缓存、AOP 日志、自定义校验怎么串起来3.1 三级缓存为什么默认只解决“单例 setter 注入”循环依赖很多面试者能把三层缓存的三个 Map 名字背出来singletonObjects一级、earlySingletonObjects二级、singletonFactories三级。但问到“为什么必须三级而不是两级”不少人就只丢一句“为了代理”。我们推演一下两级行不行。假设 A 和 B 循环依赖A 正在创建要注入 BB 创建时又要注入 A。如果没有三级缓存容器在 A 还没初始化完的时候提供不了 A循环就断了。Spring 的做法是A 实例化后就把一个 ObjectFactory 放进三级缓存。这个工厂的核心逻辑是protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) { // 如果有需要提前代理的 BeanPostProcessor就在这里生成代理 exposedObject applyBeanPostProcessorsBeforeInstantiation(beanName, mbd, bean); } return exposedObject; }看到没三级缓存里存的不是一个 Bean而是一个“工厂”等到别的 Bean 真正需要提前引用 A 的时候才通过这个工厂去生成对象。如果是二级缓存那只能存一个固定对象——要么是原始对象要么是代理对象。但问题来了如果直接存原始对象等 A 初始化到底时AOP 代理才生成B 里面拿到的引用还是原始对象代理失效如果直接存代理对象可此时 A 的依赖还没填充完代理也不能提前正确生成。所以必须有一层延迟机制让系统在“别人真的需要引用”这一刻再决定返回什么。这就是三级缓存的本质把“创建时机”和“使用时机”解耦。不过这机制有明显边界。构造器注入的循环依赖救不了因为实例化阶段都过不去prototype 作用域也不管因为本来就不缓存原型对象。实际开发中真遇到构造器循环依赖最干净的做法还是重构把双向依赖拆开别指望容器帮你兜底。3.2 Spring AOP 实现日志记录以及怎么确认切面真的生效AOP 是 Spring 的另一根顶梁柱。面试题常问“怎么用 Spring AOP 实现日志记录”大部分人都能写出一个 Aspect 类。真正难的是工程里排查“切面为什么没生效”。先看一个标准写法Aspect Component public class LogAspect { Around(execution(* com.example.service.*.*(..))) public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { String method joinPoint.getSignature().getDeclaringTypeName() . joinPoint.getSignature().getName(); long start System.currentTimeMillis(); try { Object result joinPoint.proceed(); return result; } catch (Throwable t) { throw t; } finally { System.out.println(method 耗时 (System.currentTimeMillis() - start) ms); } } }注意几个前提切面试图作用的 Bean 必须由 Spring 容器管理。Spring Boot 2.x 之后 AOP 默认开启但需要引入spring-boot-starter-aop。被调用的方法不能是内部 this 调用必须通过代理对象调用否则切面会被跳过。最后一条是重灾区。同一个类里this.foo()调用bar()bar 上的 Around 不会触发因为 this 指向原始对象而不是代理对象。解决办法是拆类或者注入自身代理。实际项目中我排查过太多“日志没打出来”的问题最终都是 this 调用造成的。那“怎么查看 Spring AOP 有没有启用”呢最好的办法不是看配置而是看运行时对象。你可以在启动类里加一段Bean public CommandLineRunner check(ApplicationContext ctx) { return args - { Object proxy ctx.getBean(orderService); System.out.println(proxy.getClass()); }; }如果类名里带$$EnhancerBySpringCGLIB或者jdk.proxy说明代理已经生效。如果没有说明切面根本没切入这个 Bean。另外Spring 2.x 还支持用日志级别看 AOP 的自动配置报告把logging.level.org.springframework.boot.autoconfigure.aopdebug打开能看到是否自动装配了 AopAutoConfiguration。3.3 Spring 自定义 Validate从 Valid 到业务规则校验Spring 的校验体系看起来简单用着用着就会发现默认能力不够。参数对象上加NotNull、Size请求进来后 Controller 加个Valid框架帮你做基础校验。但业务上经常有这种规则订单金额必须大于 0 且小于用户额度手机号格式需要符合当前地区规则某个字段在 A 场景必填、在 B 场景选填。这些是标准注解表达不了的。自定义 Validate 有两条路。一条是写一个类实现Validator接口手动绑定到 WebDataBinder另一条更业务化是自定义注解配合校验器比如Target({ElementType.FIELD, ElementType.PARAMETER}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy AmountLimitValidator.class) public interface AmountLimit { String message() default 金额超出限制; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; }校验器里可以注入 Spring 管理的服务做数据库校验、额度判断。这是我推荐的方式因为它把校验逻辑留在能拿到业务上下文的地方而不是在 Controller 里堆 if 判断。这里我想提醒一句自定义校验最容易被忽略的是分组校验groups。同一个 DTO 在新增时要求字段必填在修改时允许为空这时候给注解指定不同的 groupController 方法上用Validated(CreateGroup.class)就不用拆开写两个类。很多团队不知道这个用法结果就用“建两个 DTO”这种笨办法硬扛代码越写越多。4. 真实业务系统的模块边界商城项目、就业推荐、第三方接口往哪放4.1 单体优先仿天猫商城与就业推荐系统的分层切法你们可能在 GitHub 上看到过不少“基于 Spring Vue 的仿天猫购物系统”“基于 Spring Boot 的大学生就业推荐系统”这类项目都有一个共同点单体应用足够用了。仿天猫系统要处理登录注册、用户管理、商品浏览、购物车、订单核心玩法是数据库表之间的关系。大学生就业推荐系统更是典型的 CRUD 加一个推荐策略模块再多撑死十几个。我做过几次项目结构评审发现初学者最容易犯的错是“为了模块化而模块化”。一个单体项目里硬塞多个 Maven Module每个 Module 只有两个类结果改一个需求要横跨三个子工程。模块化的意义是为了隔离和复用不是为了好看。单体项目最合适的切法是按业务域分包不是按技术分层包。比如com.example.mall/ controller/ // 登录注册、商品、购物车、订单 service/ mapper/ domain/ common/横向分层的包名大家都能背但真正起作用的边界在 Service 层。比如订单创建的时候要扣库存、发优惠券、写积分这些逻辑不应该散落在 Controller 里而应该在一个createOrder事务里编排。这才是模块边界的真正意义把“谁调用谁”的关系理清楚而不是把包名写得好看。4.2 给第三方开放的接口必须独立出来吗“Spring Boot 对外提供的接口应该放在哪里是单独服务还是放在业务模块”这是我看到提问频率非常高的一个点。答案取决于使用场景但有一个原则对外接口和内部接口不要混在同一条语义链里。为什么第三方接口的特性完全不同。第一它要做独立的鉴权体系不能跟你登录态共用一套 Session第二它需要独立限流防止外部调用把内部业务冲垮第三它要有版本控制第三方客户端升级永远比你的迭代慢第四它的入参、出参格式往往要兼容历史版本比内部接口保守得多。业务规模不大时我的建议是不要一开始就拆成另一个微服务但一定要拆成独立包com.example.mall/ controller/ // 对内接口 openapi/ // 对外接口 controller/ dto/ auth/凡是进入 openapi 的接口统一用单独的拦截器做签名校验、独立权限注解。等外部调用方变多、流量变大再把这个包平滑迁出去变成独立服务。这样既避免过度设计又留了分隔线。另外对外接口的文档别懒得写。你不可能让每个对接方都来问你“这个字段是什么意思”一套清楚的 OpenAPI 文档能省掉你一个月的答疑时间。4.3 Spring Boot Admin 能帮我们监控哪些需求和功能“Spring Boot 实现监控都有哪些需求和功能”这是一个典型的工程问题。Spring Boot Actuator 提供了最基础的监控端点health、info、metrics、loggers、env 等。Spring Boot Admin 把这些端点收拢成一个可视化后台直接看到每个应用实例的状态。我对接监控需求时一般会梳理成四层健康检查应用活着没、数据库连接在不在、Redis 通不通。这个做不了假得靠 Actuator 的自定义 HealthIndicator。运行时信息JVM 内存、CPU、线程数、HTTP 请求的 QPS 和响应时间。日志调整线上某个服务报错临时需要把某个类的日志从 INFO 调到 DEBUG又不想重启——Spring Boot Admin 的动态日志级别就可以直接改。告警通知应用挂掉、内存飙升、接口超时通过邮件或企业微信推送。核心代码就两步引入spring-boot-admin-starter-server和客户端依赖再在配置里指定 admin 地址。但这里有个功课要做Actuator 端点默认暴露得有限你需要自己决定哪些端点允许外部访问像/env、/heapdump这些敏感端点生产环境默认就应该关掉或加权限。监控的本质是拿信息换稳定性但别把“门锁”拆了。5. 日常开发最容易卡住的操作IDEA 社区版、VSCode、端口与启动项5.1 IntelliJ IDEA 社区版没有 Spring Initializr怎么创建项目“IDEA 为什么创建不了 Spring”这个问题几乎每个月都有人问。原因很简单IntelliJ IDEA 社区版是免费的功能阉割掉了 Spring Initializr所以界面里没有 Spring Boot 项目创建向导。商业版 Ultra/Ultimate 才有。不是没有替代方案。最可靠的方式是去官网生成项目用浏览器打开 start.spring.io选构建工具Maven/Gradle、Spring Boot 版本、依赖然后生成一个 zip 包解压后用 IDEA 打开即可。这个流程甚至比内置向导更灵活因为你能看到每个依赖对应什么选择版本也一目了然。还可以用命令行curl https://start.spring.io/starter.zip \ -d dependenciesweb,data-jpa,validation \ -d namedemo \ -d packageNamecom.example.demo \ -o demo.zip生成后一样解压导入。之后再遇到“创建不了”的问题别去纠结 IDE 功能它本质就是一个代码编辑器加调试器项目的脚手架生成可以完全脱离它。5.2 修改端口号、上下文路径这些基础操作Spring Boot 修改端口是最简单的操作之一但你得知道去哪儿改。默认启动端口 8080如果你想改成 8090在application.yml里写server: port: 8090 servlet: context-path: /api加了context-path之后所有接口的访问前缀都会变成/api/...。命令行启动时也能覆盖java -jar demo.jar --server.port8090命令行参数的优先级高于配置文件这在部署环境特别有用开发、测试、生产用同一份 jar端口和配置中心地址由启动脚本注入不需要为每个环境单独维护一套配置。这里有个小坑如果你用了 Redis 或数据库连接池端口改了防火墙和安全组不记得放行应用可能能启动但外部访问不了。排查这种问题的时候先确认端口是不是监听在 0.0.0.0 还是 127.0.0.1很多时候不是 Spring 的问题是服务器网络策略的问题。5.3 VSCode 下开发 Spring Boot 的体验顺带聊聊 FastAPI 的选型用 VSCode 写 Spring Boot 不是主流但确实有呼声因为很多前端同学装了 VSCode 就不想再装一个 IDEA。VSCode 里开发 Spring Boot 需要装两个扩展Extension Pack for Java以及 Spring Boot Extension Pack。前者提供 Java 语言服务、调试、Maven 支持后者给 Spring Boot 项目专属支持能直接识别启动类一键运行和调试。体感如何写代码、补全、调试这些基本能力都够用但跟 IDEA 比还是有差距尤其在重构、查找引用、代码导航这方面。VSCode 的定位是轻量编辑器适合改 Bug、写小项目、临时看代码。你要是每天超过六个小时在写 Java我还是建议用 IDEA 商业版或者免费但功能稍全的 Eclipse效率差距是实打实的。顺便说一个被问烂的话题Spring Boot 3 和 Python FastAPI 选哪个。这俩表面上是“Java 和 Python 谁快”其实是两套哲学。Spring Boot 的强项是工程体系类型安全、事务管理、丰富生态、企业级基础设施。FastAPI 的强项是快速原型、异步 IO 性能、Python 数据生态。异步 IO 在小并发下有优势但到了高并发、复杂业务链路Java 的 JIT 和成熟调优经验反而更稳。技术选型不是比谁写起来短而是比谁能扛住三年后的维护成本。6. 一条务实的 Spring 学习路线视频、源码、手写框架、面试题6.1 视频和实战项目怎么搭配才不会学了就忘现在网络上关于 Spring 的实践视频非常多有从零搭建商城系统的有把 Spring Cloud 组件挨个演示的。我的建议是视频只看你看不太懂的部分别从头到尾刷十个小时那叫追剧不叫学习。一个合理的节奏是先跑通 Spring Boot 的 Hello World然后照着官方文档把一个带数据库的 CRUD 写出来。遇到概念卡住比如 IoC、AOP再去找对应的视频片段。视频的价值在于“把抽象过程可视化”比如有人用画图演示三级缓存你看一遍就懂。但看懂了不等于会了必须有一个自己的项目在路上。不要一上来就选商城、秒杀这种大项目。你真正需要的是一个从登录到数据展示的小系统比如待办事项管理。它有用户输入、数据库映射、接口输出足够覆盖 Spring Boot 的日常操作。6.2 手写一个迷你 Spring抵过刷十套教程这句话我说了很多遍但每次都值得重复想彻底搞懂 Spring下载一个轻量级的心智模型手写一版迷你 Spring。这个练习不是让你在生产环境上自己造轮子而是为了让你对注解、反射、Bean 生命周期这些概念“脱敏”。迷你 Spring 至少要实现这几步用反射扫描一个包下带MyComponent注解的类。把实例存进 ConcurrentHashMap。发现有MyAutowired字段时从容器取依赖并注入。支持简单的单例和原型作用域。这一步做完你对 IoC/DI 就形成了肌肉记忆。接下来再看 Spring 的源码你就不会被各种抽象挡住你知道 BeanFactory 就是“造 Bean 的东西”ApplicationContext 就是加了更多集成的 BeanFactoryAOP 就是 BeanPostProcessor 里偷偷换了一个代理对象。6.3 面试答题的底层思路把“背概念”换成“讲过程”Spring 高级面试题翻来覆去就是那几个三级缓存、AOP 代理、事务失效场景、Spring Security 认证流程、Bean 生命周期。很多人背答案背得很熟一被追问就露馅。原因在于他把“知道结论”当成了“理解逻辑”。面试官想听的不是“三级缓存有三个 Map”而是你能不能讲清楚Bean 在实例化的时候发生了什么另一处依赖发生时拿到的对象是什么为什么代理和原始对象不能替换。同样问 Spring Security不是让你背 Filter 链条名称而是让你画一遍“请求从进来到返回”的完整流程过滤器、AuthenticationManager、ProviderManager、UserDetailsService、JWT 生成、再往后的权限拦截。我的训练方法是不看笔记把一个问题当成在跟同事讲课用五分钟讲明白“为什么这么设计”。如果能讲得让对方听懂了这题你才算真会。最后再分享一个我记了很多年的经验Spring 的问题永远不要只看表面。遇到 Bean 没注入、切面没生效、事务没回滚别急着改配置先想一个问题——我操作的这个对象到底是容器里的代理对象还是某个地方 new 出来的裸对象想清楚了这个Spring 一半的难题都会迎刃而解。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑