Spring Boot + CXF实践

本文介绍如何在SpringBoot项目中使用CXF发布WebService,包括添加Maven依赖、定义业务接口与实现类、配置发布Web服务等步骤,并提供访问验证方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Red Hat Document文档整理的挺全乎的,可以参考:

Apache CXF 开发指南 | Red Hat Product Documentation

在Spring Boot(2.5.4)中集成CXF发布web服务(jax-ws)

步骤1:添加Maven依赖

因为cxf starter依赖版本的问题,踩过好几个坑,大家如果遇到没有明确解决线索的问题,可以尝试用最新版本试试。

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
    <version>3.4.4</version>
</dependency>

步骤2:定义业务接口

实测接口层是可以去掉的

package com.etoak.ws;

import javax.jws.WebService;

@WebService(name = "Hello01", targetNamespace = "http://ws.etoak.com")
public interface IWsBusi01 {

    String sayHello01();
}

步骤3:定义业务实现类

package org.etoak;

import com.etoak.ws.IWsBusi01;

import javax.jws.WebService;

/**
 * Hello world!
 *
 */
@WebService(serviceName = "Hello01", targetNamespace = "http://ws.etoak.com", endpointInterface = "com.etoak.ws.IWsBusi01")
public class WsBusi01Impl implements IWsBusi01 {

    @Override
    public String sayHello01() {
        System.out.println(">>>>>>>>>sayHello01");

        return "sayHello01";
    }
}

步骤4:发布Web服务

@Configuration
public class WebServiceConfig {
    @Autowired
    private Bus bus;

    @Bean
    public Endpoint endpoint1() {
        EndpointImpl endpoint = new EndpointImpl(bus, new WsBusi01Impl());
        //Web服务名称
        endpoint.publish("/Hello01");
        return endpoint;
    }


    @Bean
    public Endpoint endpoint2() {
        EndpointImpl endpoint = new EndpointImpl(bus, new WsBusi02Impl());
        endpoint.publish("/Hello02");
        return endpoint;
    }


    /*@Bean (name = "cxfServlet")
    public ServletRegistrationBean cxfServlet() {
        return new ServletRegistrationBean(new CXFServlet(), "/webservice/*");
    }

    @Bean (name = Bus.DEFAULT_BUS_ID)
    public SpringBus getSpringBus() {
        return new SpringBus();
    }


    // 手动发布
    @Bean
    public EndpointImpl serviceEndpoint() {
        EndpointImpl endpoint = null;
        
        // 获取配置的需要发布服务的列表
        // ...

        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        for (迭代配置的服务列表) {
            // 实现类的全限定名称
            String clazzName;
            // 配置的自定义服务名
            String webServiceAnnotationName;
            try {
                Class<?> clazz = Class.forName(clazzName);
                Object o = clazz.newInstance();
                // 判断是否有@WebService注解
                WebService webServiceAnnotation = clazz.getAnnotation(WebService.class);
                if (webServiceAnnotation != null) {
                    if (StringUtils.hasLength(webServiceAnnotationName)) {
                        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(EndpointImpl.class);
                        endpoint = new EndpointImpl((SpringBus) applicationContext.getBean(Bus.DEFAULT_BUS_ID), o);
                        endpoint.getInInterceptors().add(authInterceptor);
                        endpoint.publish(webServiceAnnotationName);

                        defaultListableBeanFactory.registerBeanDefinition(webServiceAnnotationName, beanDefinitionBuilder.getBeanDefinition());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return endpoint;
    }*/
}

步骤5:启动Spring Boot

访问:http://localhost:8080/services/Hello01?wsdl

服务发布成功

===============================================

实际上当时业务需求要稍微复杂一点,拆拆成了SpringBoot工程和业务工程,SpringBoot工程作为单独的Web应用启动,加载每一个业务工程jar包。所以在打包SpringBoot工程的时候,需要将第三方jar包分离出来,具体实现参考《关于打包jar/war_wsdhla的专栏-优快云博客

再加上XXL,最终架构如下:

==============================================

配置文件方式:

1 增加配置文件application-cxf.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:jaxws="http://cxf.apache.org/jaxws"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
    <jaxws:endpoint implementor="com.xxx.XxxImpl" address="/demoServ"/>
