SpringBoot集成WebService(wsdl)实践

2025-09-25 00:50

本文主要是介绍SpringBoot集成WebService(wsdl)实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringBoot集成WebService(wsdl)实践》文章介绍了SpringBoot项目中通过缓存IWebService接口实现类的泛型入参类型,减少反射调用提升性能的实现方案,包含依赖配置...

pom.xml

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
            <!-- 对版本没要求,建议跟我一样 -->
            <version>3.4.4</version>
        </dependency>

创建入口

ApplicationContextUtils.java

bean调用工具

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 创建日期:2024-07-01
 */
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
    //构造函数私有化,防止其它人实例化该对象
    private ApplicationContextUtils() {
    }

    private sbwxRsLtatic ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtils.applicationContext = applicationContext;
    }

    //通过name获取 Bean.(推荐,因为bean的name是唯一的,出现重名的bean启动会报错。)
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    //通过class获取Bean.(确保bean的name不会重复。因为可能会出现在不同包的同名bean导致获取到2个实例)
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean(这个是最稳妥的)
    public static <T> T getBean(String name, Class<T> clazz) {
        return applicationContext.getBean(name, clazz);
    }

    public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
        return applicationContext.getBeansOfType(clazz);
    }

}

JacksonUtils.java

Jackson 工具类

import com.fasterxml.jackson.annotation.jsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

/**
 * jackson 工具类
 */
public abstract class JacksonUtils {
    public static final ObjectMapper JSON = new ObjectMapper();
    public static final ObjectMapper XML = new XmlMapper();

    static {
        // json 配置
        JSON.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        JSON.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        JSON.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        JSON.registerModule(new JavaTimeModule());//处理java8新日期时间类型
        // xml 配置
        XML.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        XML.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        XML.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        XML.registerModule(new JavaTimeModule());//处理java8新日期时间类型
    }

}

IWebService.java

统一入口

/**
 * 统一 post 调用
 * 创建日期:2024-07-01
 */
public interface IWebService<T> {

    Object handle(T req);

}

WebServiceEntry.java

import lombok.extern.slf4j.Slf4j;
import org.springfrajsmework.stereotype.Service;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

/**
 * 创建日期:2024-07-01
 */
@Slf4j
@Service
@WebService
public class WebServiceEntry {

    /**
     * 通过实现了 IWebService 接口的 bean name 反射调用 handle 方法
     *
     * @param service   bean name
     * @param parameter XML 字符串请求参数
     */
    @WebMethod
    @SuppressWarnings("unchecked")
    public <T> String invoke(@WebParam(name = "service") String service, @WebParam(name = "parameter") String parameter) throws JsonProcessingException {
        IWebService<T> webService = (IWebService<T>) ApplicationContextUtils.getBean(service);

        // 通过缓存获取 IWebService 实现类的 handle 函数泛型类型入参,这样就不用每次请求都通过反射去获取入参,提升了程序性能。
        Class<T> parameterType = (Class<T>) WebServiceTypeCache.getParameterType(service);
        // 使用 Jackson-XML 将 XML 字符串转换为 Java 对象
        T reqObject = JacksonUtils.XML.readValue(parameter, parameterType);

        R<?> r;
        try {
            r = R.ok(webService.handle(reqObject));
        } catch (Exception e) {
            String message = e.getMessage();
            log.error(message, e);
            r = R.err(message);
        }
        return JacksonUtils.XML.writeValueAsString(r);
    }

}

WebServiceTypeCache.java

启动的时候把 IWebService实现类 handle函数的泛型入参写入缓存,在请求的时候直接通过缓存获取泛型入参的类型,减少每次请求的时候都使用反射获取泛型入参,提升程序性能。

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * 启动的时候把 IWebService实现类 handle函数的泛型入参写入缓存,在请求的时候直接通过缓存获取泛型入参的类型,
 * 减少每次请求的时候都使用反射获取泛型入参,提升程序性能。
 *
 * @since 2024-08-09
 */
@Component
public class WebServiceTypeCache implements ApplicationRunner {
    /**
     * 只能在启动的时候 put,运行的时候 get。不能在运行的时候 put,因为 HashMap 不是线程安全的。
     */
    private static final Map<String, Class<?>> typeCache = new HashMap<>();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Map<String, IWebService> beans = ApplicationContextUtils.getBeansOfType(IWebService.class);
        //循环map,forEach(key,value) 是最现代的方式,使用起来简洁明了。也可以用 for (Map.Entry<String, IWebService> entry : beans.entrySet()){}。
        beans.forEach((bean, type) -> {
            // AopProxyUtils.ultimateTargetClass 解决Spring Boot 使用 @Transactional 事务注解的问题。
            Class<?> beanClass = AopProxyUtils.ultimateTargetClass(type);
            // 获取 IWebService 实现类的泛型类型
            Type[] genericInterfaces = beanClass.getGenericInterfaces();
            for (Type genericInterface : genericInterfaces) {
                if (genericInterface instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
                    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

                    if (actualTypeArguments.length > 0) {
                        Class<?> parameterType = (Class<?>) actualTypeArguments[0];
                        //把泛型入参放入缓存。防止每次请求都通过反射获取入参,影响程序性能。
                        typeCache.put(bean, parameterType);
                    }
                }
            }
        });
    }

    /**
     * 通过缓存获取 IWebService 实现类 handle 函数的 泛型入参
     *
     * @param serviceName IWebService实现类的 bean name
     */
    public static Class<?> getParameterType(String serviceName) {
        return typeCache.get(serviceName);
    }

}

