- 1WEB 基础HTTP / Browser / WebServer静态页面写死,人人看到一样
- 2Servlet手写 if-else 分发不可扩展,无生命周期/并发管理
- 3JSP:参数散字符串参数散成一堆 String,取值靠字段名硬记
- 3JSP:scriptlet 塞逻辑scriptlet 把逻辑塞进页面,代码与 HTML 糊在一起
- 3JavaBean:数据对象化对象全靠 new、层层硬依赖
- 4Spring IoCWeb 路由仍手写 web.xml/if-else、手动取参绑定,展示/业务/数据访问没分开
- 5MVC · SpringMVC数据在库里,访问是重复样板 + 拼串有注入风险
- 6MySQLJava 连库要手写五步,连接与资源管理全手动
- 7JDBCSQL 与 Java 混写、结果集逐行手映射、没有动态 SQL
- 8MyBatis四份配置 + web.xml 装配繁琐,部署要外部 Tomcat
- 9SpringBoot链尾 · 不考核
Spring 框架(IoC/DI)
new、层层硬依赖。本章把「谁创建谁、谁依赖谁」交给容器——Spring IoC/DI。Spring 框架(IoC/DI)
目标:理解 IoC 控制反转与 DI 依赖注入,掌握 XML 与注解两种 Bean 配置方式,能看懂容器如何创建、持有并装配一个 bean。
本模块从「手写
new」的痛点切入:对象之间层层互相 new,改一处牵一片。Spring 把「谁创建谁、谁依赖谁」交给容器——这就是 IoC/DI,也是后面 SpringMVC、SSM 分层的底座。
一、为什么需要 IoC:new 的痛点
传统写法里,A 要 B 就 B b = new B();;B 要 C 又 new C()……对象之间硬编码依赖:改一个实现就要改所有引用它的地方,测试时也没法换个假实现。
IoC(Inversion of Control,控制反转):把对象的创建与依赖管理从业务代码里拿出来,交给外部容器。业务代码只声明「我需要一个 Guestbook」,具体是哪个、何时创建由容器决定——依赖关系被「反转」了。
底层支撑:容器靠反射(读类信息、调构造器/setter)+ 注解(标记谁是 bean、谁要注入什么)自动装配——这两块见
参考素材/JAVA反射/、JAVA注解/。
二、IoC 容器:创建 → 持有 → 注入
容器做三件事:读定义 → 实例化 → 装配依赖。下面交互实验把「容器」黑盒拆开,逐步看它如何把 guestBookContext.xml 里的 gb1 / gb2 / z 三个 bean 建出来并接好线(对照真实工程 GuestBookSpring/GuestbookApp.java):
交互实验:IoC 容器如何创建并装配 Bean(IoCContainerSim)
业务代码里**没有一行 `new`**——对象由外部容器创建、持有、装配。点「下一步」,看容器按 「解析定义 → 实例化 → 依赖装配 → 就绪」四步把 guestBookContext.xml 里的 gb1 / gb2 / z 三个 bean 建出来并互相接好线(对照真实工程 GuestBookSpring/GuestbookApp.java)。
解析定义 · gb1 —— 读取 <bean id="gb1" class="com.un.Guestbook">
登记 bean 定义(id、类名),尚未创建对象
容器注册表(已创建的对象)
| bean id | 类 | 状态 | 已注入属性 |
|---|---|---|---|
| gb1 | Guestbook | 未创建 | — |
| gb2 | Guestbook | 未创建 | — |
| z | User | 未创建 | — |
关键点:业务代码里没有一行
new。GuestbookApp只做两件事——new ClassPathXmlApplicationContext("guestBookContext.xml")启动容器(启动即饿加载全部单例),再ctx.getBean("z")取用。取到的 User 里 gbset/gblist 已接好 gb1、gb2。
三、XML 配置:<bean> + <property>/<ref>
在 XML 里,一个 <bean> 声明「id → 类」,<property> 负责装配(对照 GuestBookSpring/src/guestBookContext.xml):
<bean id="gb1" class="com.un.Guestbook">
<property name="title" value="第一条留言"></property>
<property name="contents" value="第一条留言内容"></property>
</bean>
<bean id="z" class="com.un.User">
<property name="gbset">
<set><ref bean="gb1"/> <ref bean="gb2"/></set>
</property>
</bean>
<property name value>:setter 注入字面量——容器调setTitle("第一条留言");<property name><ref bean/>:依赖注入——把已创建的 bean 引用填进属性(不是字符串);集合属性用<set>/<list>包多个<ref>。
四、注解配置:@Component / @Autowired / @Value
同样的装配也能写成注解。下面交互实验把「XML 版 vs 注解版」并排对照,点任意一行看等价关系:
交互实验:同一个 Bean,XML 版 vs 注解版(BeanWiringLab)
下面两栏是**同一个 User bean** 的两种登记方式。点下面的对照表任意一行,看它「XML 怎么写、注解怎么写、等价在哪」。
XML 配置
<bean id="user" class="com.un.User">
<property name="name" value="zhang"/>
<property name="gb"><ref bean="gb1"/></property>
</bean>注解配置
@Service
public class User {
@Value("zhang")
private String name;
@Autowired
private Guestbook gb;
}逐条等价关系
| XML 写法 | 注解等价写法 | 说明 |
|---|---|---|
| <bean id="user" class="com.un.User"> | @Service public class User { ... } // + 组件扫描 <context:component-scan> | 登记一个 bean:XML 用 <bean> 声明 id 与类;注解用类上的 @Component/@Service 等 + 组件扫描自动发现 |
| <property name="name" value="zhang"/> | @Value("zhang") private String name; | 注入字面量值(基本类型/字符串):XML 用 <property value>,注解用 @Value |
| <property name="gb"><ref bean="gb1"/></property> | @Autowired private Guestbook gb; // 同类型多个时:@Qualifier("gb1") | 注入另一个 bean(依赖注入核心):XML 用 <ref bean>,注解用 @Autowired 按类型匹配(多个同类型加 @Qualifier 指定名字) |
@Component 四兄弟(分层语义)
@Component
通用组件(兜底,不特指某一层)
@Service
业务层 Service
@Repository
持久层 DAO / Mapper
@Controller
Web 层 Controller
XML 与注解只是「把装配规则写给容器看」的两种记法,最终交给容器做的事完全一致:创建实例 → 填属性 → 注入依赖 → 就绪可取。工程里常混用:容器/数据源等基础设施偏 XML,业务类偏注解。
@Component 四兄弟按分层语义区分:
@Service(业务层)/@Repository(持久层)/@Controller(Web 层)/@Component(通用兜底)——本质都是注册一个 bean。
五、Bean 生命周期:五阶段
容器管理一个单例 bean 的全过程:实例化 → 属性赋值 → 初始化 → 可用 → 销毁,每阶段有对应回调:
交互实验:Bean 生命周期五阶段(BeanLifecycleFlow)
点「下一步」,看容器如何把一个单例 bean 从「空实例」一路带到「可用」再到「销毁」。注意每一步触发的回调。
实例化 —— 触发 无参构造 new User()
容器用无参构造创建空实例,此时属性都还是 null
生命周期轨迹
- 1. 实例化 —— 无参构造 new User():容器用无参构造创建空实例,此时属性都还是 null
Servlet 是「单例多线程」:service() 被每次请求反复调用;Bean 是「建好就一直用」——实例化/初始化各只一次,之后长期可用直到容器关闭。理解这一点,才能分清「框架托管对象」与「Web 组件对象」。
与 Servlet 的区别:Servlet 的
service()被每次请求反复调用;Bean 是「建好就一直用」——实例化/初始化各只一次,之后长期可用直到容器关闭。
六、BeanFactory vs ApplicationContext
- BeanFactory:最基础的容器接口,bean 按需懒加载(第一次 getBean 才创建);
- ApplicationContext:BeanFactory 的超集——启动时即饿加载全部单例(如
ClassPathXmlApplicationContext),并整合事件发布、国际化、资源加载等。实际项目几乎都用 ApplicationContext。
思考题
- 「控制反转」到底「反转」了什么?它解决了手写
new的什么问题? - XML 的
<property value>与<property><ref bean/>分别对应哪种注入?注解里又各对应什么? @Autowired按什么匹配依赖?同类型有多个 bean 时怎么办?- BeanFactory 与 ApplicationContext 在「何时创建单例」上有什么不同?
- 为什么 Spring bean 的生命周期没有「每次请求都调用一次」的阶段,而 Servlet 有?
学习小测验
学习小测验
IoC(控制反转)的含义是( )
Spring XML 里 <property name="title" value="第一条留言"/> 的作用是( )
注解配置中 @Autowired 的作用是( )
关于 @Component 四兄弟,正确的是( )
BeanFactory 与 ApplicationContext 的区别,正确的是( )
Spring bean 生命周期的正确顺序是( )
XML 里 <property name="gb"><ref bean="gb1"/></property> 表示( )
注解配置中 @Value("zhang") 的作用是( )