Spring 的核心双引擎 —— 容器帮你创建对象,DI 帮你组装零件
按照下面的步骤,先把一个能跑的项目建起来。复制粘贴就能运行!
选择 Maven,GroupId 填 com.example,ArtifactId 填 spring-ioc-demo
打开项目根目录的 pom.xml,在 <dependencies> 里添加:
<dependencies>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.30</version>
</dependency>
</dependencies>// 📄 AppConfig.java — Spring 配置类(替代 XML 配置文件)
package com.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
// @Configuration: 标记为配置类,Spring 启动时会读取它
// @ComponentScan: 告诉 Spring 去哪个包下扫描 @Component/@Service/@Repository 等注解
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 不需要写任何代码!
// Spring 自动扫描 com.example 包下所有带注解的类
}// 📄 UserDao.java — 数据访问层接口
package com.example.dao;
// 定义接口: 面向接口编程,方便后续切换实现(如 MySQL → MongoDB)
public interface UserDao {
void save(String username); // 保存用户
}// 📄 UserDaoImpl.java — UserDao 的具体实现
package com.example.dao;
import org.springframework.stereotype.Repository;
// @Repository: 标记为数据访问层组件
// 效果等同于 @Component,但语义更明确("我是存数据的")
// Spring 扫描时会自动创建这个类的实例,放入容器
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void save(String username) {
// 实际项目中这里会调用 MyBatis/JPA 操作数据库
System.out.println("[DAO] 保存用户到数据库: " + username);
}
}// 📄 UserService.java — 业务逻辑层接口
package com.example.service;
public interface UserService {
void register(String username); // 注册用户
}// 📄 UserServiceImpl.java — 业务逻辑层实现(DI 核心示例)
package com.example.service;
import com.example.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
// @Service: 标记为业务逻辑层组件,Spring 会自动创建并管理
@Service
public class UserServiceImpl implements UserService {
// final: 依赖不可变,一旦注入就不能更改(更安全)
private final UserDao userDao;
// @Autowired: 构造器注入(官方推荐!)
// Spring 启动时自动找到容器中的 UserDao 实例,传入构造器
// 你没有写 new UserDaoImpl(),Spring 帮你"送货上门"!
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void register(String username) {
System.out.println("[Service] 开始注册用户: " + username);
userDao.save(username); // 调用 DAO 层,此时 userDao 已被 Spring 注入
System.out.println("[Service] 注册完成!");
}
}// 📄 MainApp.java — 启动入口
package com.example;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
// 第1步: 启动 Spring 容器
// 传入配置类 → Spring 读取 @ComponentScan → 扫描并创建所有 Bean
ApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class);
// 第2步: 从容器中取出 UserService
// 注意: 没有 new!Spring 已经创建好了,并且 UserDao 也自动注入了
UserService userService = ctx.getBean(UserService.class);
// 第3步: 直接使用(内部的 userDao 已经被 Spring 注入好了)
userService.register("小明");
// 输出:
// [Service] 开始注册用户: 小明
// [DAO] 保存用户到数据库: 小明
// [Service] 注册完成!
}
}右键 MainApp → Run,你会看到:
[Service] 开始注册用户: 小明
[DAO] 保存用户到数据库: 小明
[Service] 注册完成!
1. 没有写 new UserDaoImpl() — Spring 容器帮你创建了
2. 没有手动把 UserDao 传给 UserService — @Autowired 帮你注入了
3. 这就是 IoC(控制反转)+ DI(依赖注入) 的核心!
public class UserService {
// 硬编码写死了用 UserDaoImpl
private UserDao dao
= new UserDaoImpl();
}public class UserService {
// 不管具体实现,容器来决定
private UserDao dao;
// 容器自动把 dao "送"进来
}"不要自己 new,让容器送过来"
控制权从"你的代码" → 反转到"Spring 容器",所以叫控制反转。
@Service
public class UserServiceImpl implements UserService {
private final UserDao userDao; // final = 创建后不可变!
// @Autowired 可省略(Spring 4.3+ 只有一个构造器时自动注入)
// 推荐原因: final 保证依赖不为 null,且不会被意外修改
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao; // Spring 自动传入容器中的 UserDao
}
}@Service
public class UserServiceImpl implements UserService {
private UserDao userDao; // 没有 final,后续可以修改
// Setter 注入: Spring 先创建对象,再调用 setXxx() 方法注入
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao; // 对象创建后通过 setter "装上去"
}
}@Service
public class UserServiceImpl implements UserService {
@Autowired // 直接加在字段上,Spring 用反射强行赋值
private UserDao userDao; // ⚠️ 不能用 final!反射无法修改 final 字段
// 看起来最省事,但测试时无法手动传入 Mock 对象
}final,依赖可能被修改| 对比项 | 🏆 构造器 | 🔧 Setter | ⚡ 字段 |
|---|---|---|---|
| 支持 final | ✅ | ❌ | ❌ |
| 保证非 null | ✅ | ❌ | ❌ |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ |
Spring 看到 @Autowired 就去容器里找:"有没有这个类型的 Bean?有就注入!"
比如 UserDao 有 MysqlUserDao 和 MongoUserDao 两个实现,Spring 不知道注入哪个!
@Autowired
@Qualifier("mysqlDao") // 指定 Bean 名称
private UserDao dao;
// 容器中有多个 UserDao 时,明确要哪个@Repository
@Primary // 标记为首选实现
public class MysqlDao
implements UserDao {}
// 不加 @Qualifier 时默认用这个@Resource(name="mysqlDao")
private UserDao dao;
// javax 标准注解,按名称查找
// 与 @Autowired 区别: 先按名称找
@Autowired = 按类型找 → 多个同类型?用 @Qualifier 指定名字
@Resource = 按名称找 → Java 标准注解(非 Spring 独有)
全服只有一把屠龙刀🗡️
不管谁来取,拿到的都是同一把
Spring 默认就是这种!
每次领取都给你新锻造一把🔨
每个人的都不一样
需要 @Scope("prototype")
// 默认 singleton(单例),不用额外声明
// 整个容器中只有一个实例,所有地方共享同一个对象
@Component
public class UserService { }
// prototype(原型): 每次 getBean() 都创建新实例
// 适合有状态的对象(如购物车,每个用户应该各自独立)
@Component
@Scope("prototype")
public class ShoppingCart { } // 购物车应该每人一个!点击按钮创建 Bean,观察 singleton 和 prototype 的区别!
点击按钮,逐步观察 Spring 容器如何启动、创建 Bean、注入依赖
点击空白处选中,再点击下方选项填入