SSM 框架学习:Spring IoC 容器与依赖注入
Spring/SpringMVC/MyBatis 全栈框架学习项目,从 IoC 容器、DI 注入到 AOP,层层递进掌握 SSM 核心技术。
SSM 框架学习:Spring IoC 容器与依赖注入
项目背景
SSM 是 Spring + Spring MVC + MyBatis 的缩写,在 Spring Boot 普及之前是 Java Web 开发的标准技术栈。这个项目通过整合 SSM 框架,重点学习了 Spring 最核心的 IoC(控制反转)容器和 DI(依赖注入)机制。
没有 IoC 时的问题
传统开发中,Service 层调用 DAO 层时直接在代码里 new 实现类:
public class UserService {
private UserDao userDao = new UserDaoImpl(); // 硬编码耦合
public User getById(Long id) {
return userDao.findById(id);
}
}
问题:如果要把 UserDaoImpl 换成另一个实现(比如加缓存的实现),必须修改 UserService 的源码。测试时也很难替换为 Mock 对象。
IoC 容器解决耦合
IoC 把对象的创建和管理交给 Spring 容器。Service 只需要声明"我需要什么":
public class UserService {
private UserDao userDao; // 只声明接口,不 new 实现
// Spring 容器自动注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public User getById(Long id) {
return userDao.findById(id);
}
}
三种配置方式
方式一: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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 声明 Bean -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserService">
<!-- 注入依赖 -->
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userController" class="com.example.controller.UserController">
<constructor-arg ref="userService"/>
</bean>
</beans>
方式二:注解配置(目前主流)
@Repository // 标记为 DAO 组件
public class UserDaoImpl implements UserDao {
public User findById(Long id) {
// JDBC 或 MyBatis 查询
}
}
@Service // 标记为 Service 组件
public class UserService {
@Autowired // 自动注入 UserDao
private UserDao userDao;
}
@RestController // 标记为 Controller 组件
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
}
方式三:Java Config(类型安全)
@Configuration
@ComponentScan("com.example")
public class AppConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/db");
ds.setUsername("root");
ds.setPassword("password");
ds.setMaximumPoolSize(10);
return ds; // 返回的 Bean 自动由 Spring 管理
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(ds);
factory.setTypeAliasesPackage("com.example.domain");
return factory.getObject();
}
}
Java Config 的优势:编译期就能发现配置错误(比如拼错方法名),IDE 自动补全,不需要手写 XML。
SSM 整合关键点
SSM 整合的核心是把 MyBatis 的 SqlSessionFactory 交给 Spring 容器管理:
@Configuration
@MapperScan("com.example.mapper") // 扫描 Mapper 接口
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds);
// 配置 MyBatis 全局属性
org.apache.ibatis.session.Configuration config =
new org.apache.ibatis.session.Configuration();
config.setMapUnderscoreToCamelCase(true); // user_name → userName
config.setLogImpl(StdOutImpl.class); // 打印 SQL
factoryBean.setConfiguration(config);
return factoryBean.getObject();
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}
通过 @MapperScan,MyBatis 的 Mapper 接口自动生成代理实现类并注册为 Spring Bean,Service 层直接 @Autowired 就能使用 Mapper,实现了"无感集成"。
常见踩坑
循环依赖: A Service 注入 B Service,同时 B Service 也注入 A Service。虽然 Spring 通过三级缓存可以解决大部分循环依赖,但这通常是设计上的坏味道,应该把公共依赖抽取到第三个服务类。
Bean 作用域: 默认是 singleton(单例),所有线程共享同一个实例。如果 Service 持有状态变量,在高并发下会出现竞态条件,需要使用 @Scope("prototype") 或 ThreadLocal。
事务失效: @Transactional 在同类内部调用不生效——必须从外部调用才能通过 AOP 代理触发事务。
总结
理解 IoC 是理解 Spring 一切功能的基础。AOP 是 IoC 的扩展应用,事务管理是 AOP 的典型场景,Spring MVC 是 IoC 在 Web 层的实现。SSM 虽然已被 Spring Boot 取代,但理解它的整合过程能更好地理解 Spring Boot"约定优于配置"的设计理念。