WebServiceConfig.java

配置类

有些依赖千万不要导错,所以我依赖都粘贴进来了。防止导错包。

import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.xml.ws.Endpoint;

/**
 * 创建日期:2024-07-01
 */
@Configuration
public class WebServiceConfig {

    @Bean(name = "cxfServlet")
    public ServletRegistrationBean<?> cxfServlet() {
        //urlMappings默认是:services
        return new ServletRegistrationBean<>(new CXFServlet(), "/services/*");
    }

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

    @Bean
    public Endpoint helloServiceEndpoint() {
        EndpointImpl endpoint = new EndpointImpl(springBus(), new WebServiceEntry());
        //services后面的urijs地址
        endpoint.publish("/WebServiceEntry");
        return endpoint;
    }

}

WebMvcConfig.java

web的配置类,因为增加了xml依赖,springboot会默认把json放到xml后面,因此要手动改回默认json。

import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        //引入 jackson-dataformat-xml 后,原本默认返回json变成了默认返回xml。因此这里要设置默认返回json
        configurer.defaultContentType(MediaType.APPLICATION_JSON);
    }

}

实现IWebService接口

如:WebServiceImpl

@Service("Hello")
public class Hello implements IWebService<HelloReq> {

    @Override
    public HelloRes handle(HelloReq req) {
        String name = req.getName();
        List<Work> works = req.getWorks();

        if (!StringUtils.hasText(name)) {
            throw new RuntimeException("Name 不能为空")php;
        }
        if (!CollectionUtils.isEmpty(works)) {
            for (Work work : works) {
                String workName = work.getWorkName();
                log.info("workName={}", workName);
            }
        }

        HelloRes res = new HelloRes();
        res.setName(name);
        res.setAge(18);
        return res;
    }

}

HelloReq.java

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Data;

import java.util.List;

@Data
@JacksonXmlRootElement(localName = "Params")
public class HelloReq {

    @JacksonXmlProperty(localName = "Name")
    private String name;

    @JacksonXmlElementWrapper(localName = "Works")
    @JacksonXmlProperty(localName = "Work")
    private List<Work> works;

}

HelloRes.java

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.Data;

@Data
public class HelloRes {

    @JacksonXmlProperty(localName = "Name")
    private String name;

    @JacksonXmlProperty(localName = "Age")
    private Integer age;

}

Work.java

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Data;

@Data
@JacksonXmlRootElement(localName = "Work")
public class Work {

    @JacksonXmlProperty(localName = "WorkName")
    private String workName;

}

统一返回类

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 统一返回类
 * 创建日期:2024-07-01
 */
@Data
@NoArgsConstructor(Access = lombok.AccessLevel.PRIVATE)
@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
@JacksonXmlRootElement(localName = "R")
public class R<T> {
    @JacksonXmlProperty(localName = "Code")
    private Integer code;
    @JacksonXmlProperty(localName = "Message")
    private String message;
    @JacksonXmlProperty(localName = "Data")
    private T data;

    public static <T> R<T> ok() {
        return ok(null);
    }

    public static <T> R<T> ok(T data) {
        return new R<>(200, "success", data);
    }

    public static <T> R<T> err(String message) {
        return err(400, message);
    }

    public static <T> R<T> err(Integer code, String message) {
        return new R<>(code, message, null);
    }

}

启动SpringBoot

访问

http://localhost:8080/services/WebServiceEntry?wsdl
  • 会出现如下所示界面

SpringBoot集成WebService(wsdl)实践

用soapUI去调用接口

ws = "http://ws.bsjkt.bsoft.com/"这里每个人可能不一样
service = bean name
parameter = XML 请求参数
  • 入参:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.springbootwebservicedemo.fu.com/">
    <soapenv:Header/>
    <soapenv:Body>
        <ws:invoke>
            <service>Hello</service>
            <parameter>
                <![CDATA[
            <Params><Name>哈哈</Name><Works><Work><WorkName>Java</WorkName&http://www.chinasem.cngt;</Work>
                </Works>
            </Params>
        ]]>
    </parameter>
</ws:invoke>
</soapenv:Body>
</soapenv:Envelope>
  • 出参:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <ns2:invokeResponse xmlns:ns2="http://ws.springbootwebservicedemo.fu.com/">
            <return><R><Code>200</Code><Message>success</Message><Data><Name>哈哈</Name><Age>18</Age></Data></R></return>
        </ns2:invokeResponse>
    </soap:Body>
</soap:Envelope>

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于SpringBoot集成WebService(wsdl)实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156014

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础