SpringBoot集成WebService(wsdl)实践
作者:Meta39
文章介绍了Spring Boot项目中通过缓存IWebService接口实现类的泛型入参类型,减少反射调用提升性能的实现方案,包含依赖配置、工具类封装、接口定义及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 static 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.springframework.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后面的uri地址 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 不能为空"); } 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
- 会出现如下所示界面
用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></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>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。