spring boot 集成 cxf 实现 webservice

服务端实现

引入依赖
 <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>jakarta.xml.ws</groupId>
    <artifactId>jakarta.xml.ws-api</artifactId>
</dependency>
服务类

使用 jax-ws 注解

@WebService(name = "UserService")
public interface UserService {

    @WebMethod
    String getUser(@WebParam(name = "userId")String userId);
}

@Service
@WebService(
        serviceName = "UserService",
        targetNamespace = "http://ws.bootintegration.example.com/",
        // 需要填targetName,不然报错,  No operation was found with the name, 一般为接口所在包名倒序,不是实现类的包名
        endpointInterface = "com.example.bootintegration.client.ws.UserService"
        //一般是接口所在包名
)
@WebPath(path = "/userService")
@Slf4j
public class UserServiceImpl implements UserService {
    @Override
    public String getUser(String userId) {
        log.info("ws server recv: {}", userId);
        return userId + " hello";
    }
}
服务发布
  • 自定义 @WebPath 实现webservice自动注册
  • 实现 webservice 指定专门端口(一般的,endpoint.publish 默认将 webservice服务发布在和web端口一致)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WebPath {

    /**
     * 发布地址
     * @return
     */
    String path();
}

@Configuration
@Slf4j
public class CxfConfig {
    @Autowired
    private SpringBus bus;

    @Autowired
    private ApplicationContext applicationContext;

    @Value("${cxf.port:9999}")
    private int webServicePort = 9999;
    @Value("${cxf.context:/soap-api}")
    private String webServiceContextPath;
    @Value("${cxf.schema:http}")
    private String webServiceSchema;


    /* *
     * 设置 webservice 上下文根路径
     *
     * @return*/
    @Bean
    public ServletRegistrationBean<CXFServlet> registrationBean() {
        return new ServletRegistrationBean<>(new CXFServlet(), webServiceContextPath + "/*");
    }


    @PostConstruct
    public void userServiceEndPoint() {
        String[] beanNames = applicationContext.getBeanNamesForAnnotation(WebPath.class);
        for (String beanName : beanNames) {
            Object bean = applicationContext.getBean(beanName);
            WebPath annotation = bean.getClass().getAnnotation(WebPath.class);
            if (!Objects.isNull(annotation)) {
                String path = annotation.path();
                //默认端口,这种是使用cxf注册
                Endpoint endpoint = new EndpointImpl(bus, applicationContext.getBean(beanName));
                endpoint.publish(path);
                //这种是使用jax-ws java 原生注册
//                Endpoint.publish("http://localhost:8080/cxfbt/userService?wsdl", applicationContext.getBean(beanName));
                log.info("auto publish webservice: {}", path);
            }
        }
        log.info("webservice starts at port: {}, path: {}", webServicePort, webServiceContextPath);

    }

/*
    @Bean
    public Endpoint userServiceEndPoint() {
        EndpointImpl endpoint = new EndpointImpl(bus, userService);
        //自动绑定 server.port 端口
        endpoint.publish("/userService");
        return endpoint;
    }*/

    //一般的,webservice 端口和 web 端口一致: server.port
    //======================以下是给 webservice服务提供 指定端口=============//
    // WebService 专用端口(如 9999)

    /**
     * 这种方式是同时启动tomcat 多个端口,如8080,9999,但是这两个都可以访问wsdl
     *
     * @return
     */
    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        // 添加额外连接器
        tomcat.addAdditionalTomcatConnectors(createWebServiceConnector());
        return tomcat;
    }

    private Connector createWebServiceConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setPort(webServicePort);
        connector.setScheme(webServiceSchema);
        return connector;
    }

    /**
     * 1. 给tomcat 启一个额外的端口,这里是 9999
     * 2.通过 filter 强制 9999 给 webservice专门使用
     */
    @Component
    public class CxfFilter extends HttpFilter {
        @Override
        protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
            if (request.getRequestURI().startsWith(webServiceContextPath) && request.getLocalPort()!=webServicePort){
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
            chain.doFilter(request, response);
        }
    }
}


cxf 客户端

cxf 调用分两种

  • 代理对象
  • 动态客户端
生成客户端代码

使用 cxf-codegen-plugin 插件生成,引入依赖

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>3.5.5</version>
    <executions>
        <execution>
            <id>generate-sources</id>
            <phase>generate-sources</phase>
            <configuration>
                <sourceRoot>${project.basedir}/src/main/java</sourceRoot>
                <encoding>UTF-8</encoding>
                <wsdlOptions>
                    <!-- 指定wsdl文件 -->
                    <wsdlOption>
                        <!-- 也可以指定 http-->
                        <wsdl>http://localhost:9999/soap-api/userService?wsdl</wsdl>
<!--                                    <wsdl>src/main/resources/wsdl/userService.wsdl</wsdl>-->
<!--                                    <wsdlLocation>classpath:wsdl/userService.wsdl</wsdlLocation>-->
                    </wsdlOption>
                </wsdlOptions>
            </configuration>
            <goals>
                <goal>wsdl2java</goal>
            </goals>
        </execution>
    </executions>
</plugin>

在 idea 中执行 clean generate-sources, 就可以生成客户端代码,将其 copy 到自己的包内

代理调用

public interface WsServiceConst {
    String SOAP_USER = "http://localhost:9999/soap-api/userService?wsdl";
}


@Configuration
public class CxfClientConfig {


    /**
     * 通过 cxf-codegen-plugin 插件生成客户端代码,创建代理
     * 每个 wsdl 都需要创建一个代码
     * @return
     */
    @Bean("userServiceProxy")
    public UserService userServiceProxy(){
        JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
        //UserService 客户端生成的服务名称,注意不要引错
        factoryBean.setServiceClass(UserService.class);
        factoryBean.setAddress(SOAP_USER);
        return (UserService) factoryBean.create();
    }

}

动态 client 调用
public class DynamicClient {


    private static Map<String, Client> clientMap = new ConcurrentHashMap<>();


    /**
     * 使用动态工厂
     *
     * @param serviceName
     * @return
     */
    public static Client getClient(String serviceName) {
        if (clientMap.containsKey(serviceName)) {
            return clientMap.get(serviceName);
        }
        return clientMap.computeIfAbsent(serviceName, s -> {
            JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
            return dcf.createClient(SOAP_USER);
        });
    }
}

测试

@RestController
@Slf4j
public class CxfClientController {

    @Resource(name = "userServiceProxy")
    private UserService userService;

    /**
     * 使用代理对象
     *
     * @return
     */
    @GetMapping("/ws/hello")
    public String invoke() {
        String user = userService.getUser("tom");
        log.info("ws result: {}", user);
        return "success";
    }

    /**
     * 使用动态工厂
     *
     * @return
     */
    @GetMapping("/ws/hello2")
    public String invoke2() throws Exception {
        Client client = DynamicClient.getClient(WsServiceConst.SOAP_USER);
        Object[] response = client.invoke("getUser", "tom");
        if (response != null && response.length > 0) {
            log.info("ws result: {}", response[0]);
        } else {
            log.info("ws client invoke error: {}", JSON.toJSONString(response));
        }

        return "success";
    }
}

结果:

c.e.b.ws.impl.UserServiceImpl            : ws server recv: tom
c.e.b.controller.CxfClientController     : ws result: tom hello
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