Spring p 命名空间注入详解
一、什么是 p 命名空间?
p 命名空间是 Spring 对 XML 配置的一种语法糖,用于简化 Setter 注入的写法。它允许你直接在 <bean> 标签上以属性的形式注入依赖,而不需要嵌套 <property> 标签。
没有 p 命名空间时,Setter 注入要这样写:
xml
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
<property name="appName" value="myapp"/>
<property name="timeout" value="30"/>
</bean>
使用 p 命名空间后:
xml
<bean id="userService" class="com.example.UserService"
p:userDao-ref="userDao"
p:appName="myapp"
p:timeout="30"/>
代码量明显减少,尤其是依赖较多时,配置会简洁很多。
二、引入 p 命名空间
p 命名空间不是 Spring 内置的 XML 标准命名空间,使用时需要在 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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用 p 命名空间 -->
<bean id="userService" class="com.example.UserService"
p:userDao-ref="userDao"
p:appName="myapp"/>
</beans>
关键就是 xmlns:p="http://www.springframework.org/schema/p" 这一行。Spring 在解析时,会识别 p: 前缀,并将其转换为标准的 <property> 配置。
三、p 命名空间的基本语法
3.1 注入简单值
xml
<bean id="myBean" class="com.example.MyBean"
p:name="张三"
p:age="25"
p:debug="true"
p:timeout="30"/>
等价于:
xml
<bean id="myBean" class="com.example.MyBean">
<property name="name" value="张三"/>
<property name="age" value="25"/>
<property name="debug" value="true"/>
<property name="timeout" value="30"/>
</bean>
3.2 注入 Bean 引用
注入 Bean 引用时,属性名后面要加 -ref:
xml
<bean id="userService" class="com.example.UserService"
p:userDao-ref="userDao"
p:emailService-ref="emailService"/>
等价于:
xml
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
<property name="emailService" ref="emailService"/>
</bean>
3.3 注入集合
p 命名空间本身不支持直接定义集合,但可以通过 util: 命名空间配合使用:
xml
<beans xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="serverList">
<value>server1</value>
<value>server2</value>
</util:list>
<bean id="myBean" class="com.example.MyBean"
p:servers-ref="serverList"/>
</beans>
3.4 注入内部 Bean
p 命名空间不支持内联定义 Bean。如果需要注入匿名内部 Bean,必须使用标准的 <property> 写法:
xml
<bean id="userService" class="com.example.UserService">
<property name="userDao">
<bean class="com.example.UserDaoImpl"/>
</property>
</bean>
p 命名空间只能引用容器中已存在的 Bean。
四、p 命名空间与 c 命名空间
p 命名空间对应 Setter 注入,c 命名空间对应构造器注入。它们都是 Spring XML 配置的语法糖。
c 命名空间示例:
xml
<bean id="userService" class="com.example.UserService"
c:userDao-ref="userDao"
c:appName="myapp"/>
需要声明:
xml
xmlns:c="http://www.springframework.org/schema/c"
p 与 c 的对比:
| 对比维度 | p 命名空间 | c 命名空间 |
|---|---|---|
| 对应注入方式 | Setter 注入 | 构造器注入 |
| 前提条件 | 类有无参构造方法和 setter | 类有对应的有参构造方法 |
| 属性名后缀 | -ref 表示 Bean 引用 |
-ref 表示 Bean 引用 |
| 集合注入 | 需配合 util: 命名空间 |
需配合 util: 命名空间 |
| 内部 Bean | 不支持 | 不支持 |
两者可以混用,但同一个 Bean 如果同时使用 p 和 c,需要注意构造器注入和 Setter 注入的执行顺序------先调用构造方法,再调用 Setter 方法。
五、底层原理
p 命名空间并不是 Spring 的"新功能",而是 XML 解析层面的语法糖。Spring 在启动时,BeanDefinitionParserDelegate 会解析 <bean> 标签的所有属性。当遇到 p: 前缀的属性时,会调用 parsePropertyElement() 将其转换为标准的 PropertyValue,最终存入 BeanDefinition 的 propertyValues 中。
解析规则:
p:name="value"→<property name="name" value="value"/>p:name-ref="beanId"→<property name="name" ref="beanId"/>
转换完成后,后续的 Bean 实例化和属性填充流程与标准 <property> 完全一致。所以 p 命名空间没有任何运行时开销,只是让 XML 写起来更简洁。
六、完整示例
Java 类:
java
public class UserService {
private UserDao userDao;
private String appName;
private int timeout;
private boolean debug;
// 必须有默认无参构造方法
public UserService() {}
// 必须有 setter 方法
public void setUserDao(UserDao userDao) { this.userDao = userDao; }
public void setAppName(String appName) { this.appName = appName; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public void setDebug(boolean debug) { this.debug = debug; }
}
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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.example.UserDaoImpl"/>
<bean id="userService" class="com.example.UserService"
p:userDao-ref="userDao"
p:appName="myapp"
p:timeout="30"
p:debug="true"/>
</beans>
测试代码:
java
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService.getAppName()); // myapp
七、注意事项
1. 必须有 setter 方法
p 命名空间本质上是 Setter 注入,如果类没有对应的 setter 方法,启动时会报 NotWritablePropertyException。
2. 必须有默认无参构造方法
Spring 需要先通过无参构造方法创建对象,再调用 setter 方法注入。如果类没有无参构造方法,p 命名空间会失败。
3. 不支持构造器注入
构造器注入需要用 c 命名空间,或者标准的 <constructor-arg>。
4. 不支持内部 Bean
p 命名空间只能引用容器中已有的 Bean,不能内联定义匿名 Bean。
5. 属性名中的 -ref 后缀
注入 Bean 引用时,必须写 p:属性名-ref,不能写成 p:属性名,否则 Spring 会把值当作字符串处理。
6. 集合类型需借助 util 命名空间
p 命名空间不能直接写 <list>、<map> 等集合标签。如果需要注入集合,先用 util: 命名空间定义集合 Bean,再用 p:属性名-ref 引用。
7. IDE 支持有限
部分旧版 IDE 对 p 命名空间的自动补全支持不够完善,可能需要手动输入。
八、p 命名空间与标准 <property> 的选择
| 场景 | 推荐写法 |
|---|---|
| 简单值、Bean 引用 | p 命名空间,简洁 |
| 内部 Bean | 标准 <property> |
| 集合、数组、Map | 标准 <property> 或 util: + p |
| 需要条件化注入 | 标准 <property> |
| 老项目维护 | 保持原有风格,不强行改写 |
在 Spring Boot 项目中,XML 配置已经很少使用,p 命名空间更少见。但阅读老项目代码时,经常会遇到 p 命名空间,理解它的写法有助于快速读懂配置。
九、总结
| 维度 | 核心要点 |
|---|---|
| 本质 | XML 语法糖,简化 Setter 注入 |
| 声明 | xmlns:p="http://www.springframework.org/schema/p" |
| 简单值 | p:属性名="值" |
| Bean 引用 | p:属性名-ref="beanId" |
| 前提 | 无参构造方法 + setter 方法 |
| 不支持 | 构造器注入、内部 Bean、集合内联定义 |
| 对应关系 | p 对应 Setter 注入,c 对应构造器注入 |
| 底层 | 解析时转换为标准 <property> |
| 适用场景 | 老项目 XML 配置,简化依赖注入写法 |
p 命名空间是 Spring XML 配置时代的一个实用技巧,它让 Setter 注入的配置更紧凑。虽然在新项目中已经很少使用,但在维护老项目或阅读 Spring 传统配置时,掌握 p 命名空间能帮你更快理解配置意图。