首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring 自定義命名空間擴展實戰 (二):Schema 驗證、BeanDefinitionParser、註解掃描與上下文感知完整實現

     編輯:Java 相關知識

前言:從 XML 擴展到註解驅動的 Spring 擴展點

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

二、Schema 定義 (META-INF/schema/stdws-1.0.xsd)

<?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));
    }
}

四、BeanDefinitionParser 實現

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;
    }
}

五、核心處理器:EndpointBeanProcessor

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、關閉連接
    }
}

六、輔助類:BeanRegistry (動態註冊 Bean)

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 擴展點 (META-INF)

# 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

八、使用示例

1. 註解定義

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {
    String address();
    String id() default "";
}

2. 業務實現

@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; }
}

3. Spring XML 配置

<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>

4. 測試驗證

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class NamespaceExtensionTest {
    @Test
    public void testEndpointDetection() {
        // 控制台應輸出: Detected @Endpoint bean: com.codestd.spring.cxf.ws.HelloServiceImpl...
    }
}

九、進階擴展點

  1. 動態屬性注入:在 postProcessBeforeInitialization 中根據 @Endpoint 屬性自動注入依賴 (如 Bus, EndpointRegistry)。
  2. 條件裝配:實現 ImportSelector 或 Condition,根據環境 (Profile、Property) 決定是否啟用命名空間。
  3. 嵌套元素支援:Schema 定義 <stdws:interceptors> 子元素,Parser 解析為列表注入 EndpointBeanProcessor。
  4. 註解屬性別名:支援 @AliasFor 風格屬性別名,兼容不同版本註解。

十、總結

Spring 自定義命名空間本質是 XML 到 BeanDefinition 的聲明式映射,結合 BeanFactoryPostProcessor (掃描註冊)、BeanPostProcessor (生命週期攔截)、ApplicationContextAware (上下文訪問),可實現從配置到運行時行為的完整閉環。本文實現的 stdws:annotation-endpoint 展示了:Schema 驗證 → 解析器 → 掃描器 → 後處理器 → 業務邏輯 的標準化擴展路徑,可直接應用於 CXF、Dubbo、gRPC 等 RPC 框架的 Spring 集成模塊開發。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved