上一章节,讲解了如何使用
Spring-WS
构建WebService
服务。其实,建立WebService
的方式有不少的,今天来看看如何使用apache cxf
来构建及调用WebService
服务。html
Apache CXF
是一个开源的Services
框架,CXF
帮助您利用Frontend
编程 API 来构建和开发Services,像JAX-WS
、JAX-RS
。这些Services
能够支持多种协议,好比:SOAP
、XML/HTTP
、RESTful HTTP
或者CORBA
,而且能够在多种传输协议上运行,好比:HTTP
、JMS
或者JBI
,CXF大大简化了 Services 的建立,同时它能够自然地和Spring进行无缝集成。java
如下是官网给出的介绍:https://github.com/apache/cxfgit
最经常使用的是使用cxf
开发web-service
。自己是基于JAX-WS
规范来实现的。固然,自己CXF
也实现了JAX-RS
规范来实现RESTFul Service
。github
JAX-WS
全称:Java API for XML-Based Web Services
。JAX-WS
是一种编程模型,它经过支持将基于注释的标准模型用于开发Web Service
应用程序和客户机来简化应用程序开发。web
JAX-WS
是Java程序设计语言一个用来建立Web服务的API。spring
Java
语言定义远程调用所须要实现的接口SEI(service endpoint interface)
,并提供相关的实现,经过调用JAX-WS
的服务发布接口就能够将其发布为WebService
接口。JAX-WS
的API建立一个代理(用本地对象来替代远程的服务)来实现对于远程服务器端的调用。固然JAX-WS
也提供了一组针对底层消息进行操做的API调用,你能够经过Dispatch
直接使用SOAP消息或XML消息发送请求或者使用Provider处理SOAP或XML消息。
JAX-WS
提供了一系列的注解,能够对WebService
的接口规范化。如下介绍下最经常使用的几个注解。apache
@WebService:用于将Java类标记为实现Web Service
或者将服务端点接口 (SEI) 标记为实现Web Service
接口。 其包含的属性有:编程
Web Service
的服务名称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串)WebService.name+Port
@WebMethod:表示做为一项Web Service
操做的方法。仅支持在使用@WebService
注解的类上使用@WebMethod
注解。安全
@WebParam:用于定制从单个参数至Web Service
消息部件和XML
元素的映射。springboot
其余注解,能够查看:WebService注解总结
为了有个直观感觉,你们能够看看如下这个wsdl文件,对应以上各注解属性的值(加了前缀oKong
)。
//@WebService 属性示例 @WebService(targetNamespace = 'http://www.lqdev.cn/webservice' ,name = "oKongName", serviceName="oKongServiceName", portName = "oKongPortName",endpointInterface="cn.lqdev.learning.springboot.cxf.service.AuthorService") //@webMethod @WebParam 经常使用属性示例 @WebMethod(operationName="oKongOperationName",action="oKongAction") String getAuthorName(@WebParam(name = "paramName") String name);
标记的有点花,⊙﹏⊙‖∣。你们能够本身对照下。
接下来,咱们以一个简单的示例来演示下,如何发布服务及如何进行服务调用。
建立一个工程:spring-boot-cxf-service
.
0.引入CXF的POM文件
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.2.5</version> </dependency>
1.建立实体,按JAX-WS
规范,建立接口及其实现类。 AuthorDto.java
/** * 做者信息实体 * @author oKong * */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class AuthorDto { String name; List<String> hobby; String birthday; String description; Sex sex; }
Sex.java
性别枚举类
/** * 性别枚举类 * @author oKong * */ public enum Sex { MALE("male"), FEMALE("female"); String value; Sex(String value) { this.value = value; } public String value() { return value; } public static Sex fromValue(String v) { for (Sex c : Sex.values()) { if (c.value.equals(v)) { return c; } } throw new IllegalArgumentException(v); } }
AuthorService.java
接口类
/** * 建立服务接口 * @author oKong * */ @WebService(targetNamespace = WsConst.NAMESPACE_URI ,name = "authorPortType") public interface AuthorService { /** * 根据名称获取做者信息 * @author 做者:oKong */ @WebMethod(operationName="getAuthorByName") AuthorDto getAuthor(@WebParam(name = "authorName") String name); /** * 获取做者列表信息 * @author oKong */ @WebMethod List<AuthorDto> getAuthorList(); /** * 返回字符串测试 * @author oKong */ String getAuthorString(@WebParam(name = "authorName")String name); }
AuthorServiceImpl.java
接口实现类
@WebService( targetNamespace = WsConst.NAMESPACE_URI, //wsdl命名空间 name = "authorPortType", //portType名称 客户端生成代码时 为接口名称 serviceName = "authorService", //服务name名称 portName = "authorPortName", //port名称 endpointInterface = "cn.lqdev.learning.springboot.cxf.service.AuthorService")//指定发布webservcie的接口类,此类也须要接入@WebService注解 public class AuthorServiceImpl implements AuthorService{ @Override public AuthorDto getAuthor(String name) { AuthorDto author = new AuthorDto(); author.setBirthday("1990-01-23"); author.setName("姓名:" + name); author.setSex(Sex.MALE); author.setHobby(Arrays.asList("电影","旅游")); author.setDescription("描述:一枚趔趄的猿。如今时间:" + new Date().getTime()); return author; } @Override public List<AuthorDto> getAuthorList() { List<AuthorDto> resultList = new ArrayList<>(); AuthorDto author = new AuthorDto(); author.setBirthday("1990-01-23"); author.setName("姓名:oKong"); author.setSex(Sex.MALE); author.setHobby(Arrays.asList("电影","旅游")); author.setDescription("描述:一枚趔趄的猿。如今时间:" + new Date().getTime()); resultList.add(author); resultList.add(author); return resultList; } @Override public String getAuthorString(String name) { AuthorDto author = getAuthor(name); return author.toString(); } }
注意:相关注解能够查看章节:经常使用注解介绍
主要是接口实现类的@WebService
对应属性值都要wsdl文件的映射关系。
@WebService( targetNamespace = WsConst.NAMESPACE_URI, //wsdl命名空间 name = "authorPortType", //portType名称 客户端生成代码时 为接口名称 serviceName = "authorService", //服务name名称 portName = "authorPortName", //port名称 endpointInterface = "cn.lqdev.learning.springboot.cxf.service.AuthorService")//指定发布webservcie的接口类,此类也须要接入@WebService注解
2.建立常量类,配置类,设置访问uri路径等。
WsConst.java
/** * 常量类 * @author oKong * */ public class WsConst { public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice"; }
CxfWebServiceConfig.java
/** * cxf配置类 * @author oKong * */ @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(), "/ws/*"); } /** * 申明业务处理类 固然也能够直接 在实现类上标注 @Service * @author oKong */ @Bean public AuthorService authorService() { return new AuthorServiceImpl(); } /* * 非必要项 */ @Bean(name = Bus.DEFAULT_BUS_ID) public SpringBus springBus() { SpringBus springBus = new SpringBus(); return springBus; } /* * 发布endpoint */ @Bean public Endpoint endpoint(AuthorService authorService) { EndpointImpl endpoint = new EndpointImpl(springBus(), authorService); endpoint.publish("/author");//发布地址 return endpoint; } }
注意事项:
ServletRegistrationBean
时,须要注意设置方法的名称或者bean的名称时,不要和默认的DispatcherServlet
类重名了,会致使原先的mvc
接口没法使用,由于被覆盖了。ServletRegistrationBean
来修改,但同时,要注意须要设置bean的名称为cxfServletRegistration
,否则会形成注册多个CXFServlet
的。具体缘由可查看自动配置类:org.apache.cxf.spring.boot.autoconfigure.CxfAutoConfiguration
。因此,修改访问路径还能够经过配置项:cxf.path
来设置。其默认的访问url为:/services
3.建立启动类,同时启动应用。
/** * cxf服务发布示例 * @author oKong * */ @SpringBootApplication @Slf4j public class CxfServiceApplication { public static void main(String[] args) throws Exception { SpringApplication.run(CxfServiceApplication.class, args); log.info("spirng-boot-cxf-service-chapter34启动!"); } }
启动后,能够从控制台看见能够访问的url路径信息。
2018-11-10 22:06:40.898 INFO 46364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'CXFServlet' to [/ws/*] 2018-11-10 22:06:40.899 INFO 46364 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
访问:http://127.0.0.1:8080/ws/author?wsdl ,验证是否发布成功。
自此,webService
发布成功了。
建立一个客户端工程:spring-boot-cxf-client
。
0.引入cxf依赖。
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.2.5</version> </dependency>
1.建立wsdl
文件,同时利用插件:cxf-codegen-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.build.directory}/generated/cxf</sourceRoot> <wsdlOptions> <wsdlOption> <wsdl>src/main/resources/wsdl/author.wsdl</wsdl> <wsdlLocation>classpath:wsdl/author.wsdl</wsdlLocation> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin>
将wsdl
文件,放入main/resources/wsdl
目录下。以后执行:mvn generate-sources
命令,就会自动建立相应的类文件了。拷贝相应的类文件至src/java
目录下便可。或者直接指定sourceRoot
也是能够的。
2.建立调用的配置类,这里演示两种方式。
WsConst.java
/** * 常量类 * @author oKong * */ public class WsConst { public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice"; public static final String SERVICE_ADDRESS= "http://127.0.0.1:8080/ws/author?wsdl"; }
CxfClinetConfig.java
/** * 配置类 * * @author oKong * */ @Configuration public class CxfClientConfig { /** * 以接口代理方式进行调用 AuthorPortType接口 */ @Bean("cxfProxy") public AuthorPortType createAuthorPortTypeProxy() { JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean(); jaxWsProxyFactoryBean.setServiceClass(AuthorPortType.class); jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);//服务地址:http://127.0.0.1:8080/ws/autho return (AuthorPortType) jaxWsProxyFactoryBean.create(); } /* * 采用动态工厂方式 不须要指定服务接口 */ @Bean public Client createDynamicClient() { JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance(); Client client = dcf.createClient(WsConst.SERVICE_ADDRESS); return client; } }
注意:除了使用JaxWsProxyFactoryBean
和JaxWsDynamicClientFactory
调用外,还能够直接使用自动生成的AuthorService
类直接调用的,此类继承至javax.xml.ws.Service
。 如:
/* * 直接调用 */ @Bean("jdkProxy") public AuthorPortType createJdkService() { AuthorService authorService = new AuthorService(); return authorService.getAuthorPortName(); }
其实,最后都是使用AuthorPortType
进行调用的。
3.建立控制层,进行调用示例。
/** * 调用示例 * @author oKong * */ @RestController @RequestMapping("/cxf") public class DemoController { @Autowired Client client; @Autowired @Qualifier("cxfProxy") AuthorPortType authorPort; @GetMapping("/getauthorstring") public String getAuthorString(String authorName) { return authorPort.getAuthorString(authorName); } @GetMapping("/getauthor") public AuthorDto getAuthor(String authorName) { return authorPort.getAuthorByName(authorName); } @GetMapping("/getauthorlist") public List<AuthorDto> getAuthorList() { return authorPort.getAuthorList(); } @GetMapping("/dynamic/{operation}") public Object getAuthorStringByDynamic(@PathVariable("operation")String operationName, String authorName) throws Exception { //这里就简单的判断了 Object[] objects = null; // client.getEndpoint().getBinding().getBindingInfo().getOperations() if ("getAuthorList".equalsIgnoreCase(operationName)) { objects = client.invoke(operationName); } else if ("getAuthorString".equalsIgnoreCase(operationName)) { objects = client.invoke(operationName, authorName); } else if ("getAuthorByName".equalsIgnoreCase(operationName)) { objects = client.invoke(operationName, authorName); } else { throw new RuntimeException("无效的调用方法"); } return objects != null && objects.length > 0 ? objects[0] : "返回异常"; } }
4.编写启动类,同时制定应用端口为:8090。
/** * cxf-客户端调用示例 * * @author oKong * */ @SpringBootApplication @Slf4j public class CxfClientApplication { public static void main(String[] args) throws Exception { SpringApplication.run(CxfClientApplication.class, args); log.info("spring-boot-cxf-client-chapter34启动!"); } }
端口号配置:
server.port=8090
5.启动应用,依次访问。查看是否调用成功。
http://127.0.0.1:8090/cxf/getauthorstring?authorName=oKong
http://127.0.0.1:8090/cxf//getauthorlist?authorName=oKong
动态工厂方式调用: http://127.0.0.1:8090/cxf/dynamic/getAuthorList?authorName=oKong
其余的就不一一贴图了,可自行访问下。
Cxf
发生异常时,会统一抛出:org.apache.cxf.interceptor.Fault
类的,因此想要捕获异常,能够在统一异常里面进行捕获,关于统一异常处理,能够查看文章:第八章:统一异常、数据校验处理。
CXF的拦截器分为两种:InInterceptor
和OutInterceptor
。显而易见,InInterceptor
能够处理soap请求消息,OutInterceptor
能够处理soap响应消息。其拦截器都继承至AbstractPhaseInterceptor<Message>
接口类,并且,自己也自带了不少的拦截器,能够自行添加看看,好比日志拦截器之类的:LoggingInInterceptor
和LoggingOutInterceptor
。
请求流程图:
拦截器链的阶段:
输入拦截器链有以下几个阶段,这些阶段按照在拦截器链中的前后顺序排列。
输出拦截器链有以下几个阶段,这些阶段按照在拦截器链中的前后顺序排列。
具体名称,可查看:org.apache.cxf.phase.Phase
。
如今,咱们自定义个实现拦截器,实现请求时header须要带上特定参数,或者你们可不写一些安全校验的自定义拦截器,本例只是简单的示例。
1.检验拦截器:CheckAuthInterceptor.java
/** * 简易-安全校验拦截器 * * @author oKong * */ @Slf4j public class CheckAuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> { public CheckAuthInterceptor() { super(Phase.PRE_INVOKE);// 拦截节点:调用以前 } @Override public void handleMessage(SoapMessage message) throws Fault { log.info("检验拦截器开始检验:{}", message); // 处理方法 List<Header> headers = message.getHeaders(); // 判断是否存header // 检查headers是否存在 if (headers == null | headers.size() < 1) { throw new Fault(new IllegalArgumentException("验证失败,请传入正确参数(40001)"));//可自定义编码规范 } //取出header Header header = headers.get(0); //获取对象 Element element = (Element) header.getObject();//这里获取的就时 auth对象了 NodeList tokenNode = element.getElementsByTagName("token"); if(tokenNode == null || tokenNode.getLength() < 1) { //无token节点 throw new Fault(new IllegalArgumentException("验证失败,请传入正确参数(40002)"));//自定义编码规范 } //获取token String token = tokenNode.item(0).getTextContent(); log.info("请求的token为:{}", token); //这里能够对token 有效性进行判断 } }
2.Endpoint中加入拦截器配置。
/* * 发布endpoint */ @Bean public Endpoint endpoint(AuthorService authorService) { EndpointImpl endpoint = new EndpointImpl(springBus(), authorService); endpoint.publish("/author");//发布地址 endpoint.getInInterceptors().add(createCheckAuthInterceptor());//加入拦截器 // endpoint.getOutInterceptors().add()//响应拦截器 return endpoint; } @Bean public Interceptor<SoapMessage> createCheckAuthInterceptor(){ return new CheckAuthInterceptor(); }
1.编写拦截器。
/** * 简易-安全校验拦截器 * @author oKong * */ public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage>{ public AuthInterceptor() { super(Phase.PREPARE_SEND);//准备请求时进行拦截 } @Override public void handleMessage(SoapMessage message) throws Fault { //处理方法 List<Header> headers = message.getHeaders(); Document doc = DOMUtils.createDocument(); Element element = doc.createElement("auth"); Element tokenEle = doc.createElement("token"); tokenEle.setTextContent(UUID.randomUUID().toString()); element.appendChild(tokenEle); //这里须要注意 默认状况下 是使用 org.w3c.dom.Element对象设置对象值的。 //也能够指定 DataBinding 设置对象的。可继承抽象类: org.apache.cxf.databinding.AbstractDataBinding //具体源码可查看:org.apache.cxf.binding.soap.interceptor.SoapOutInterceptor Header tokenHeader = new SoapHeader(new QName(""), element); // tokenHeader.setDataBinding() headers.add(tokenHeader); } }
这里须要注意:
org.w3c.dom.Element
对象。DataBinding
类来解析(何尝试,只是看了一眼源码,里面有此逻辑,有兴趣的同窗能够自行试试)。2.请求类中加入拦截器。 CxfClientConfig.java
/** * 以接口代理方式进行调用 AuthorPortType接口 */ @Bean("cxfProxy") public AuthorPortType createAuthorPortTypeProxy() { JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean(); jaxWsProxyFactoryBean.setServiceClass(AuthorPortType.class); jaxWsProxyFactoryBean.setAddress(WsConst.SERVICE_ADDRESS);//服务地址:http://127.0.0.1:8080/ws/autho jaxWsProxyFactoryBean.getOutInterceptors().add(createInterceptor());//加入自定义拦截器 return (AuthorPortType) jaxWsProxyFactoryBean.create(); } /* * 采用动态工厂方式 不须要指定服务接口 */ @Bean public Client createDynamicClient() { JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance(); Client client = dcf.createClient(WsConst.SERVICE_ADDRESS); client.getOutInterceptors().add(createInterceptor()); return client; } @Bean public Interceptor<SoapMessage> createInterceptor() { return new AuthInterceptor(); }
从新启动后,再次请求就能够看见相关日志输出了,能够试着不设置token,看看有拦截。
异常请求:
本章节主要简单介绍了
apache-cxf
的使用。这文章示例写下来,我发现比spring-ws
更简单呀,也更让人容易理解、逻辑比较清晰,并且也能设置一些差别化的东西。不知道是否是真的对spring-ws
了解的不够呀,没有发现spring-ws
的优势呀。自此,关于WebService
的文章就暂时告一段落了。
目前互联网上不少大佬都有
SpringBoot
系列教程,若有雷同,请多多包涵了。原创不易,码字不易,还但愿你们多多支持。若文中有所错误之处,还望提出,谢谢。
499452441
lqdevOps
我的博客:http://blog.lqdev.cn
完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-34
原文地址:http://blog.lqdev.cn/2018/11/12/springboot/chapter-thirty-four/