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 项目,并规划出如下的目录结构:

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() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
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 配置文件中,bean
的 id
必须是唯一的,它就像是对象的身份证号。如果重复,容器在启动时就会抛出异常。
很好。那 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-core
和 log4j-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 应用,并为其配备了专业的日志系统。这是后续所有学习的坚实基础。