</beans>

2 启动类读取配置文件

@ImportResource(locations = {"classpath:application-cxf.xml"})

意外的元素:将XML字符串转为实体类_xml转实体类-优快云博客


使用HttpClient请求WebService

前提条件是,服务端、客户端代码(主要是入参、出参模型类)均使用cxf工具包生成的:

服务端:wsdl2java -server -impl -encoding utf-8 xxx.wsdl

客户端:wsdl2java -client -encoding utf-8 xxx.wsdl

代码:

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.xml.soap.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPMessage;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
    // 组装实体对象

    Envelope envelope = new Envelope();
    Body body = new Body();
    body.setInputParameters(实体对象);
    envelope.setBody(body);

    String requestXml = YkfcTzUtils.bo2Xml(envelope);

    HttpPost post = new HttpPost("http://localhost:8080/services/xxxsrv");
    StringEntity entity = new StringEntity(requestXml);
    post.setEntity(entity);

    // 执行请求并获得响应
    HttpResponse response = httpClient.execute(post);

    // 打印响应状态
    System.out.println("Response Code : " + response.getStatusLine().getStatusCode());

    // 获取并打印响应内容
    String responseBody = EntityUtils.toString(response.getEntity());
    System.out.println(responseBody);
} catch (Exception e) {
    log.error("出现异常:", e);
}

依赖:

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
            <version>3.4.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>

常见异常:

1 客户端异常:

com.sun.istack.SAXException2: 由于类型 "com.etoak.xxxsrv.InputParameters" 缺少 @XmlRootElement 注释, 无法将该类型编集为元素

解决:

@XmlRootElement(name = "InputParameters")
public class InputParameters {

2 服务端异常:

org.apache.cxf.binding.soap.SoapFault: "http://xxx.com/XxxSrv", the namespace on the "InputParameters" element, is not a valid SOAP version.

