Spring 自定義命名空間不僅能簡化 XML 配置,更能結合註解掃描、Bean 後處理器、上下文感知實現框架級功能。本文接續基礎篇,完整實現一個 stdws 命名空間,支援 <stdws:annotation-endpoint> 元素,自動掃描 @Endpoint 註解並註冊 Bean,展示 NamespaceHandler、BeanDefinitionParser、BeanPostProcessor、ApplicationContextAware 協作全鏈路。
stdws.xsd (Schema 定義)
↓
spring.schemas (映射 Schema 位置)
↓
spring.handlers (映射 NamespaceHandler)
↓
WebServiceAnnotationNamespaceHandler (extends NamespaceHandlerSupport)
├─ registerBeanDefinitionParser("annotation-endpoint", AnnotationBeanDefinitionParser)
↓
AnnotationBeanDefinitionParser (implements BeanDefinitionParser)
├─ 解析 屬性 (id, name, package)
├─ 創建 RootBeanDefinition (beanClass=EndpointBeanProcessor)
├─ 注入 annotationPackage 屬性
└─ 註冊 BeanDefinition 到 Registry
↓
EndpointBeanProcessor (implements BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware, DisposableBean)
├─ postProcessBeanFactory: ClassPathBeanDefinitionScanner 掃描 @Endpoint
├─ postProcessAfterInitialization: 業務邏輯 (如發布 WebService)
└─ setApplicationContext: 獲取 ConfigurableApplicationContext、創建 BeanRegistry
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
xmlns="https://www.codestd.com/schema/std/ws"
targetNamespace="https://www.codestd.com/schema/std/ws"
elementFormDefault="qualified">
<xsd:element name="annotation-endpoint">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="package" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
public class WebServiceAnnotationNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 註冊元素解析器
registerBeanDefinitionParser("annotation-endpoint",
new AnnotationBeanDefinitionParser(EndpointBeanProcessor.class));
}
}
public class AnnotationBeanDefinitionParser implements BeanDefinitionParser {
private final Class> beanClass;
public AnnotationBeanDefinitionParser(Class> beanClass) {
this.beanClass = beanClass;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
// 生成 Bean ID:優先 id > name > 類全限定名
String id = element.getAttribute("id");
if (!StringUtils.hasText(id)) {
id = element.getAttribute("name");
}
if (!StringUtils.hasText(id)) {
id = beanClass.getName();
}
// 重複檢查
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id: " + id);
}
// 註冊
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
// 注入掃描包屬性
String annotationPackage = element.getAttribute("package");
if (StringUtils.hasText(annotationPackage)) {
beanDefinition.getPropertyValues().add("annotationPackage", annotationPackage);
}
return beanDefinition;
}
}
public class EndpointBeanProcessor
implements BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware, DisposableBean {
private ApplicationContext applicationContext;
private ConfigurableApplicationContext configurableContext;
private BeanRegistry beanRegistry; // 輔助動態註冊 Bean
private String annotationPackage;
private String[] annotationPackages;
private static final String COMMA_SPLIT = ",";
// ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
if (applicationContext instanceof ConfigurableApplicationContext) {
this.configurableContext = (ConfigurableApplicationContext) applicationContext;
this.beanRegistry = new BeanRegistry(this.configurableContext);
}
}
// 屬性設置 (由 BeanDefinitionParser 注入)
public void setAnnotationPackage(String annotationPackage) {
this.annotationPackage = annotationPackage;
if (StringUtils.hasText(annotationPackage)) {
this.annotationPackages = StringUtils.commaDelimitedListToStringArray(annotationPackage);
}
}
// BeanFactoryPostProcessor: 容器實例化 Bean 前,掃描註冊 @Endpoint Bean
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (!StringUtils.hasText(annotationPackage)) return;
if (!(beanFactory instanceof BeanDefinitionRegistry)) return;
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true);
// 只掃描帶 @Endpoint 註解的類
scanner.addIncludeFilter(new AnnotationTypeFilter(Endpoint.class));
// 掃描指定包
scanner.scan(annotationPackages);
}
// BeanPostProcessor: Bean 初始化後 (屬性注入完成),執行業務邏輯
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (!isMatchPackage(bean)) return bean;
Endpoint endpoint = bean.getClass().getAnnotation(Endpoint.class);
if (endpoint != null) {
// 業務邏輯:如發布 CXF/JAX-WS Endpoint、註冊到註冊中心等
System.out.println("Detected @Endpoint bean: " + bean.getClass().getName()
+ ", address=" + endpoint.address() + ", id=" + endpoint.id());
// publishEndpoint(bean, endpoint); // 實際發布邏輯
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean; // 可用於注入依賴
}
// 包匹配檢查
private boolean isMatchPackage(Object bean) {
if (annotationPackages == null || annotationPackages.length == 0) return true;
String className = bean.getClass().getName();
for (String pkg : annotationPackages) {
if (className.startsWith(pkg)) return true;
}
return false;
}
@Override
public void destroy() throws Exception {
// 清理資源:如取消發布 Endpoint、關閉連接
}
}
public class BeanRegistry implements ApplicationContextAware {
private ConfigurableApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext instanceof ConfigurableApplicationContext) {
this.context = (ConfigurableApplicationContext) applicationContext;
}
}
public BeanDefinition register(Class> clazz) {
if (context == null) return null;
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) context.getBeanFactory();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
BeanDefinition bd = builder.getRawBeanDefinition();
registry.registerBeanDefinition(clazz.getName(), bd);
return bd;
}
}
# spring.handlers
http\://www.codestd.com/schema/std/ws=com.codestd.spring.cxf.config.schema.WebServiceAnnotationNamespaceHandler
# spring.schemas
http\://www.codestd.com/schema/std/ws/stdws-1.0.xsd=META-INF/schema/stdws-1.0.xsd
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {
String address();
String id() default "";
}
@Endpoint(address = "/HelloService", id = "HelloServiceEndpoint")
@WebService(endpointInterface = "com.codestd.spring.cxf.ws.HelloService")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHi(String name) { return "Hello " + name; }
}
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:stdws="https://www.codestd.com/schema/std/ws"
xsi:schemaLocation="
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.codestd.com/schema/std/ws https://www.codestd.com/schema/std/ws/stdws-1.0.xsd">
<stdws:annotation-endpoint package="com.codestd.spring.cxf.ws" />
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class NamespaceExtensionTest {
@Test
public void testEndpointDetection() {
// 控制台應輸出: Detected @Endpoint bean: com.codestd.spring.cxf.ws.HelloServiceImpl...
}
}
postProcessBeforeInitialization 中根據 @Endpoint 屬性自動注入依賴 (如 Bus, EndpointRegistry)。ImportSelector 或 Condition,根據環境 (Profile、Property) 決定是否啟用命名空間。<stdws:interceptors> 子元素,Parser 解析為列表注入 EndpointBeanProcessor。@AliasFor 風格屬性別名,兼容不同版本註解。Spring 自定義命名空間本質是 XML 到 BeanDefinition 的聲明式映射,結合 BeanFactoryPostProcessor (掃描註冊)、BeanPostProcessor (生命週期攔截)、ApplicationContextAware (上下文訪問),可實現從配置到運行時行為的完整閉環。本文實現的 stdws:annotation-endpoint 展示了:Schema 驗證 → 解析器 → 掃描器 → 後處理器 → 業務邏輯 的標準化擴展路徑,可直接應用於 CXF、Dubbo、gRPC 等 RPC 框架的 Spring 集成模塊開發。