Spring 系列目录
| 篇目 | 标题 | 状态 |
|---|---|---|
第 1 篇 | Spring 入门与 Spring MVC 实战指南:从零理解 IoC 到 RESTful 图书管理 | 已发布 |
第 2 篇 | 告别 | 已发布 |
第 3 篇 | Spring 配置文件完全指南:从 XML 到 Spring Boot,一次搞懂(本文) | 当前阅读 |
第 4 篇 | Spring Boot 自动配置原理与多环境 Profile 配置详解 | 规划中 |
第 5 篇 | Spring 持久层实战:Spring Data JPA / MyBatis | 规划中 |
第 6 篇 | Spring Security:认证与授权入门 | 规划中 |
第 7 篇 | Spring Cloud 微服务入门(进阶) | 规划中 |
系列阅读建议:第 1 篇建立 Spring 全貌与 MVC 实战;第 2 篇吃透 IoC / DI / Bean;本文讲清 Bean 配置 与 应用属性配置 的分工与写法。建议按 1 → 2 → 3 顺序阅读,再进入自动配置与持久层。
文章目录
- 系列文章目录
- 引言
- 一、核心概念:容器、Bean、依赖注入与配置
- 1.1 用大白话理解
- 1.2 两种配置,别搞混
- 1.3 概念关系图
- 二、常见配置方式
- 2.1 XML配置(经典方式)
- 2.2 Java配置(现代推荐)
- 三、Spring Boot属性配置
- application.properties 示例
- application.yml 示例(等价写法)
- 在代码中读取配置
- 四、完整示例:通知服务小项目
- 4.1 项目结构
- 4.2 Java相关代码
- 4.3 容器启动与依赖注入流程
- 4.4 运行时的对象关系
- 4.5 配置方式对比
- 五、常见错误与排查建议
- 5.1 Bean 找不到:NoSuchBeanDefinitionException
- 5.2 循环依赖:BeanCurrentlyInCreationException
- 5.3 属性绑定失败:配置值为 null 或默认值
- 5.4 XML 与 Java 配置重复定义同一 Bean
- 5.5 混淆 Bean 配置与属性配置
- 六、总结
引言
我们写 Java 程序时,是不是经常遇到这样的问题:
- 创建对象时,总要自己`new`,改一处就要改很多地方
- 数据库连接地址写死在代码里,换环境就要重新编译
- 类之间的依赖关系越来越乱,测试时 mock 一个对象都费劲
Spring 配置文件就是来解决这些问题的。它告诉 Spring 容器:要创建哪些对象(Bean)、它们之间怎么配合、应用运行时需要哪些参数。
本文适合有 Java 基础、刚接触 Spring 的开发者。读完后,你应该能区分「Bean 配置」和「应用属性配置」,并独立完成基础配置。
提示:以下是本篇文章正文内容,下面案例可供参考
一、核心概念:容器、Bean、依赖注入与配置
1.1 用大白话理解:
想象Spring容器是一个智能工厂:
1.我们给它一张【生产清单】(配置文件)
2.它按清单生产零件(创建Bean)
3.它还会自动把零件组装成成品(依赖注入)
Bean就是Spring 容器管理的对象。普通`new`出来的对象,Spring不管;通过配置注册的对象,Spring管它的创建、组装和生命周期。
依赖注入(DI)的意思是:A 需要B,我们不用自己new B()塞给A,Spring会自动把B注入给A。
配置文件就是那张【生产清单】。它回答三个问题:
1.2 两种配置,别搞混
这是新手最容易踩的坑:
Bean配置管【对象怎么造】;属性配置管【对象用什么参数运行】。两者配合使用,但职责不同。
1.3 概念关系图
图说明:NotificationService依赖MessageSender接口,具体实现可以是EmailSender或SmsSender。Spring 容器负责创建这些对象,并把sender注入到NotificationService中。换实现类时,只改配置,不改业务代码。
二、常见配置方式
2.1 XML配置(经典方式)
XML配置是Spring最早的方式,把所有Bean定义写在一个`.xml`文件里。优点是一目了然;缺点是配置多了文件会很长。
适用场景:老项目维护、需要集中查看所有Bean定义、团队习惯XML。
2.示例项目结构
otification-demo/
├── src/main/java/com/example/notification/
│ ├── MessageSender.java # 接口
│ ├── EmailSender.java # 实现类
│ ├── NotificationService.java # 业务类
│ └── Application.java # 启动类
└── src/main/resources/
└── applicationContext.xml # XML 配置
Java代码
package org.example.notification.service; // MessageSender.java public interface MessageSender { void send(String message); }package org.example.notification.service.sender; import org.example.notification.service.MessageSender; import org.springframework.stereotype.Component; // EmailSender.java @Component public class EmailSender implements MessageSender { @Override public void send(String message) { System.out.println("[Email]" + message); } }package org.example.notification.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // NotificationService.java @Service public class NotificationService { // 字段注入(Spring Boot 组件扫描时使用) @Autowired private MessageSender sender; // Setter 注入(applicationContext.xml 的 <property> 需要) public void setSender(MessageSender sender) { this.sender = sender; } public void notifyUser(String content) { sender.send(content); } }package org.example.notification; import org.example.notification.service.NotificationService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; // Application.java @SpringBootApplication public class NotificationApplication { public static void main(String[] args) { SpringApplication.run(NotificationApplication.class, args); ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); NotificationService service = context.getBean("notificationService",NotificationService.class); service.notifyUser("您的订单已发货!"); } }XML文件
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 注册 EmailSender 为 Bean,id 就是 Bean 的名称 --> <bean id="emailSender" class="org.example.notification.service.sender.EmailSender"/> <!-- 注册 NotificationService,并通过 property 注入 emailSender --> <bean id="notificationService" class="org.example.notification.service.NotificationService"> <property name="sender" ref="emailSender"/> </bean> </beans>大白话解读:
- <bean id="emailSender" ...>:告诉容器,创建一个 EmailSender 对象,名字叫 emailSender
- <property name="sender" ref="emailSender"/>:创建 NotificationService 时,把名为 emailSender 的 Bean 塞给它的 sender 属性
这就是 字段注入。XML 里的 ref 指向另一个 Bean 的 id,容器会自动完成组装。
2.2 Java配置(现代推荐)
Java 配置用 @Configuration 和 @Bean 注解代替 XML。配置写在 Java 类里,有类型检查、IDE 自动补全,重构也更方便。
适用场景:新项目、需要复杂配置逻辑、团队偏好代码式配置。
配置类
package org.example.notification.config; import org.example.notification.service.MessageSender; import org.example.notification.service.NotificationService; import org.example.notification.service.sender.EmailSender; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration//标记这是一个配置类 public class AppConfig { @Bean//这个方法返回的对象会被注册为Bean public MessageSender emailSender(){ return new EmailSender(); } @Bean public NotificationService notificationService(MessageSender sender){ //方法参数sender 会自动注入容器中的MessageSender Bean NotificationService service = new NotificationService(); service.setSender(sender); return service; } }package org.example.notification; import org.example.notification.config.AppConfig; import org.example.notification.service.NotificationService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; // Application.java @SpringBootApplication public class NotificationApplication { public static void main(String[] args) { SpringApplication.run(NotificationApplication.class, args); //加载Java配置类,启动容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); NotificationService service = context.getBean(NotificationService.class); service.notifyUser("您的订单已发货!"); } }大白话解读:
- @Configuration:这个类是「配置说明书」
- @Bean:这个方法生产一个 Bean,方法名默认是 Bean 名称(如 emailSender)
- notificationService(MessageSender sender):Spring 看到参数类型,自动从容器里找一个 MessageSender 塞进来——这叫 方法注入
> 版本说明:Spring 4.3+ 起,如果 @Bean 方法只有一个构造器参数,也可以直接在 @Bean 方法里用构造器注入,写法更简洁。上面示例用 setter 是为了和 XML 版保持一致,便于对比。
三、Spring Boot属性配置
Spring Boot 大幅简化了Bean配置(自动配置+组件扫描),但应用运行参数仍然需要配置文件。
适用场景:几乎所有Spring Boot项目。
application.properties 示例
spring.application.name=notification # 服务端口 server.port=8080 自定义业务配置 notification.default-channel=email notification.retry-count=3application.yml 示例(等价写法)
server: port: 8080 notification: default-channel: email retry-count: 3在代码中读取配置
package org.example.notification.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "notification") public class NotificationProperties { private String defaultChannel = "email"; private int retryCount = 3; // getter/setter 省略 public String getDefaultChannel() { return defaultChannel; } public void setDefaultChannel(String defaultChannel) { this.defaultChannel = defaultChannel; } public int getRetryCount() { return retryCount; } public void setRetryCount(int retryCount) { this.retryCount = retryCount; } }package org.example.notification.service; import org.example.notification.config.NotificationProperties; import org.springframework.stereotype.Service; @Service public class NotificationService { private final MessageSender sender; private final NotificationProperties properties; // 构造器注入:Spring 自动传入容器中的 MessageSender 和 NotificationProperties public NotificationService(MessageSender sender, NotificationProperties properties) { this.sender = sender; this.properties = properties; } public void notifyUser(String content) { System.out.println("渠道:" + properties.getDefaultChannel() + ",重试:" + properties.getRetryCount()); sender.send(content); } }Spring Boot的Bean配置方式:
属性文件加载规则(SpringBoot 默认行为):
- 按优先级加载:application.properties / application.yml
- 支持多环境:application-dev.yml、application-prod.yml,通过 spring.profiles.active=dev 切换
- 默认位置:src/main/resources/,无需手动指定路径
四、完整示例:通知服务小项目
我们把上面的代码整合成一个可运行的Spring Boot项目。
4.1项目结构
notification/
├── pom.xml # Maven 构建配置
└── src/
├── main/
│ ├── java/org/example/notification/
│ │ ├── NotificationApplication.java # 启动类
│ │ ├── controller/
│ │ │ └── NotificationController.java # REST 接口层
│ │ ├── config/
│ │ │ ├── AppConfig.java # Java 配置(@Configuration + @Bean)
│ │ │ └── NotificationProperties.java # 应用属性绑定
│ │ ├── dto/
│ │ │ ├── SendNotificationRequest.java # 发送通知请求体
│ │ │ ├── SendNotificationResponse.java # 发送通知响应体
│ │ │ └── NotificationConfigResponse.java # 配置查询响应体
│ │ └── service/
│ │ ├── MessageSender.java # 消息发送接口
│ │ ├── NotificationService.java # 业务服务
│ │ └── sender/
│ │ └── EmailSender.java # 邮件发送实现
│ └── resources/
│ ├── application.yml # Spring Boot 属性配置
│ └── applicationContext.xml # XML Bean 配置(对比学习用)
└── test/
└── java/org/example/notification/
└── NotificationApplicationTests.java # 上下文加载测试
4.2 Java相关代码
启动类
package org.example.notification; import org.example.notification.service.NotificationService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class NotificationApplication { public static void main(String[] args) { SpringApplication.run(NotificationApplication.class, args); } }给 EmailSender 加上组件注解
package org.example.notification.service.sender; import org.example.notification.service.MessageSender; import org.springframework.stereotype.Component; // EmailSender.java @Component public class EmailSender implements MessageSender { @Override public void send(String message) { System.out.println("[Email]" + message); } }工具类
package org.example.notification.config; import org.example.notification.service.MessageSender; import org.example.notification.service.sender.EmailSender; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public MessageSender emailSender() { return new EmailSender(); } }package org.example.notification.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "notification") public class NotificationProperties { private String defaultChannel = "email"; private int retryCount = 3; // getter/setter 省略 public String getDefaultChannel() { return defaultChannel; } public void setDefaultChannel(String defaultChannel) { this.defaultChannel = defaultChannel; } public int getRetryCount() { return retryCount; } public void setRetryCount(int retryCount) { this.retryCount = retryCount; } }Service类
package org.example.notification.service; import org.example.notification.config.NotificationProperties; import org.springframework.stereotype.Service; @Service public class NotificationService { private final MessageSender sender; private final NotificationProperties properties; // 构造器注入:Spring 自动传入容器中的 MessageSender 和 NotificationProperties public NotificationService(MessageSender sender, NotificationProperties properties) { this.sender = sender; this.properties = properties; } public void notifyUser(String content) { System.out.println("渠道:" + properties.getDefaultChannel() + ",重试:" + properties.getRetryCount()); sender.send(content); } }package org.example.notification.service; // MessageSender.java public interface MessageSender { void send(String message); }DTO类
package org.example.notification.dto; public record NotificationConfigResponse( String defaultChannel, int retryCount ) { }package org.example.notification.dto; public record SendNotificationRequest(String content) { }package org.example.notification.dto; public record SendNotificationResponse( String message, String content, String channel, int retryCount ) { }package org.example.notification.controller; import org.example.notification.config.NotificationProperties; import org.example.notification.dto.NotificationConfigResponse; import org.example.notification.dto.SendNotificationRequest; import org.example.notification.dto.SendNotificationResponse; import org.example.notification.service.NotificationService; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/notifications") public class NotificationController { private final NotificationService notificationService; private final NotificationProperties properties; public NotificationController(NotificationService notificationService, NotificationProperties properties) { this.notificationService = notificationService; this.properties = properties; } /** 发送通知 — POST /api/notifications */ @PostMapping("/send) @ResponseStatus(HttpStatus.CREATED) public SendNotificationResponse send(@RequestBody SendNotificationRequest request) { notificationService.notifyUser(request.content()); return new SendNotificationResponse( "通知已发送", request.content(), properties.getDefaultChannel(), properties.getRetryCount() ); } /** 查看当前通知配置 — GET /api/notifications/config */ @GetMapping("/config") public NotificationConfigResponse getConfig() { return new NotificationConfigResponse( properties.getDefaultChannel(), properties.getRetryCount() ); } }配置文件
server: port: 8081 notification: default-channel: email retry-count: 3 spring: profiles: active: dev测试:
POST http://localhost:8081/api/notifications/send
-H "Content-Type: application/json" \
-d "{\"content\":\"您的订单已发货!\"}"
4.3 容器启动与依赖注入流程
4.4 运行时的对象关系
4.5 配置方式对比
实际建议
- Spring Boot 新项目:业务类用 @Service / @Component 扫描;第三方 Bean 用 @Configuration + @Bean;参数用 application.yml
- 传统 Spring 项目:优先 Java 配置,XML 仅用于维护遗留代码
- 不要混用过多方式:一个项目选主风格,避免同一 Bean 既在 XML 又在 Java 里定义
五、常见错误与排查建议
5.1 Bean 找不到:NoSuchBeanDefinitionException
原因:类没加 @Component / @Service,或不在组件扫描路径下。
排查:
- 确认类在 @SpringBootApplication 所在包或其子包下
- 或显式指定:@ComponentScan("com.example.notification")
2. 循环依赖:BeanCurrentlyInCreationException
原因:A 依赖 B,B 又依赖 A,容器无法决定先创建谁。
解决:改用构造器注入 + @Lazy,或重构设计打破循环。
3. 属性绑定失败:配置值为 null 或默认值
原因:
- 键名写错(default-channel vs defaultChannel)
- 缺少 getter/setter(@ConfigurationProperties 需要)
- 未启用:@EnableConfigurationProperties(NotificationProperties.class)
排查:启动时加 --debug 查看绑定日志。
4. XML 与 Java 配置重复定义同一 Bean
原因:同一 Bean 被 @Bean 和 <bean> 各定义一次。
解决:只保留一种方式,删除重复定义。
5. 混淆 Bean 配置与属性配置
错误示例:在 application.yml 里写 myService.class=com.example.MyService(这不是标准做法)。
正确做法:Bean 用注解或 @Configuration;yml 只写运行参数。
六、总结
Spring 配置文件解决的核心问题是:谁来创建对象、对象之间如何协作、应用用什么参数运行。
三个要点:
- Bean 配置(XML / Java / 注解扫描)管对象的创建和依赖注入
- 属性配置(application.properties/yml)管端口、数据库 URL 等业务参数
- Spring Boot 通过自动配置和组件扫描,让大部分 Bean 无需手写配置
动手建议:
- 用本文的 notification-demo 结构,分别尝试 XML 和 Java 两种 Bean 配置方式
- 在 application.yml 里改 notification.default-channel,观察输出变化
- 故意写错一个 Bean 名称或配置键,对照「常见错误」章节练习排查
搞懂配置文件,你就掌握了 Spring 的「组装说明书」。后面学 AOP、事务、Web 开发,都是在容器已经帮你把对象准备好之后,往上面加能力——地基打牢,后面才稳。