最近接手了一个老项目,项目中用到了WebService技术,WebService试一种传统的SOA技术架构,它不依赖于任何编程语言,也不依赖于任何技术平台,可以直接基于HTTP协议实现网络应用之间的数据交互。WebService组成架构采用传统的"C/S"模型,如果某个平台需要对外暴露操作接口,这个时候就可以直接通过WSDL(Web Services Description Language)Web服务描述语言对要公布的接口进行描述。
WebService服务端是以远程接口为主的,在Java实现的WebService技术里主要依靠CXF开发框架,而这个CXF开发框架可以直接将接口发布成WebService。
CXF又分为JAX-WS和JAX-RS,JAX-WS是基于xml协议,而JAX-RS是基于Restful风格,两者的区别如下:
新建一个工程,引入核心依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-web-servicesartifactId>
dependency>
<dependency>
<groupId>org.apache.cxfgroupId>
<artifactId>cxf-spring-boot-starter-jaxwsartifactId>
<version>3.4.5version>
dependency>
新建实体类
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement
public class UserInfo implements Serializable {
private static final long serialVersionUID = -5352429333001227976L;
private Long id;
private String username;
private String password;
}
新建接口
@WebService(name = "userInfoService", targetNamespace = "http://service.api.ws.webservice.boot.xlhj.com/")
public interface UserInfoService {
@WebMethod(operationName = "saveUserInfo")
void saveUserInfo(@WebParam(name = "userInfo") UserInfo userInfo);
@WebMethod
UserInfo getUserInfoById(@WebParam(name = "id") Long id);
}
新建接口实现类
@Service
@WebService(serviceName = "userInfoService", targetNamespace = "http://service.api.ws.webservice.boot.xlhj.com/", endpointInterface = "com.xlhj.boot.webservice.ws.api.service.UserInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Override
public void saveUserInfo(UserInfo userInfo) {
System.out.println("保存用户信息成功" + userInfo.toString());
}
@Override
public UserInfo getUserInfoById(Long id) {
return UserInfo.builder().id(1L).username("zhangsan").password("123456").build();
}
}
新建配置类
@Configuration
public class CXFConfig {
@Autowired
private Bus bus;
@Autowired
private UserInfoService userInfoService;
@Autowired
private WebServiceAuthInterceptor interceptor;
/**
* 设置WebService访问父路径
* @return
*/
@Bean
public ServletRegistrationBean getRegistrationBean() {
return new ServletRegistrationBean(new CXFServlet(), "/services/*");
}
@Bean
public Endpoint messageEndPoint() {
EndpointImpl endpoint = new EndpointImpl(this.bus, this.userInfoService);
endpoint.publish("/userInfoService");
endpoint.getInInterceptors().add(this.interceptor);
return endpoint;
}
}
新建拦截器类
@Component
public class WebServiceAuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
/**用户名*/
private static final String USER_NAME = "xxxx";
/**密码*/
private static final String USER_PASSWORD = "xlhj.com";
private static final String NAME_SPACE_URI = "http://service.api.ws.webservice.boot.xlhj.com/";
/**创建拦截器*/
private SAAJInInterceptor interceptor = new SAAJInInterceptor();
private static final Logger logger = LoggerFactory.getLogger(WebServiceAuthInterceptor.class);
public WebServiceAuthInterceptor() {
super(Phase.PRE_PROTOCOL);
//添加拦截
super.getAfter().add(SAAJInInterceptor.class.getName());
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
//获取指定消息
SOAPMessage soapMessage = message.getContent(SOAPMessage.class);
if (null == soapMessage) {
this.interceptor.handleMessage(message);
soapMessage = message.getContent(SOAPMessage.class);
}
//SOAP头信息
SOAPHeader header = null;
try {
header = soapMessage.getSOAPHeader();
} catch (SOAPException e) {
e.printStackTrace();
}
if (null == header) {
throw new Fault(new IllegalAccessException("没有Header信息,无法实现用户认证处理!"));
}
//SOAP是基于XML文件结构进行传输的,所以如果要想获取认证信息就必须进行相关的结构约定
NodeList usernameNodeList = header.getElementsByTagNameNS(NAME_SPACE_URI, "username");
NodeList passwordNodeList = header.getElementsByTagNameNS(NAME_SPACE_URI, "password");
if (usernameNodeList.getLength() < 1) {
throw new Fault(new IllegalAccessException("没有用户信息,无法实现用户认证处理!"));
}
if (passwordNodeList.getLength() < 1) {
throw new Fault(new IllegalAccessException("没有密码信息,无法实现用户认证处理!"));
}
String username = usernameNodeList.item(0).getTextContent().trim();
String password = passwordNodeList.item(0).getTextContent().trim();
if (USER_NAME.equals(username) && USER_PASSWORD.equals(password)) {
logger.debug("用户访问认证成功!");
} else {
SOAPException soapException = new SOAPException("用户认证失败!");
logger.debug("用户认证失败!");
throw new Fault(soapException);
}
}
}
新建主启动类后启动项目,在浏览器输入地址http://localhost:8235/services/
点击WSDL链接可以查看到具体的接口信息
新建客户端拦截器类
public class ClientLoginInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private String username;
private String password;
private static final String NAME_SPACE_URI = "http://service.api.ws.webservice.boot.xlhj.com/";
public ClientLoginInterceptor(String username, String password) {
super(Phase.PREPARE_SEND);
this.username = username;
this.password = password;
}
@Override
public void handleMessage(SoapMessage soapMessage) throws Fault {
List<Header> headers = soapMessage.getHeaders();
Document document = DOMUtils.createDocument();
Element authority = document.createElementNS(NAME_SPACE_URI, "authority");
Element username = document.createElementNS(NAME_SPACE_URI, "username");
Element password = document.createElementNS(NAME_SPACE_URI, "password");
username.setTextContent(this.username);
password.setTextContent(this.password);
authority.appendChild(username);
authority.appendChild(password);
headers.add(0, new Header(new QName("authority"), authority));
}
}
新建客户端接口
@Component
public class UserInfoApiClient {
private static final String USERNAME = "xxxx";
private static final String PASSWORD = "xlhj.com";
private static final String ADDRESS = "http://localhost:8235/services/userInfoService?wsdl";
/**
* 使用代理方法
* @param userInfo
*/
public void saveUserInfoWithProxy(UserInfo userInfo) {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setAddress(ADDRESS);
jaxWsProxyFactoryBean.setServiceClass(UserInfoService.class);
jaxWsProxyFactoryBean.getOutInterceptors().add(
new ClientLoginInterceptor(USERNAME, PASSWORD)
);
UserInfoService userInfoService = (UserInfoService) jaxWsProxyFactoryBean.create();
userInfoService.saveUserInfo(userInfo);
}
/**
* 使用动态代理
* @param id
* @throws Exception
*/
public void getUserInfoByIdWithDynamic(Long id) throws Exception {
JaxWsDynamicClientFactory clientFactory = JaxWsDynamicClientFactory.newInstance();
Client client = clientFactory.createClient(ADDRESS);
client.getOutInterceptors().add(new ClientLoginInterceptor(USERNAME, PASSWORD));
Object[] userInfos = client.invoke("getUserInfoById", id);
String userInfo = userInfos[0].toString();
System.out.println(userInfo);
}
}
@SpringBootTest
class WebServiceWSClientApplicationTest {
@Autowired
private UserInfoApiClient userInfoApiClient;
@Test
void saveUserInfoWithProxy() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1L);
userInfo.setUsername("张三");
userInfo.setPassword("123456");
userInfoApiClient.saveUserInfoWithProxy(userInfo);
}
@Test
void getUserInfoByIdWithDynamic() throws Exception {
userInfoApiClient.getUserInfoByIdWithDynamic(1L);
}
}
注意:客户端使用动态代理访问时,参数中有bean对象时会提示类型转换错误异常,有解决办法的小伙伴欢迎在评论区留言
新建一个项目,引入核心依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-web-servicesartifactId>
dependency>
<dependency>
<groupId>org.apache.cxfgroupId>
<artifactId>cxf-spring-boot-starter-jaxrsartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>org.apache.cxfgroupId>
<artifactId>cxf-rt-rs-extension-providersartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>org.codehaus.jettisongroupId>
<artifactId>jettisonartifactId>
<version>1.4.1version>
dependency>
新建实体类和接口
实体类同ws中实体类
public interface UserInfoService {
@POST
void saveUserInfo(UserInfo userInfo);
@PUT
void updateUserInfo(UserInfo userInfo);
@DELETE
@Path("/{id}")
void deleteUserById(@PathParam("id") Long id);
@GET
List<UserInfo> findAllUser();
@GET
@Path("/{id}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
UserInfo findUserInfoById(@PathParam("id") Long id);
}
新建配置类
@Configuration
public class CXFConfig {
@Autowired
private Bus bus;
@Autowired
private UserInfoService userInfoService;
/**
* 设置WebService访问父路径
* @return
*/
@Bean
public Server createServer() {
JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean();
factoryBean.setAddress("/userInfoService");
factoryBean.setBus(bus);
factoryBean.setServiceBean(userInfoService);
return factoryBean.create();
}
}
新建业务接口实现类
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Override
public void saveUserInfo(UserInfo userInfo) {
System.out.println("保存用户信息成功!" + userInfo.toString());
}
@Override
public void updateUserInfo(UserInfo userInfo) {
System.out.println("更新用户信息成功!" + userInfo.toString());
}
@Override
public void deleteUserById(Long id) {
System.out.println("删除用户信息成功!" + id);
}
@Override
public List<UserInfo> findAllUser() {
return null;
}
@Override
public UserInfo findUserInfoById(Long id) {
return null;
}
}
开发客户端接口类
@Component
public class UserInfoApiClient {
private static final String ADDRESS = "http://localhost:8335/services/userInfoService";
public Response saveUserInfo(UserInfo userinfo) {
return WebClient.create(ADDRESS).post(userinfo);
}
public Response updateUserInfo(UserInfo userInfo) {
return WebClient.create(ADDRESS).put(userInfo);
}
public Response deleteUserById(Long id) {
return WebClient.create(ADDRESS + "/" + id).delete();
}
public Response findAllUser() {
return WebClient.create(ADDRESS).get();
}
public Response findUserInfoById(Long id) {
return WebClient.create(ADDRESS + "/" + id).get();
}
}
@SpringBootTest
public class WebServiceRSClientApplicationTest {
@Autowired
private UserInfoApiClient userInfoApiClient;
@Test
void saveUserInfoTest() {
UserInfo userInfo = UserInfo.builder()
.id(1L)
.username("zhangsan")
.password("123456")
.build();
userInfoApiClient.saveUserInfo(userInfo);
}
@Test
void findUserInfoByIdTest() {
Response response = userInfoApiClient.findUserInfoById(1L);
System.out.println(response);
}
@Test
void deleteUserByIdTest() {
userInfoApiClient.deleteUserById(1L);
}
}