在 Spring 框架的学习中,基于 XML 的非注解开发模式是理解框架底层机制的核心路径。本文结合黑马程序员 SSM 教程内容,系统解析 IoC(控制反转)、DI(依赖注入)的核心原理,深度演示 Bean(含第三方组件)的配置与管理,并通过企业级案例展现实战技巧。
一、IoC(控制反转):从手动创建到容器托管
1. 核心思想与容器本质
IoC 的核心是将对象的创建与生命周期管理从代码中剥离,交由 Spring 容器统一处理。传统开发中通过new关键字创建对象,而在 IoC 模式下,开发者只需在 XML 中声明 Bean,由容器通过反射、工厂模式等机制生成实例。Spring 提供BeanFactory(基础容器)和ApplicationContext(增强容器,支持国际化、事件机制)两种实现,企业开发中通常使用后者。
2. Bean 定义的三种方式
(1)无参构造函数(默认方式)
<bean id="userService" class="com.service.UserServiceImpl"/>
容器通过反射调用类的无参构造方法实例化对象,适用于大多数简单场景。
(2)静态工厂方法
<bean id="car" class="com.factory.StaticCarFactory" factory-method="createCar"/>
通过factory-method指定静态方法创建对象,适用于需要复用复杂创建逻辑的场景。
(3)实例工厂方法
<bean id="carFactory" class="com.factory.InstanceCarFactory"/>
<bean id="car" factory-bean="carFactory" factory-method="createCar"/>
需先定义工厂 Bean,再通过factory-bean指定实例工厂,适用于工厂本身需要注入其他依赖的场景。
3. 容器操作核心代码
// 初始化容器(加载XML配置)
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取Bean(两种方式)
UserService userService1 = (UserService) context.getBean("userService");
UserService userService2 = context.getBean("userService", UserService.class);
二、DI(依赖注入):解耦对象依赖关系
1. 两种核心注入方式
(1)构造函数注入(适合依赖关系固定的场景)
<bean id="userService" class="com.service.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"/> <!-- 按顺序注入引用类型 -->
<constructor-arg name="timeout" value="3000"/> <!-- 按名称注入基本类型 -->
</bean>
(2)Setter 方法注入(适合依赖关系可变的场景)
<bean id="userService" class="com.service.UserServiceImpl">
<property name="userDao" ref="userDao"/> <!-- 注入引用类型 -->
<property name="version" value="1.0"/> <!-- 注入基本类型 -->
</bean>
2. 高级注入技巧
(1)集合类型注入(支持 List/Set/Map/Props)
<bean id="roleService" class="com.service.RoleServiceImpl">
<property name="adminRoles">
<list>
<value>ROLE_ADMIN</value>
<value>ROLE_SUPER</value>
</list>
</property>
<property name="userProps">
<props>
<prop key="timeout">3000</prop>
<prop key="retries">3</prop>
</props>
</property>
</bean>
(2)自动装配(Autowiring)
通过autowire属性简化配置,常用模式:
- byName:按属性名匹配 Bean 的id
- byType:按属性类型匹配唯一 Bean(需保证容器中该类型唯一)
<bean id="userService" class="com.service.UserServiceImpl" autowire="byType"/>
3. 第三方 Bean 的依赖注入
以数据库连接池 Druid 为例,演示如何注入第三方组件的复杂配置:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/> <!-- 注入JDBC驱动 -->
<property name="url" value="${jdbc.url}"/> <!-- 注入数据库URL -->
<property name="maxActive" value="20"/> <!-- 注入连接池参数 -->
</bean>
关键点: 第三方 Bean 的属性需与类中的 Setter 方法严格对应(如setDriverClassName对应driverClassName属性)。
三、Bean 管理:生命周期、作用域与高级特性
1. 生命周期控制(初始化与销毁回调)
(1)XML 配置方式(推荐)
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" <!-- 初始化方法 -->
destroy-method="close"> <!-- 销毁方法(仅单例Bean生效) -->
<!-- 配置省略 -->
</bean>
(2)接口实现方式(侵入性强,不推荐)
public class UserService implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}
@Override
public void destroy() throws Exception {
// 销毁逻辑
}
}
2. 作用域控制(4 大核心作用域)
作用域 | 说明 | 典型场景 |
---|---|---|
singleton | 容器中唯一实例(默认),线程安全,适用于无状态 Bean(Service/DAO) | 业务逻辑层、数据访问层 |
prototype | 每次获取创建新实例,非线程安全,适用于有状态 Bean(命令对象) | 需保存用户状态的组件 |
request | 每次 HTTP 请求创建新实例(仅 Web 环境) | Web 层请求参数处理器 |
session | 每个 HTTP 会话共享一个实例(仅 Web 环境) | 会话级缓存组件 |
<bean id="userSession" class="com.SessionBean" scope="session"/> <!-- Web环境专用 -->
3. 第三方 Bean 的继承与别名
(1)配置继承(复用公共参数)
<!-- 定义基础数据源配置 -->
<bean id="baseDataSource" abstract="true">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
</bean>
<!-- 子类继承并扩展 -->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" parent="baseDataSource">
<property name="username" value="${jdbc.master.user}"/>
</bean>
(2)别名定义(简化 Bean 引用)
<alias name="userService" alias="userServiceProxy"/> <!-- 为同一Bean定义多个名称 -->
四、企业级实战:SSM 整合与第三方组件深度集成
1. SSM 框架核心整合步骤(XML 版)
(1)Spring 与 MyBatis 整合
<!-- 1. 配置数据源(Druid) -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 2. 配置SqlSessionFactory(MyBatis第三方Bean) -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/> <!-- 映射文件路径 -->
</bean>
<!-- 3. 扫描Mapper接口(自动生成代理Bean) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dao"/> <!-- Mapper接口所在包 -->
</bean>
(2)Spring 与 SpringMVC 整合
<!-- web.xml配置DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<!-- springmvc.xml配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
2. 复杂场景:多数据源动态切换(以 Druid 为例)
(1)定义目标数据源
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 主库配置 -->
</bean>
<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 从库配置 -->
</bean>
(2)配置动态数据源路由(Spring 原生支持)
<bean id="dynamicDataSource" class="org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="masterDataSource"/>
<entry key="slave" value-ref="slaveDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"/> <!-- 默认数据源 -->
</bean>
(3)实现数据源切换逻辑(线程安全)
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String type) {
CONTEXT_HOLDER.set(type);
}
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
}
3. 第三方日志框架(Log4j)整合
<!-- 配置ConsoleAppender(输出到控制台) -->
<bean id="consoleAppender" class="org.apache.log4j.ConsoleAppender">
<property name="layout" ref="patternLayout"/> <!-- 注入日志格式 -->
</bean>
<bean id="patternLayout" class="org.apache.log4j.PatternLayout">
<property name="conversionPattern" value="%d [%t] %-5p %c - %m%n"/> <!-- 日志模板 -->
</bean>
<!-- 配置根Logger(关联Appender) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.apache.log4j.Logger"/>
<property name="targetMethod" value="getRootLogger"/>
<property name="arguments">
<list>
<value>INFO</value> <!-- 日志级别 -->
<ref bean="consoleAppender"/> <!-- 关联Appender -->
</list>
</property>
</bean>
五、非注解模式的优缺点与适用场景
1. 核心优势
- 配置可见性:所有 Bean 定义集中在 XML,依赖关系一目了然,适合复杂依赖场景。
兼容性:可无缝整合不支持注解的老旧系统或第三方库(如遗留数据库驱动、传统日志框架)。 - 学习价值:深入理解 Spring 底层机制(反射、工厂模式、容器生命周期)的最佳实践。
2. 局限性
- 配置冗余:大量 XML 标签导致文件臃肿,修改时需频繁切换上下文。
类型安全问题:编译期无法检查配置错误(如class路径错误、属性名拼写错误),需运行时验证。 - 维护成本:复杂继承关系可能导致配置混乱,团队协作时需严格规范命名与结构。
3. 适用场景
- 遗留系统迁移:逐步将传统 JavaEE 项目纳入 Spring 生态,避免大规模代码改动。
- 基础设施管理:数据源、事务管理器、缓存框架等底层组件的配置,适合在 XML 中统一管理。
- 教学与底层研究:理解 Spring IoC/DI 核心原理,必须掌握 XML 配置模式。
六、总结:从 XML 到注解的演进与融合
非注解开发模式是 Spring 的 “基石”,其核心是通过 XML 文件显式定义 Bean 的行为与依赖关系。尽管现代开发以注解(@Component/@Autowired)为主流,但 XML 在以下场景仍不可替代:
- 第三方组件整合:当第三方库不支持注解或需要复杂初始化参数时,XML 是最直接的配置方式。
- 全局配置管理:如多数据源、事务