Spring c 命名空间注入详解
一、什么是 c 命名空间?
c 命名空间是 Spring 对 XML 配置的一种语法糖,用于简化构造器注入 的写法。它允许你直接在 <bean> 标签上以属性的形式注入构造参数,而不需要嵌套 <constructor-arg> 标签。
没有 c 命名空间时,构造器注入要这样写:
xml
<bean id="userService" class="com.example.UserService">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="appName" value="myapp"/>
<constructor-arg name="timeout" value="30"/>
</bean>
使用 c 命名空间后:
xml
<bean id="userService" class="com.example.UserService"
c:userDao-ref="userDao"
c:appName="myapp"
c:timeout="30"/>
代码量明显减少,依赖较多时配置更紧凑。c 命名空间与 p 命名空间是"兄弟关系":p 对应 Setter 注入,c 对应构造器注入。
二、引入 c 命名空间
在 XML 根元素中声明:
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"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- c 命名空间在这里使用 -->
</beans>
关键是 xmlns:c="http://www.springframework.org/schema/c" 这一行。
三、c 命名空间的基本语法
3.1 按参数名注入
xml
<bean id="userService" class="com.example.UserService"
c:userDao-ref="userDao"
c:appName="myapp"
c:timeout="30"/>
前提是构造方法的参数名可用。Java 编译时默认不保留参数名,需要在编译时加上 -parameters 参数,或者使用 @ConstructorProperties 注解,或者显式指定参数索引。
3.2 按参数索引注入(推荐)
当参数名不可用时,用索引来指定参数位置。索引从 0 开始:
xml
<bean id="userService" class="com.example.UserService"
c:_0-ref="userDao"
c:_1="myapp"
c:_2="30"/>
_0、_1、_2表示第 0、1、2 个参数- 引用 Bean 时加
-ref后缀
索引方式不依赖参数名,更稳妥。
3.3 单个参数时省略索引
如果构造方法只有一个参数,可以省略索引:
xml
<bean id="userService" class="com.example.UserService"
c:_-ref="userDao"/>
或者按参数名:
xml
<bean id="userService" class="com.example.UserService"
c:userDao-ref="userDao"/>
3.4 注入集合
c 命名空间不能直接定义集合。需要用 util: 命名空间定义集合 Bean,然后引用:
xml
<util:list id="serverList">
<value>server1</value>
<value>server2</value>
</util:list>
<bean id="myBean" class="com.example.MyBean"
c:servers-ref="serverList"/>
四、Java 类的要求
构造器注入要求类提供匹配的构造方法:
java
public class UserService {
private final UserDao userDao;
private final String appName;
private final int timeout;
// 构造方法必须有对应的参数
public UserService(UserDao userDao, String appName, int timeout) {
this.userDao = userDao;
this.appName = appName;
this.timeout = timeout;
}
}
如果类有多个构造方法,需要确保参数类型或索引能明确匹配到某一个。Spring 会根据参数的数量和类型自动匹配。
五、p 与 c 的对比
| 对比维度 | p 命名空间 | c 命名空间 |
|---|---|---|
| 对应注入方式 | Setter 注入 | 构造器注入 |
| 声明 | xmlns:p=".../p" |
xmlns:c=".../c" |
| 简单值 | p:name="value" |
c:name="value" 或 c:_0="value" |
| Bean 引用 | p:name-ref="beanId" |
c:name-ref="beanId" 或 c:_0-ref="beanId" |
| 前提条件 | 无参构造方法 + setter | 有参构造方法 |
| 参数名依赖 | 依赖属性名(一定存在) | 依赖构造参数名(可能不可用,需用索引) |
| 推荐程度 | 一般 | 推荐(符合 Spring 官方构造器注入优先原则) |
六、底层原理
c 命名空间的解析由 BeanDefinitionParserDelegate 处理。当解析到 c: 前缀的属性时,会将其转换为 ConstructorArgumentValues:
c:name="value"→ConstructorArgumentValues中name对应的参数c:_0="value"→ConstructorArgumentValues中索引0对应的参数c:name-ref="beanId"→RuntimeBeanReference
转换后的数据存入 BeanDefinition 的 constructorArgumentValues。后续创建 Bean 时,ConstructorResolver 根据这些信息匹配构造方法并调用。
c 命名空间本质上还是 <constructor-arg> 的语法糖,只是写起来更简洁。运行时行为完全一致。
七、完整示例
Java 类:
java
public class OrderService {
private final UserDao userDao;
private final PaymentService paymentService;
private final String appName;
private final int timeout;
public OrderService(UserDao userDao,
PaymentService paymentService,
String appName,
int timeout) {
this.userDao = userDao;
this.paymentService = paymentService;
this.appName = appName;
this.timeout = timeout;
}
}
XML 配置(按参数名):
xml
<bean id="orderService" class="com.example.OrderService"
c:userDao-ref="userDao"
c:paymentService-ref="paymentService"
c:appName="myapp"
c:timeout="30"/>
XML 配置(按索引):
xml
<bean id="orderService" class="com.example.OrderService"
c:_0-ref="userDao"
c:_1-ref="paymentService"
c:_2="myapp"
c:_3="30"/>
测试代码:
java
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
OrderService orderService = context.getBean("orderService", OrderService.class);
八、注意事项
1. 参数名依赖编译设置
按参数名注入时,Java 编译必须保留参数名。Maven 中配置:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
否则参数名会变成 arg0、arg1,c 命名空间找不到匹配。这种情况下必须使用索引方式。
2. 不支持内部 Bean
c 命名空间只能引用容器中已有的 Bean,不能内联定义匿名 Bean。
3. 不支持集合内联定义
需要用 util: 命名空间定义集合,再通过 -ref 引用。
4. 构造方法重载歧义
如果类有多个构造方法,c 命名空间可能无法确定使用哪一个。可以通过参数数量区分,或改用标准的 <constructor-arg> 显式指定。
5. 循环依赖无法解决
构造器注入无法解决循环依赖。两个 Bean 互相通过构造器注入对方时,启动会失败。这是构造器注入的固有限制。
6. 与 p 命名空间混用
同一个 Bean 可以同时使用 c 和 p,Spring 会先调用构造方法,再调用 setter:
xml
<bean id="userService" class="com.example.UserService"
c:userDao-ref="userDao"
p:appName="myapp"
p:timeout="30"/>
7. 现代项目中使用减少
Spring Boot 以 Java 配置和注解为主,c 命名空间主要出现在 XML 配置的老项目中。阅读老代码时理解它能帮你快速读懂配置。
九、c 命名空间与标准 <constructor-arg> 的选择
| 场景 | 推荐写法 |
|---|---|
| 参数少、参数名可用 | c 命名空间(简洁) |
| 参数名不可用 | c 命名空间 + 索引,或标准 <constructor-arg> |
| 构造方法有歧义 | 标准 <constructor-arg>(可以精确指定类型) |
| 需要注入内部 Bean | 标准 <constructor-arg> |
| 需要注入集合 | util: + c 命名空间 |
| 老项目维护 | 保持原有风格 |
十、总结
| 维度 | 核心要点 |
|---|---|
| 本质 | XML 语法糖,简化构造器注入 |
| 声明 | xmlns:c="http://www.springframework.org/schema/c" |
| 简单值 | c:参数名="值" 或 c:_索引="值" |
| Bean 引用 | c:参数名-ref="beanId" 或 c:_索引-ref="beanId" |
| 前提 | 类有对应的有参构造方法 |
| 参数名依赖 | 需编译时保留参数名,否则用索引 |
| 不支持 | 内部 Bean、集合内联定义 |
| 与 p 的关系 | c 对应构造器注入,p 对应 Setter 注入,可以混用 |
| 底层 | 解析时转换为 <constructor-arg> |
| 推荐场景 | 老项目 XML 配置、构造器注入的简洁写法 |
c 命名空间是 Spring XML 配置中构造器注入的简洁写法。它和 p 命名空间一起,分别对应构造器注入和 Setter 注入两种方式。由于构造器注入是 Spring 官方推荐的方式,c 命名空间在理念上比 p 命名空间更"正统"。不过在实际项目中,随着 Java 配置和注解的普及,两种命名空间的使用都明显减少。理解它们的语法和底层原理,主要在维护老项目时派上用场。