📚 前言
这是我之前写 项目时的一些理解和感悟, 我喊了AI帮我润色了一下语言文字,发出来了,希望对大家有用
在学习Spring框架时,经常会遇到@Configuration、@Bean、@Service、@Resource等注解,以及各种设计模式的应用。本文通过具体的代码示例(MailConfig和MailService),深入浅出地解释这些概念,帮助理解Spring的核心机制。
🎯 核心问题
问题1:为什么需要@Configuration和@Bean?
问题2:为什么没有注解的类也能被@Resource注入?
问题3:构造函数参数与配置类的关系?
问题4:涉及了哪些设计模式?
让我们逐一解答这些问题。
🏭 第一部分:Spring容器 = 大仓库
🤔 没有Spring的痛苦
假设你要在3个地方发邮件:用户注册、密码重置、系统通知。
没有Spring配置的代码:
java
// 第1个地方:用户注册
public void register() {
// 每次都要写这一堆配置!
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("smtp.qq.com");
sender.setPort(587);
sender.setUsername("myemail@qq.com");
sender.setPassword("mypassword");
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.timeout", "5000");
props.put("mail.smtp.connectiontimeout", "5000");
props.put("mail.smtp.writetimeout", "5000");
sender.setJavaMailProperties(props);
MailProperties mailProps = new MailProperties();
mailProps.setUsername("myemail@qq.com");
// ... 更多配置
MailService mailService = new MailService(sender, mailProps);
mailService.sendRegisterCode("user@email.com", "123456");
}
// 第2个地方:密码重置
public void resetPassword() {
// 又要写一遍同样的15行配置!!!
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("smtp.qq.com"); // 重复!
sender.setPort(587); // 重复!
sender.setUsername("myemail@qq.com"); // 重复!
// 又是15行重复配置...
MailService mailService = new MailService(sender, mailProps);
mailService.sendResetEmail("user@email.com");
}
// 第3个地方:系统通知
public void sendNotification() {
// 再写一遍同样的15行配置!!!
// 同样的重复代码...
}
问题有多严重?
- 重复代码:同样的15行配置要写3次、5次、10次...
- 修改困难:如果邮箱密码变了,要改10个地方!
- 容易出错:某个地方配置写错了,邮件就发不出去
- 浪费时间:每次写业务代码都要先写配置
✨ Spring的解决方案:仓库管理
Spring容器就像一个大仓库,统一管理所有对象。
java
@Configuration // "我是配置管理员"
public class MailConfig {
@Bean // "我来准备现成的MailService放到仓库"
public MailService mailService(JavaMailSender sender, MailProperties props) {
// 复杂配置我来写,你不用管
return new MailService(sender, props); // 现成的产品放入仓库
}
@Bean // "我来准备现成的JavaMailSender放到仓库"
public JavaMailSender javaMailSender() {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(env.getProperty("spring.mail.host"));
sender.setPort(Integer.parseInt(env.getProperty("spring.mail.port", "25")));
sender.setUsername(env.getProperty("spring.mail.username"));
sender.setPassword(env.getProperty("spring.mail.password"));
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.timeout", "5000");
sender.setJavaMailProperties(props);
return sender; // 现成的邮件发送器放入仓库
}
}
你的业务代码变得超简单:
java
// 第1个地方:用户注册
@Resource
private MailService mailService; // 从仓库提货
public void register() {
mailService.sendRegisterCode("user@email.com", "123456"); // 一行搞定!
}
// 第2个地方:密码重置
@Resource
private MailService mailService; // 还是从仓库提货
public void resetPassword() {
mailService.sendResetEmail("user@email.com"); // 一行搞定!
}
// 第3个地方:系统通知
@Resource
private MailService mailService; // 依然从仓库提货
public void sendNotification() {
mailService.sendNotification("通知内容"); // 一行搞定!
}
🏪 第二部分:Bean进入仓库的两种方式
📦 方式1:贴标签自动入库
java
@Service // ← 这就像给盒子贴了"请放入仓库"的标签
public class UserService {
public void doSomething() {
// 业务逻辑
}
}
@Controller // ← 另一种标签
public class UserController {
}
@Repository // ← 又一种标签
public class UserRepository {
}
工作流程:
Spring启动 → 扫描所有类 → 发现@Service标签 → 自动创建UserService → 放入仓库
🚚 方式2:手动搬运入库
java
// MailService没贴标签,Spring不会自动放入
public class MailService {
private final JavaMailSender javaMailSender;
private final MailProperties mailProperties;
public MailService(JavaMailSender javaMailSender, MailProperties mailProperties) {
this.javaMailSender = javaMailSender;
this.mailProperties = mailProperties;
}
}
@Configuration
public class MailConfig {
@Bean // ← 这里是"搬运工",手动把MailService放入仓库
public MailService mailService(JavaMailSender sender, MailProperties props) {
MailService service = new MailService(sender, props); // 手动创建
return service; // 手动放入仓库
}
}
工作流程:
Spring启动 → 发现MailConfig → 看到@Bean方法 → 调用这个方法 → 得到MailService → 放入仓库
🛍️ 从仓库取货
java
@Resource // "仓库管理员,给我一个MailService"
private MailService mailService; // Spring从仓库里拿给你
// Spring不关心这个东西是怎么进仓库的(贴标签还是手动搬运)
// 只要仓库里有,就能给你
关键理解:@Resource不看类有没有注解,只看仓库里有没有这个Bean!
🔗 第三部分:构造函数 = 需求清单
📋 MailService的需求清单
java
public class MailService {
private final JavaMailSender javaMailSender; // 需求1:我要一个邮件发送器
private final MailProperties mailProperties; // 需求2:我要一个配置信息
// 构造函数 = "需求清单"
public MailService(JavaMailSender javaMailSender, MailProperties mailProperties) {
this.javaMailSender = javaMailSender; // 收下需求1
this.mailProperties = mailProperties; // 收下需求2
}
public void sendRegisterCode(String to, String code) throws Exception {
// 使用构造函数收到的 javaMailSender 发邮件
MimeMessage message = javaMailSender.createMimeMessage();
// 使用构造函数收到的 mailProperties 获取配置
MimeMessageHelper helper = new MimeMessageHelper(message, false);
helper.setFrom(mailProperties.getUsername());
helper.setTo(to);
helper.setSubject("注册验证码通知");
String content = buildRegisterTemplate(code);
helper.setText(content, true);
javaMailSender.send(message); // 发送邮件
}
}
🏭 MailConfig = 材料供应商
java
@Configuration
public class MailConfig {
@Bean
public MailService mailService(JavaMailSender javaMailSender, MailProperties mailProperties) {
// ↑ 材料1: Spring自动提供 ↑ 材料2: Spring自动提供
// Spring调用这个方法时会自动传入这两个参数
return new MailService(javaMailSender, mailProperties);
// ↑ 把材料1给MailService ↑ 把材料2给MailService
}
}
🔄 完整的供需匹配过程
Step 1: MailService提出需求
java
public MailService(JavaMailSender javaMailSender, MailProperties mailProperties) {
// "要造我,必须给我这两样材料"
}
Step 2: Spring准备材料
java
// Spring自己准备或者其他地方准备好了:
JavaMailSender sender = ...; // 材料1准备好(可能是自动配置或其他@Bean提供)
MailProperties props = ...; // 材料2准备好(可能是配置文件或其他@Bean提供)
Step 3: MailConfig组装
java
@Bean
public MailService mailService(JavaMailSender sender, MailProperties props) {
// Spring: "我把准备好的材料传给你"
// MailConfig: "好的,我用这些材料造MailService"
return new MailService(sender, props); // 用材料造产品
}
Step 4: MailService收到材料开始工作
java
public void sendRegisterCode(String to, String code) {
// 使用构造函数收到的现成材料工作,不用自己创建
javaMailSender.send(message);
}
🎯 如果没有构造函数参数会怎样?
错误做法:MailService自己找材料
java
public class MailService {
public void sendRegisterCode(String to, String code) {
// 每次都要自己创建!很麻烦!很容易出错!
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("smtp.qq.com");
sender.setPort(587);
sender.setUsername("myemail@qq.com");
sender.setPassword("mypassword");
// ... 15行重复配置代码
sender.send(message);
}
}
正确做法:通过构造函数接收现成的
java
public class MailService {
private final JavaMailSender javaMailSender; // 别人给我准备好的
public MailService(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender; // 接收别人准备的
}
public void sendRegisterCode(String to, String code) {
javaMailSender.send(message); // 直接用,不用自己配置
}
}
🎨 第四部分:涉及的设计模式
1. 🏭 工厂模式(Factory Pattern)
问题: 创建复杂对象很麻烦,到处都要重复写创建代码。
解决: 专门的工厂类负责创建对象。
java
// MailConfig = 邮件服务工厂
@Configuration
public class MailConfig {
@Bean // 工厂方法:专门创建MailService
public MailService mailService(JavaMailSender sender, MailProperties props) {
// 复杂的创建逻辑集中在这里
return new MailService(sender, props);
}
@Bean // 工厂方法:专门创建JavaMailSender
public JavaMailSender javaMailSender() {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
// 复杂的配置逻辑
sender.setHost(env.getProperty("spring.mail.host"));
sender.setPort(Integer.parseInt(env.getProperty("spring.mail.port")));
// ... 更多复杂配置
return sender;
}
}
工厂模式的好处:
- 创建逻辑集中管理
- 业务代码不需要关心如何创建对象
- 修改创建逻辑只需要改一个地方
2. 💉 依赖注入模式(Dependency Injection)
问题: 对象之间耦合度太高,A对象需要B对象时直接new B()。
解决: 外部注入依赖,而不是内部创建。
java
// 错误做法:高耦合
public class MailService {
public void sendMail() {
// 自己创建依赖,高耦合!
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("smtp.gmail.com"); // 写死了配置
sender.send();
}
}
// 正确做法:依赖注入
public class MailService {
private final JavaMailSender javaMailSender; // 依赖
// 构造器注入:外部传入依赖
public MailService(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
public void sendMail() {
javaMailSender.send(); // 使用注入的依赖,低耦合!
}
}
依赖注入的好处:
- 松耦合:MailService不关心JavaMailSender怎么创建
- 可测试:容易替换为Mock对象进行测试
- 灵活性:可以注入不同的实现
3. 🎯 策略模式(Strategy Pattern)
问题: 不同环境需要不同的配置策略。
解决: 根据条件选择不同的创建策略。
java
@Configuration
public class MailConfig {
// 策略1:优先使用Spring Boot自动配置的JavaMailSender
@Bean
@ConditionalOnMissingBean(MailService.class) // 条件:如果还没有MailService
public MailService mailService(JavaMailSender javaMailSender, MailProperties mailProperties) {
return new MailService(javaMailSender, mailProperties);
}
// 策略2:如果没有JavaMailSender,自己创建一个
@Bean
@ConditionalOnMissingBean(JavaMailSender.class) // 条件:如果还没有JavaMailSender
public JavaMailSender javaMailSender() {
// 从Environment读取Nacos配置,创建自定义的邮件发送器
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(env.getProperty("spring.mail.host"));
// ... 从配置中心读取配置
return sender;
}
}
策略模式的体现:
- 本地application.yml有配置 → 使用策略1(Spring Boot自动配置)
- 只有Nacos远程配置 → 使用策略2(手动创建)
- 自动适配不同的部署环境
4. 🔌 适配器模式(Adapter Pattern)
问题: 配置来源(Environment)和目标对象(JavaMailSender)接口不匹配。
解决: 创建适配器转换接口。
java
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
// 适配器:将Environment配置适配到JavaMailSender接口
sender.setHost(env.getProperty("spring.mail.host")); // 适配host
sender.setPort(Integer.parseInt(env.getProperty("spring.mail.port", "25"))); // 适配port
sender.setUsername(env.getProperty("spring.mail.username")); // 适配username
sender.setPassword(env.getProperty("spring.mail.password")); // 适配password
// 适配Properties配置
Properties props = new Properties();
String timeout = env.getProperty("spring.mail.properties.mail.smtp.timeout", "5000");
props.put("mail.smtp.timeout", timeout);
sender.setJavaMailProperties(props);
return sender;
}
适配器模式的体现:
- Environment = 被适配者(配置源)
- javaMailSender()方法 = 适配器(转换逻辑)
- JavaMailSender = 目标接口(期望的接口)
🔄 第五部分:完整工作流程
📋 项目启动时的Bean创建流程
1. Spring Boot启动
↓
2. 扫描AutoConfiguration.imports文件
↓
3. 发现MailConfig配置类
↓
4. 处理@Bean方法:
- 检查:容器中有JavaMailSender吗?
* 有 → 直接使用
* 没有 → 调用javaMailSender()方法创建
↓
5. 处理@Bean方法:
- 检查:容器中有MailService吗?
* 有 → 跳过
* 没有 → 调用mailService()方法创建
↓
6. 将创建的Bean存储到Spring容器中
↓
7. 启动完成,Bean准备就绪
🛍️ 业务代码使用Bean的流程
java
@Service
public class UserService {
@Resource // 1. Spring看到@Resource注解
private MailService mailService; // 2. 从容器中查找MailService
// 3. 找到后注入到这个字段
public void registerUser(String email) {
// 4. 直接使用注入的MailService,一行搞定
mailService.sendRegisterCode(email, "123456");
}
}
🎯 关键理解
- @Configuration + @Bean = 工厂 + 生产线
- 构造函数参数 = 需求清单
- Spring容器 = 仓库管理员
- @Resource = 取货单
核心好处:
- 写一次配置,到处都能用
- 业务代码简洁,专注业务逻辑
- 修改配置只需要改一个地方
- 自动处理依赖关系
🎓 第六部分:学习要点总结
✅ Bean注册的两种方式
| 方式 | 使用场景 | 示例 |
|---|---|---|
| 类注解 | 简单业务类,不需要复杂配置 | @Service UserService |
| @Bean方法 | 需要复杂配置的对象 | MailService、DataSource |
✅ 注解职责分工
| 注解 | 作用位置 | 职责 |
|---|---|---|
| @Configuration | 类 | 标识配置类 |
| @Bean | 方法 | 标识Bean工厂方法 |
| @Service/@Controller | 类 | 标识业务组件 |
| @Resource/@Autowired | 字段/方法 | 依赖注入 |
✅ 设计模式实际应用
- 工厂模式 → 统一创建复杂对象
- 依赖注入 → 降低耦合度
- 策略模式 → 环境自适应
- 适配器模式 → 接口兼容
✅ 核心记忆口诀
- MailConfig = 服务员(提前准备好菜)
- 构造函数 = 订餐单(我要什么)
- Spring容器 = 仓库(统一管理)
- @Resource = 取货(直接用现成的)
💡 第七部分:常见问题解答
❓ Q1: 为什么MailService没有@Service注解也能用?
A1: 因为通过MailConfig的@Bean方法手动注册到了Spring容器。@Resource只看容器里有没有,不看类有没有注解。
❓ Q2: 构造函数参数是干什么用的?
A2: 构造函数参数是"需求清单",告诉Spring"要创建我需要这些材料"。MailConfig负责准备材料并传递给构造函数。
❓ Q3: 什么时候用@Service,什么时候用@Bean?
A3:
- 简单业务类用@Service(让Spring自动管理)
- 需要复杂配置的类用@Bean(手动控制创建过程)
❓ Q4: @Configuration和@Bean必须配合使用吗?
A4: 是的。@Configuration标识配置类,@Bean标识具体的Bean工厂方法,缺一不可。
🚀 第八部分:扩展应用
📦 类似的配置示例
java
// 阿里云短信配置
@Configuration
public class AliSmsConfig {
@Bean("aliClient")
public Client client() {
Config config = new Config()
.setAccessKeyId(accessKeyId)
.setAccessKeySecret(accessKeySecret)
.setEndpoint(endpoint);
return new Client(config); // 复杂对象创建
}
}
// 数据库配置
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost/db");
dataSource.setUsername("user");
dataSource.setPassword("pass");
dataSource.setMaximumPoolSize(20); // 复杂配置
return dataSource;
}
}
🎯 最佳实践
- 配置类命名:以Config结尾,如MailConfig、DatabaseConfig
- Bean命名:使用@Bean(name="xxx")指定明确的名称
- 条件注解:使用@ConditionalOnMissingBean避免重复创建
- 配置分离:不同功能的配置放在不同的配置类中
📝 总结
通过MailConfig和MailService的例子,我们学习了:
- Spring容器的仓库管理机制
- Bean创建的两种方式(类注解 vs @Bean方法)
- 依赖注入的工作原理(构造函数参数的作用)
- 实际应用中的设计模式(工厂、依赖注入、策略、适配器)
核心思想: Spring帮我们做繁琐的对象创建和管理工作,我们专注于业务逻辑。这就是现代框架的价值 ------ 让复杂的事情变简单,让重复的事情变自动。
记住:设计模式不是为了炫技,而是为了解决实际问题。在Spring中,这些模式的应用让我们的代码更加优雅、可维护、可扩展。
📧 第九部分:配置文件的作用机制
🎯 邮箱配置在Spring中的作用
根据你提供的配置文件,让我们分析邮箱配置是如何发挥作用的:
yaml
spring:
mail:
host: smtp.qq.com
username: 454284665@qq.com # 登录账户
password: vdxewnwgchllbjha
port: 465
default-encoding: UTF-8
protocol: smtps
🔄 配置文件 → Spring容器 → Bean创建的完整流程
Step 1: Spring Boot启动时读取配置
java
// Spring Boot会自动读取配置文件,创建MailProperties Bean
@ConfigurationProperties(prefix = "spring.mail")
public class MailProperties {
private String host; // 自动映射 spring.mail.host
private String username; // 自动映射 spring.mail.username
private String password; // 自动映射 spring.mail.password
private int port; // 自动映射 spring.mail.port
// ... 其他属性
}
Step 2: Spring Boot自动配置邮件服务
java
// Spring Boot内置的邮件自动配置类(你看不到,但它在工作)
@Configuration
@ConditionalOnProperty(name = "spring.mail.host") // 只有配置了host才生效
public class MailSenderAutoConfiguration {
@Bean
@ConditionalOnMissingBean(JavaMailSender.class)
public JavaMailSender javaMailSender(MailProperties properties) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
// 使用你配置文件中的值
sender.setHost(properties.getHost()); // smtp.qq.com
sender.setPort(properties.getPort()); // 465
sender.setUsername(properties.getUsername()); // 454284665@qq.com
sender.setPassword(properties.getPassword()); // vdxewnwgchllbjha
sender.setProtocol(properties.getProtocol()); // smtps
return sender; // 创建好的邮件发送器放入Spring容器
}
}
Step 3: 你的MailConfig可以直接使用
java
@Configuration
public class MailConfig {
@Bean
public MailService mailService(JavaMailSender javaMailSender, MailProperties mailProperties) {
// 这两个参数Spring会自动注入:
// 1. javaMailSender - 来自Step 2的自动配置
// 2. mailProperties - 来自Step 1的配置文件映射
return new MailService(javaMailSender, mailProperties);
}
}
📋 配置文件的三种作用方式
方式1: 自动配置(最常用)
yaml
spring:
mail:
host: smtp.qq.com # Spring Boot自动创建JavaMailSender
username: xxx@qq.com # 自动配置所有邮件相关Bean
password: xxx
结果: Spring容器自动有了JavaMailSender和MailProperties
方式2: 手动覆盖自动配置
java
@Configuration
public class MailConfig {
// 如果你想自定义,可以手动创建,会覆盖自动配置
@Bean
@Primary // 优先使用这个
public JavaMailSender customJavaMailSender() {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
// 你的自定义配置...
return sender;
}
}
方式3: 条件配置(我们之前讨论的)
java
@Configuration
public class MailConfig {
// 只有在没有JavaMailSender时才创建
@Bean
@ConditionalOnMissingBean(JavaMailSender.class)
public JavaMailSender javaMailSender(Environment env) {
// 从Nacos等配置中心读取配置
return createCustomSender(env);
}
}
🎯 你的配置文件的具体作用
yaml
spring:
mail:
host: smtp.qq.com # 邮件服务器地址
username: 454284665@qq.com # 发送邮件的账号
password: vdxewnwgchllbjha # QQ邮箱的授权码(不是QQ密码)
port: 465 # SMTPS安全端口
default-encoding: UTF-8 # 邮件编码
protocol: smtps # 使用SMTPS协议(SSL加密)
这些配置会自动变成:
java
// Spring自动创建的MailProperties
MailProperties mailProperties = new MailProperties();
mailProperties.setHost("smtp.qq.com");
mailProperties.setUsername("454284665@qq.com");
mailProperties.setPassword("vdxewnwgchllbjha");
mailProperties.setPort(465);
mailProperties.setDefaultEncoding("UTF-8");
mailProperties.setProtocol("smtps");
// Spring自动创建的JavaMailSender
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost("smtp.qq.com");
javaMailSender.setUsername("454284665@qq.com");
javaMailSender.setPassword("vdxewnwgchllbjha");
javaMailSender.setPort(465);
// ... 其他配置
🚀 完整的邮件发送流程
java
// 1. 启动时:Spring读取配置文件
spring.mail.host=smtp.qq.com
spring.mail.username=454284665@qq.com
↓
// 2. Spring自动创建Bean
MailProperties + JavaMailSender → 放入容器
↓
// 3. 你的MailService使用
@Resource
private MailService mailService; // 注入现成的
public void sendEmail() {
mailService.sendRegisterCode("user@email.com", "123456");
// 内部使用你配置的QQ邮箱发送邮件
}
💡 为什么要这样设计?
传统方式(每次手动配置):
java
public void sendEmail() {
// 每次发邮件都要写这些!
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("smtp.qq.com");
sender.setUsername("454284665@qq.com");
sender.setPassword("vdxewnwgchllbjha");
sender.setPort(465);
// ... 10行配置代码
sender.send(message); // 终于能发邮件了
}
Spring方式(配置一次,到处使用):
yaml
# 配置文件:写一次
spring:
mail:
host: smtp.qq.com
username: 454284665@qq.com
password: vdxewnwgchllbjha
java
// 业务代码:到处用
@Resource
private MailService mailService;
public void sendEmail() {
mailService.sendRegisterCode("user@email.com", "123456"); // 一行搞定!
}
🔐 安全提醒
你的配置中的password vdxewnwgchllbjha 是QQ邮箱的授权码,不是你的QQ密码。这是QQ邮箱为第三方应用提供的专用密码,更安全。
获取QQ邮箱授权码的步骤:
- 登录QQ邮箱
- 设置 → 账户
- 开启SMTP服务
- 获取授权码
🎯 总结:配置文件的核心价值
- 配置与代码分离:邮箱配置放在配置文件,代码更干净
- 环境适配:开发环境用测试邮箱,生产环境用正式邮箱
- Spring自动装配:配置文件 → 自动创建Bean → 直接注入使用
- 统一管理:所有邮件相关配置集中在一个地方
配置文件就是Spring的"原料清单",告诉Spring要准备什么材料,Spring根据清单自动准备好所有Bean供你使用!