Spring c 命名空间注入详解

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"ConstructorArgumentValuesname 对应的参数
  • c:_0="value"ConstructorArgumentValues 中索引 0 对应的参数
  • c:name-ref="beanId"RuntimeBeanReference

转换后的数据存入 BeanDefinitionconstructorArgumentValues。后续创建 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>

否则参数名会变成 arg0arg1,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 配置和注解的普及,两种命名空间的使用都明显减少。理解它们的语法和底层原理,主要在维护老项目时派上用场。

相关推荐
明月_清风1 小时前
Maven 到底是什么?一篇文章搞懂 Java 项目构建与依赖管理
java·后端·maven
明月_清风1 小时前
AI 越来越强,程序员真正的价值到底是什么?
人工智能·后端
aramae1 小时前
MySQL复合查询(8)
java·c语言·开发语言·后端·算法
Rain的Java大神之路2 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog3 小时前
Java 接入Redis: 通用命令与键管理
java·服务器·redis
Wang's Blog3 小时前
Java 接入Redis: 列表集合与有序集合操作命令
java·服务器·redis
Ivanqhz3 小时前
SVD++算法
java·服务器·网络·深度学习·神经网络
码事漫谈3 小时前
别再跟AI说“请”了,它根本不 care——但有个东西它超在意
后端
qq_2518364574 小时前
springboot vue3 开发实现 拼豆管理系统
java·开发语言·ai编程