JavaEE 环境与程序设计 · 交互课程
技术演进接力链(M1 → M9)
  1. 1WEB 基础
    HTTP / Browser / WebServer
    静态页面写死,人人看到一样
  2. 2Servlet
    手写 if-else 分发不可扩展,无生命周期/并发管理
  3. 3JSP:参数散字符串
    参数散成一堆 String,取值靠字段名硬记
  4. 3JSP:scriptlet 塞逻辑
    scriptlet 把逻辑塞进页面,代码与 HTML 糊在一起
  5. 3JavaBean:数据对象化
    对象全靠 new、层层硬依赖
  6. 4Spring IoC
    Web 路由仍手写 web.xml/if-else、手动取参绑定,展示/业务/数据访问没分开
  7. 5MVC · SpringMVC
    数据在库里,访问是重复样板 + 拼串有注入风险
  8. 6MySQL
    Java 连库要手写五步,连接与资源管理全手动
  9. 7JDBC
    SQL 与 Java 混写、结果集逐行手映射、没有动态 SQL
  10. 8MyBatis
    四份配置 + web.xml 装配繁琐,部署要外部 Tomcat
  11. 9SpringBoot
    链尾 · 不考核

Spring 框架(IoC/DI)

承接上一章的死结
上一章(JSP·JavaBean)的死结:对象全靠 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类状态已注入属性
gb1Guestbook未创建—
gb2Guestbook未创建—
zUser未创建—
第 1 / 13 步

关键点:业务代码里没有一行 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>

四、注解配置:@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. 1. 实例化 —— 无参构造 new User():容器用无参构造创建空实例,此时属性都还是 null

Servlet 是「单例多线程」:service() 被每次请求反复调用;Bean 是「建好就一直用」——实例化/初始化各只一次,之后长期可用直到容器关闭。理解这一点,才能分清「框架托管对象」与「Web 组件对象」。

与 Servlet 的区别:Servlet 的 service() 被每次请求反复调用;Bean 是「建好就一直用」——实例化/初始化各只一次,之后长期可用直到容器关闭。

六、BeanFactory vs ApplicationContext

思考题

  1. 「控制反转」到底「反转」了什么?它解决了手写 new 的什么问题?
  2. XML 的 <property value> 与 <property><ref bean/> 分别对应哪种注入?注解里又各对应什么?
  3. @Autowired 按什么匹配依赖?同类型有多个 bean 时怎么办?
  4. BeanFactory 与 ApplicationContext 在「何时创建单例」上有什么不同?
  5. 为什么 Spring bean 的生命周期没有「每次请求都调用一次」的阶段,而 Servlet 有?

学习小测验

学习小测验

Q1

IoC(控制反转)的含义是( )

Q2

Spring XML 里 <property name="title" value="第一条留言"/> 的作用是( )

Q3

注解配置中 @Autowired 的作用是( )

Q4

关于 @Component 四兄弟,正确的是( )

Q5

BeanFactory 与 ApplicationContext 的区别,正确的是( )

Q6

Spring bean 生命周期的正确顺序是( )

Q7

XML 里 <property name="gb"><ref bean="gb1"/></property> 表示( )

Q8

注解配置中 @Value("zhang") 的作用是( )

本章留下的死结
Web 路由仍手写 web.xml/if-else、手动取参绑定,展示/业务/数据访问没分开(下一章 SpringMVC)。