开发者

SpringBoot集成WebService(wsdl)实践

目录
  • pom.XML
  • 创建入口
    • ApplicationContextUtils.Java
    • JacksonUtils.java
    • IWebService.java
    • WebServiceEntry.java
    • WebServiceTypeCache.java
    • WebServiceConfig.java
    • WebMvcConfig.java
    • 实现IWebService接口
    • HelloReq.java
    • HelloRes.java
    • Work.java
    • 统一返回类
  • 启动SpringBoot
    • 用soapUI去调用接口
  • 总结

    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.devze.comgt;</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>
    

    总结

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

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