3. [奠基实践] 第一个 Spring 程序

3. [奠基实践] 第一个 Spring 程序

摘要: 本章我们将使用 Apache Maven 从零搭建一个经典的 Spring 项目。我们将掌握 Spring 依赖的引入、核心配置文件的编写,并最终从 IoC 容器中获取第一个由 Spring 管理的 Bean 对象。

学习指引: 本章将涉及大量 XML 配置和手动创建容器的代码。请务必注意,这在现代 Spring Boot 开发中已 几乎完全被自动化配置和注解所取代。学习本章的目的是为了理解 Spring IoC 的底层工作原理,无需死记硬背 XML 语法


3.1. 项目搭建与依赖配置

我们将使用 Maven 来构建我们的第一个 Spring 项目。

3.1.1. 创建标准 Maven 项目

首先,我们使用 IDEA 创建一个标准的 Maven 项目,并规划出如下的目录结构:

image-20250809205228154

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
. 📂 spring6
├── 📄 pom.xml
└── 📂 src/
│ └── 📂 main/
│ └── 📂 java/
│ └── 📂 com/
│ └── 📂 example/
│ ├── 📄 Main.java
│ └── 📂 spring6/
│ └── 📂 bean/
│ ├── 📄 User.java

│ └── 📂 resources/
│ ├── 📄 beans.xml
│ └── 📂 test/
│ └── 📂 java/
│ └── 📂 com/
│ └── 📂 example/
│ └── 📂 spring6/
│ └── 📂 test/
│ ├── 📄 Spring6Test.java
└── 📂 target/

3.1.2. 引入核心依赖

接下来,我们在 pom.xml 文件中声明项目所需的依赖。对于基础 IoC 应用,我们仅需引入 spring-context。同时,我们使用业界标准的 JUnit 5 作为测试框架。

文件路径: spring6/pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>spring6</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.9</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>

SpringBoot 简化: Spring Boot 通过 “Starters” 机制(如 spring-boot-starter)极大地简化了依赖管理,我们无需再逐个添加这些基础依赖。


3.2. 编写第一个 Spring 程序

环境就绪后,我们通过三步来完成第一个 Spring 程序的编写。

3.2.1. 定义一个简单的 Bean

Bean 是 Spring IoC 容器管理的基本单元,本质上就是一个 POJO。

文件路径: src/main/java/com/example/spring6/bean/User.java

1
2
3
4
5
6
7
package com.example.spring6.bean;

public class User {
public User() {
System.out.println("User 的无参数构造方法执行。");
}
}

3.2.2. 创建 Spring 核心配置文件

我们需要一个 XML 配置文件来告诉 Spring 容器需要管理哪些 Bean。

文件路径: src/main/resources/beans.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="userBean" class="com.example.spring6.bean.User"/>

</beans>

SpringBoot 简化: 在 Spring Boot 中,我们几乎不再使用 XML 文件。通过在 User 类上添加 @Component 注解,它就会被自动扫描并注册为 Bean。

3.2.3. 编写单元测试获取 Bean

最后,我们编写一个 JUnit 5 测试用例来手动启动 Spring 容器,并从中获取 User 对象。

文件路径: com\example\spring6\test\Spring6Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.example.spring6.test;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Spring6Test {

@Test
public void testFirst() {
// 1. 手动创建 Spring 容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

// 2. 从容器中获取 Bean
Object userBean = applicationContext.getBean("userBean");
System.out.println(userBean);
}
}

SpringBoot 简化: Spring Boot 应用的启动入口是 main 方法中的 SpringApplication.run(),它会自动创建并初始化容器,我们无需手动 new 一个 ApplicationContext


3.3. IoC 工作机制深度剖析

第一个程序成功运行的背后,隐藏着 Spring IoC 容器的许多核心工作机制。我们以面试问答的形式,来深入剖析这些细节。

面试官深度追问
今天 下午 2:30

刚才的程序跑通了,我们来深挖一下。<bean> 标签的 id 属性可以重复吗?

学习者

不可以。在同一个 Spring 配置文件中,beanid 必须是唯一的,它就像是对象的身份证号。如果重复,容器在启动时就会抛出异常。

很好。那 Spring 底层是如何创建 User 对象的?是不是必须要有无参数构造方法?

学习者

是的。Spring IoC 容器本质上是通过 Java 的反射机制 来实例化对象的。它会获取 class 属性指定的类,然后调用该类的 无参构造函数 来创建实例。因此,被 Spring 管理的 Bean 必须提供一个无参数构造器。

原来如此。那 Spring 把这些创建好的 Bean 实例存放在哪里了呢?

学习者

Spring 容器内部会维护一个类似 Map<String, Object> 的数据结构,我们通常称之为“单例池” (Singleton Cache)。配置的每个 <bean> 都会被实例化成一个对象,并以其 id 为键 (key),以对象实例为值 (value) 存放在这个 Map 中。

getBean() 方法的返回值是 Object,如果我想直接调用 User 的方法,每次都要强转,有没有更便捷的方式?

学习者

有的。getBean() 方法有一个重载版本,可以传入一个 Class 类型的参数。像这样:User user = applicationContext.getBean("userBean", User.class); 这样获取到的直接就是指定类型的对象,无需手动强转。

不错。最后问一个,ApplicationContext 和它的父接口 BeanFactory 有什么区别?

学习者

BeanFactory 是 Spring IoC 容器的顶级接口,定义了获取 Bean 的最基本方法,是“Bean 工厂”的本质。而 ApplicationContext 是它的子接口,功能更强大。它除了继承 BeanFactory 的所有功能外,还额外提供了对国际化事件发布AOP 等企业级特性的支持。我们通常推荐使用 ApplicationContext


3.4. [推荐实践] 集成 Log4j2 日志框架

专业的应用程序离不开日志系统。从 Spring 5 开始,官方推荐使用 Log4j2 作为集成的日志框架。

3.4.1. 添加依赖

我们需要在 pom.xml 中添加 log4j-corelog4j-slf4j2-impl 两个依赖。

文件路径: spring6/pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<project>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.9</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.25.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.25.1</version>
</dependency>
</dependencies>
</project>

3.4.2. 添加配置文件

按照约定,我们需要在 src/main/resources 目录下创建一个名为 log4j2.xml 的配置文件。

文件路径: src/main/resources/log4j2.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<root level="DEBUG">
<appender-ref ref="spring6log"/>
</root>
</loggers>
<appenders>
<console name="spring6log" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
</configuration>

SpringBoot 简化: Spring Boot 的 spring-boot-starter-logging 提供了开箱即用的日志功能(默认 Logback),我们通常只需在 application.properties 中配置级别即可。

总结: 至此,我们已经完整地搭建了一个最小化的、基于 Maven 和 XML 配置的 Spring IoC 应用,并为其配备了专业的日志系统。这是后续所有学习的坚实基础。