 解决:

客户端少了两个类

Envelope.java

import com.fasterxml.jackson.annotation.JsonProperty;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Envelope", propOrder = {
        "Header",
        "Body"
})
@XmlRootElement(name="Envelope", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
public class Envelope {

    @XmlElement(name = "Header", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
    @JsonProperty("Header")
    protected Object Header;

    @XmlElement(name = "Body", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
    @JsonProperty("Body")
    protected Body Body;

    public Object getHeader() {
        return Header;
    }

    public void setHeader(Object header) {
        Header = header;
    }

    public Body getBody() {
        return Body;
    }

    public void setBody(Body body) {
        Body = body;
    }
}

Body.java 

import com.fasterxml.jackson.annotation.JsonProperty;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Body", propOrder = {
        "InputParameters"
})
@XmlRootElement(name="Body", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
public class Body {

    @XmlElement(name = "InputParameters", required = true, nillable = true)
    @JsonProperty("InputParameters")
    protected InputParameters InputParameters;

    public InputParameters getInputParameters() {
        return InputParameters;
    }

    public void setInputParameters(InputParameters inputParameters) {
        InputParameters = inputParameters;
    }
}

注意:

目前还发现一个问题,客户端工程中,当Envelope和Body,与根节点(如示例中的InputParameters)不在一个package下时,会报:

Response Code : 500
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring>Unexpected element InputParameters found.   Expected {http://xxx.com/XxxSrv}InputParameters.</faultstring></soap:Fault></soap:Body></soap:Envelope>

很奇怪,按理说客户端类再哪个包下无所有,而且客户端工程编译,打包,运行都正常,没有看到源码中有哪处是限制了Envelope和Body包路径的,目前没有找到原因。

只能放在一个目录下。

3 服务端异常:

javax.xml.bind.UnmarshalException: 意外的元素 (uri:"", local:"SOURCESYSTEMID")。所需元素为<{http://xxx.com/MsgHeader}TOKEN>,<{http://xxx.com/MsgHeader}CURRENT_PAGE>, ... ...
org.apache.cxf.interceptor.Fault: Unexpected element InputParameters found.   Expected {http://xxx.com/XxxSrv}InputParameters.

解决:

上述问题是因为在客户端相应的包路径下缺少package-info.java

@javax.xml.bind.annotation.XmlSchema(namespace = "http://xxx.com/MsgHeader", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.xxx.msgheader;


@javax.xml.bind.annotation.XmlSchema(namespace = "http://xxx.com/XxxSrv", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.xxx.xxxsrv;

4 服务端异常:

org.apache.cxf.interceptor.Fault: Unmarshalling Error: 意外的元素 (uri:"http://xxx.com/XxxSrv", local:"MSGHEADER")。所需元素为<{}INPUTCOLLECTION>,<{}MSGHEADER> 

解决:

这是因为服务端相应的包路径下缺少package-info.java

5、服务端是cxf,客户端是axis2,同时报错

服务端

javax.xml.bind.UnmarshalException: 意外的元素 (uri:"", local:"InputParameters")。所需元素为<{http://xxx.com/XxxSrv}INPUTCOLLECTION>,<{http://xxx.com/XxxSrv}MSGHEADER>

客户端

org.apache.axis2.AxisFault: Unmarshalling Error: 意外的元素 (uri:"", local:"InputParameters")。所需元素为<{http://xxx.com/XxxSrv}INPUTCOLLECTION>,<{http://xxx.com/XxxSrv}MSGHEADER>

解决:

因为调用的是平台的api,最终发现组装的xml结构有问题,改正确就没有问题了。

6、客户端异常:

OutputParameters outputParameters = xml2Bo(data, OutputParameters.class);

Http响应报文解析成实体类时,报上面的异常, 

javax.xml.bind.UnmarshalException: 意外的元素 (uri:"http://xxx.com/XxxSrv", local:"OutputParameters")。所需元素为(none)

解决:

@XmlRootElement(name = "OutputParameters")
public class OutputParameters {


msgheaderxxxsrv结果
服务端xxx.msgheader;xxxsrv;正常
客户端xxx.msgheader;xxxsrv;正常
服务端当前包(客、服不一样)当前包(客、服不一样)正常
客户端当前包(客、服不一样)当前包(客、服不一样)正常
服务端当前包(客、服一样)当前包(客、服一样)正常
客户端当前包(客、服一样)当前包(客、服一样)正常
服务端任意包(客、服一样)任意包(客、服一样)正常
客户端任意包(客、服一样)任意包(客、服一样)正常
服务端任意包(客、服不一样)任意包(客、服不一样)正常
客户端任意包(客、服不一样)任意包(客、服不一样)正常
服务端存在存在正常
客户端不存在不存在报错

javax.xml.bind.UnmarshalException: 意外的元素 (uri:"http://xxx.com/XxxSrv", local:"ESB_FLAG")。所需元素为<{}ESB_RETURN_MESSAGE>,<{}BIZ_SERVICE_FLAG>,<{}BIZ_RETURN_CODE>,<{}ESB_FLAG>,<{}RESPONSECOLLECTION>,<{}BIZ_RETURN_MESSAGE>,<{}ESB_RETURN_CODE>,<{}ERRORCOLLECTION>,<{}INSTANCE_ID>

服务端存在存在正常
客户端不存在存在正常
服务端不存在不存在正常
客户端不存在不存在正常
服务端不存在不存在正常
客户端不存在存在报错javax.xml.bind.UnmarshalException: 意外的元素 (uri:"", local:"ESB_FLAG")。所需元素为<{http://xxx.com/XxxSrv}ESB_RETURN_MESSAGE>,<{http:/xxx.com/XxxSrv}BIZ_SERVICE_FLAG>,<{http://xxx.com/XxxSrv}BIZ_RETURN_CODE>,<{http://xxx.com/XxxSrv}ESB_FLAG>,<{http://xxx.com/XxxSrv}RESPONSECOLLECTION>,<{http://xxx.com/XxxSrv}ERRORCOLLECTION>,<{http://xxx.com/XxxSrv}INSTANCE_ID>,<{http://xxx.com/XxxSrv}BIZ_RETURN_MESSAGE>,<{http://xxx.com/XxxSrv}ESB_RETURN_CODE>
服务端不存在存在正常
客户端不存在存在正常
服务端存在不存在正常
客户端不存在不存在正常
服务端不存在不存在正常
客户端存在不存在正常
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wsdhla

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值