最近因为系统需要接入了一个新的支付通道,一般来说都是使用 RestApi 来接入,但是本次接入的支付通道为境外支付,使用的 WebService ,对于WS我们在实际业务中基本上不会用到,所以查阅了一些资料,记录一下自己项目中使用的WS。
Web Service技术,能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成。依据Web Service规范实施的应用之间,无论它们所使用的语言、平台或内部协议是什么,都可以相互交换数据。
简单来说WebService就是一种跨编程语言和跨操作系统平台的远程调用技术。
XML和XSD 、 SOAP 、 WSDL 、 UDDI ,详细内容请参阅百度百科: Web Service Apache CXF是一个开源的Services框架,CXF帮助您利用Frontend编程 API 来构建和开发Services,像JAX-WS、JAX-RS。这些Services可以支持多种协议,比如:SOAP、XML/HTTP、RESTful HTTP或者CORBA,并且可以在多种传输协议上运行,比如:HTTP、JMS 或者JBI,
CXF大大简化了 Services 的创建,同时它可以天然地和Spring进行无缝集成。
详细内容请参阅官方: Apache CXF
JAX-WS全称:Java API for XML-Based Web Services。JAX-WS是一种编程模型,它通过支持将基于注释的标准模型用于开发Web Service应用程序和客户机来简化应用程序开发。
JAX-WS是Java程序设计语言一个用来创建Web服务的API。
接下来,我们以一个简单的示例来演示下,如何发布服务及如何进行服务调用。
spring-boot-cxf-service <dependencies>
<!--spring-boot-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--lombok懒人依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--test依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--cxf依赖 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.2.5</version>
</dependency>
<!--commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
复制代码 NBAPlayer.java @Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class NBAPlayer {
String name;
String team;
List<String> teams;
String description;
Position position;
@Override
public String toString() {
return "NBA现役球员" + name + ",司职" + position.value + "现效力于" + team + ",曾效力球队" + teams.toString();
}
}
复制代码 2. Positon.java public enum Position {
FORWARD("小前锋"),
SHOOTING("控球後衛");
String value;
Position(String value) {
this.value = value;
}
public String value() {
return value;
}
public static Position fromValue(String v) {
for (Position c : Position.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
复制代码 3. NBAPlayerSoapService.java @WebService(targetNamespace = WsConst.NAMESPACE_URI, name = "NBAPlayerSoap")
public interface NBAPlayerSoapService {
/**
* description 根据姓名获取球员信息
* date 2019/10/15
*
* @param NBAPlayerName
* @param NBAPlayerName
* @return cn.forward.springboot.cxf.service.entity.NBAPlayer
*/
@WebMethod(operationName = "getNBAPlayerByName")
NBAPlayer getNBAPlayerByName(@WebParam(name = "NBAPlayerName") String NBAPlayerName);
/**
* description 获取球员列表
* date 2019/10/15
*
* @return java.util.List<cn.forward.springboot.cxf.service.entity.NBAPlayer>
*/
@WebMethod
List<NBAPlayer> getNBAPlayerList();
}
复制代码 4. NBAPlayerSoapImpl.java @WebService(
targetNamespace = WsConst.NAMESPACE_URI, //wsdl命名空间
name = "NBAPlayerSoap", //portType名称 客户端生成代码时 为接口名称
serviceName = "NBAPlayerSoapService", //服务name名称
portName = "NBAPlayerPortName", //port名称
endpointInterface = "cn.forward.springboot.cxf.service.service.NBAPlayerSoapService")
public class NBAPlayerSoapImpl implements NBAPlayerSoapService {
@Override
public NBAPlayer getNBAPlayerByName(String name) {
List<NBAPlayer> nbaPlayers = getNBAPlayerList();
AtomicReference<NBAPlayer> player = new AtomicReference<>(nbaPlayers.get(new Random().nextInt(2)));
nbaPlayers.forEach(nbaPlayer -> {
if (nbaPlayer.getName().equals(name)) {
player.set(nbaPlayer);
}
});
return player.get();
}
@Override
public List<NBAPlayer> getNBAPlayerList() {
return generatorList();
}
public List<NBAPlayer> generatorList() {
List<NBAPlayer> resultList = new ArrayList<>();
NBAPlayer LBJ = new NBAPlayer();
LBJ.setName("勒布朗·詹姆斯");
LBJ.setTeam("洛杉矶湖人");
LBJ.setTeams(Arrays.asList("克里夫蘭騎士(2003−2010)", "邁阿密熱火(2010−2014)"));
LBJ.setPosition(Position.FORWARD);
LBJ.setDescription(LBJ.toString());
resultList.add(LBJ);
NBAPlayer WS = new NBAPlayer();
WS.setName("罗素·威斯布鲁克");
WS.setTeam("休斯頓火箭");
WS.setTeams(Arrays.asList("奧克拉荷馬雷霆 (2008-2019)"));
WS.setPosition(Position.SHOOTING);
WS.setDescription(WS.toString());
resultList.add(WS);
return resultList;
}
}
复制代码 5. WsConst.java public class WsConst {
public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice";
}
复制代码 6. CxfWebServiceConfig.java @Configuration
public class CxfWebServiceConfig {
//这里需要注意 由于springmvc 的核心类 为DispatcherServlet
//此处若不重命名此bean的话 原本的mvc就被覆盖了。可查看配置类:DispatcherServletAutoConfiguration
//一种方法是修改方法名称 或者指定bean名称
//这里需要注意 若beanName命名不是 cxfServletRegistration 时,会创建两个CXFServlet的。
//具体可查看下自动配置类:Declaration org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration
//也可以不设置此bean 直接通过配置项 cxf.path 来修改访问路径的
@Bean("cxfServletRegistration")
public ServletRegistrationBean dispatcherServlet() {
//注册servlet 拦截/ws 开头的请求 不设置 默认为:/services/*
return new ServletRegistrationBean(new CXFServlet(), "/NBA/*");
}
/**
* 申明业务处理类 当然也可以直接 在实现类上标注 @Service
* @author oKong
*/
@Bean
public NBAPlayerSoapService authorService() {
return new NBAPlayerSoapImpl();
}
/**
* 非必要项
*/
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
SpringBus springBus = new SpringBus();
return springBus;
}
/**
* 发布endpoint
*/
@Bean
public Endpoint endpoint(NBAPlayerSoapService NBAPlayerSoap) {
EndpointImpl endpoint = new EndpointImpl(springBus(), NBAPlayerSoap);
//发布地址
endpoint.publish("/player");
return endpoint;
}
}
复制代码 注意事项: 配置ServletRegistrationBean时,需要注意设置方法的名称或者bean的名称时,不要和默认的DispatcherServlet类重名了,会导致原先的mvc接口无法使用,因为被覆盖了。 修改访问的路径可以通过设置ServletRegistrationBean来修改,但同时,要注意需要设置bean的名称为cxfServletRegistration,不然会造成注册多个CXFServlet的。具体原因可查看自动配置类:org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration。 @SpringBootApplication
@Slf4j
public class SpringBootCxfServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCxfServiceApplication.class, args);
log.info("spring-boot-cxf-service启动!");
}
}
复制代码
查看输入日志
2019-10-15 15:49:17.964 INFO 16100 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2019-10-15 15:49:17.967 INFO 16100 --- [ main] .f.s.c.s.SpringBootCxfServiceApplication : Started SpringBootCxfServiceApplication in 3.35 seconds (JVM running for 4.454) 2019-10-15 15:49:17.969 INFO 16100 --- [ main] .f.s.c.s.SpringBootCxfServiceApplication : spring-boot-cxf-service启动! 复制代码
测试
访问 http://localhost:8080/NBA/player?wsdl验证:
spring-boot-cxf-client <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.5.17</version>
</dependency>
</dependencies>
复制代码 <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- cxf-codegen-plugin -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>3.2.5</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.basedir}/src/main/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>src/main/resources/wsdl/player.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/player.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
复制代码 提示: 将wsdl文件,放入main/resources/wsdl目录下。之后执行:mvn generate-sources命令,就会自动创建相应的类文件了。拷贝相应的类文件至src/java目录下即可。或者直接指定sourceRoot也是可以的 WsCont.java public class WsConst {
public static final String SERVICE_ADDRESS= "http://localhost:8080/NBA/player?wsdl";
}
复制代码 CxfClientConfig.java @Configuration
public class CxfClientConfig {
/**
* 采用代理方式
*
* @return NBAPlayerSoap
*/
@Bean
public NBAPlayerSoap createAuthorPortTypeProxy() {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(NBAPlayerSoap.class);
jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);
return (NBAPlayerSoap) jaxWsProxyFactoryBean.create();
}
/**
* 采用动态工厂方式 不需要指定服务接口
*/
@Bean
public Client createDynamicClient() {
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient(WsConst.SERVICE_ADDRESS);
return client;
}
}
复制代码 NBAPlayerController.java @RestController
@RequestMapping("/nba/player")
public class NBAPlayerController {
@Resource
NBAPlayerSoap nbaPlayerSoap;
/**
* description 根据球员名称获取球员信息
* date 2019/10/15
*
* @param nbaPlayerName
* @return cn.lqdev.webservice.NbaPlayer
*/
@GetMapping("/getNBAPlayerByName")
public NbaPlayer getNBAPlayerByName(@RequestParam("nbaPlayerName") String nbaPlayerName) {
return nbaPlayerSoap.getNBAPlayerByName(nbaPlayerName);
}
/**
* description 获取全部球员信息
* date 2019/10/15
* @return java.util.List<cn.lqdev.webservice.NbaPlayer>
*/
@GetMapping("/getNBAPlayerList")
public List<NbaPlayer> getNBAPlayerList() {
return nbaPlayerSoap.getNBAPlayerList();
}
}
复制代码 创建启动类,测试
@SpringBootApplication
@Slf4j
public class SpringBootCxfClientApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCxfClientApplication.class, args);
log.info("spring-boot-cxf-client启动!");
}
}
复制代码 访问: http://localhost:8081/nba/player/getNBAPlayerByName?nbaPlayerName=勒布朗·詹姆斯
验证结果:
{
description: "NBA现役球员勒布朗·詹姆斯,司职小前锋现效力于洛杉矶湖人,曾效力球队[克里夫蘭騎士(2003−2010), 邁阿密熱火(2010−2014)]",
name: "勒布朗·詹姆斯",
position: "FORWARD",
team: "洛杉矶湖人",
teams: [
"克里夫蘭騎士(2003−2010)",
"邁阿密熱火(2010−2014)"
]
}
复制代码 6.参考文章