有一个老旧的服务,我需要远程调用它。
技术是WSDL + SOAP + webService
.
看到很多同学用的都是xFire。然而,它有漏洞,不让用~。
于是参考了cxf,xFire的新版包。感觉不友好(重点是折腾半天没搞定)
…
技术岂是如此不便之物?于是从传统方案开始思考:即HTTP,可行~
一、错误的认知
- webService不是什么别的协议,依旧用的HTTP。
- 解析WSDL可以和解析json一样,但同理发送请求需要WSDL、SOAP格式。
(也就是说服务侧,需要提供xml请求响应格式。)
惍!隔壁团队的问题居然没有说清楚!搞得我以为WSDL是一种与HTTP不同的协议
二、postman测试
由于之前就是信心满满的写代码,然后搞不定,浪费了很多时间。
注:请求体、响应体格式需要问对接团队
请求头
请求体
响应体
结论:请求成功(成功)
三:设计Rest远程调用模组
要求:
- 同事希望能像调用方法一样调用远程服务
- 小领导要求:扩展友好、能用
- 我的要求:简单、易维护
项目包结构
简单的远程调用,懒得画图
四、关键代码
pom文件引用
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.16</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<!-- xml bean 转换相关包 -->
<!-- https://mvnrepository.com/artifact/jakarta.xml.bind/jakarta.xml.bind-api -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.1</version>
</dependency>
<!-- aop 切面 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
调用外层
xml bean 数据结构
外围bean
@Data
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "soap:Envelope")
public class AccountCertXmlResp {
@XmlElement(name = "soap:Body")
private Body body;
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public static class Body {
@XmlElement(name = "ns1:accountCertApplyResponse")
private AccountCertApplyResponse accountCertApplyResponse;
}
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public static class AccountCertApplyResponse {
@XmlElement(name = "ns1:out")
private String out;
}
}
JaxbUtil xml打包解析工具类
@Slf4j
public class JaxbUtils {
/**
* xml 转bean
*
* @param data 字符
* @param targetClazz bean class
* @param <T> t
* @return t
*/
public static <T> T xmlStrToBean(String data, Class<T> targetClazz) {
return xmlStrToBean(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), targetClazz);
}
/**
* xml 转bean
*
* @param is 输入流
* @param targetClazz bean class
* @param <T> t
* @return t
*/
public static <T> T xmlStrToBean(InputStream is, Class<T> targetClazz) {
try {
JAXBContext jc = JAXBContext.newInstance(targetClazz);
Unmarshaller uma = jc.createUnmarshaller();
// 忽略命名空间
SAXParserFactory sax = SAXParserFactory.newInstance();
sax.setNamespaceAware(false);
XMLReader xmlReader = sax.newSAXParser().getXMLReader();
Source source = new SAXSource(xmlReader, new InputSource(is));
return (T) uma.unmarshal(source);
} catch (JAXBException e) {
log.error("xmlutil ", e);
throw new RuntimeException("Deserialization from xml to object error: " + e.getMessage());
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
/**
* bean 转字符
*
* @param t bean
* @param <T> t
* @return 字符
*/
public static <T> String beanToXml(T t) {
StringWriter writer;
try {
//1、创建上下文
JAXBContext context = JAXBContext.newInstance(t.getClass());
//从上下文中获取Marshaller对象,用作将bean编组(转换)为xml
Marshaller marshaller = context.createMarshaller();
//3、设置marshaller的属性
//以下是为生成xml做的一些配置
//格式化输出,即按标签自动换行,否则就是一行输出
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//是否省略xml头信息,默认不省略(false)
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
//4、实例化StringWriter
writer = new StringWriter();
//5、将实体类读取到writer中
marshaller.marshal(t, writer);
//6、输出结果
return writer.toString();
} catch (JAXBException e) {
log.error("xmlUtil toString error :", e);
throw new RuntimeException(e);
}
}
}
SoapUtil 工具类
@Slf4j
public class SoapUtil {
/**
* 打包为xml soap协议
*
* @param reqBean 请求对象
* @return 打包后的数据
*/
public static <T> String pkgXml(String operationName,T reqBean) {
// 对象转xml
String xml = XmlConst.XML_8 + JaxbUtils.beanToXml(reqBean);
// 替换原格式中的:操作名、内容
String xmlPkg = XmlConst.SOAP_PKG.replace(XmlConst.OPERATION_NAME, operationName)
.replace(XmlConst.REQUEST_BODY, xml);
log.info("xmlPkg : {}",xmlPkg);
return xmlPkg;
}
}
常量写法
HTTP请求工具
@Slf4j
public class HttpUtil {
static SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
static HttpHeaders headers = new HttpHeaders();
static {
factory.setReadTimeout(6000);
factory.setConnectTimeout(6000);
headers.setContentType(MediaType.APPLICATION_XML);
}
private static final RestTemplate REST = new RestTemplate(factory);
/**
* 发送Post请求
*
* @param url 地址
* @param body 内容
* @return 请求结果
*/
public static String postSoap(String url, Object body) {
// 获取操作名
String operationName = url.substring(url.lastIndexOf("/") + 1);
// 首字母小写
operationName = operationName.substring(0,1).toLowerCase() + operationName.substring(1);
// 组装请求包
String pkgXml = SoapUtil.pkgXml(operationName, body);
return post(url, pkgXml);
}
/**
* 发送Post请求
*
* @param url 地址
* @param body 内容
* @return 请求结果
*/
public static String post(String url, String body) {
return httpPost(url, headers, body);
}
/**
* 发送Post请求
*
* @param url 请求地址
* @param headers 请求头
* @param body 请求体
* @return 返回消息体
*/
public static String httpPost(String url, HttpHeaders headers, String body) {
HttpEntity<String> request = new HttpEntity<>(body, headers);
log.info("post url: {} headers: {} body:{}", url, headers, body);
ResponseEntity<String> response;
try {
URI uri = new URI(url);
response = REST.postForEntity(uri, request, String.class);
if (HttpStatus.OK.value() != response.getStatusCode().value()){
log.error("http post fail, response : {}",response);
throw new CertRestRpcException();
}
} catch (Exception e) {
log.error("http error : ",e);
throw new CertRestRpcException();
}
return response.getBody();
}
}
五、异常捕获器
- 将模组的异常捕获,并只抛出规定的异常
@Aspect
@Component
@Slf4j
public class CertRestExceptionHandler {
@Pointcut("execution(* com.pak.service.CertRestService.*(..))")
public void pointCut() {
}
@Around("pointCut()")
public Object catchException(ProceedingJoinPoint joinPoint) throws Throwable {
//核心业务处理
try {
// 获取切点请求参数
Object[] args = joinPoint.getArgs();
// 处理
return joinPoint.proceed(args);
}catch (Exception e){
log.error("CertRestExceptionHandler : ",e);
throw new CertRestRpcException();
}
}
}
六、单元测试
@SpringBootApplication
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AccountCertTest.class)
public class AccountCertTest extends TestCase {
@Resource
CertRestService certRestService;
@Resource
CertPropsConfig propsConfig;
public static void main(String[] args) {
SpringApplication.run(AccountCertTest.class, args);
}
@Test
public void rsaTest() {
AccountCertParamDTO certParam = new AccountCertParamDTO();
certParam.setIdType(propsConfig.getIdtype());
certParam.setCertsort(propsConfig.getCertsort());
certParam.setCertapptype(propsConfig.getCertapptype());
certParam.setCertstoragetype(propsConfig.getCertstoragetype());
// 填写参数
certParam.setIdvalue("xxxx");
String publicKey_RSA = "xxxx";
certParam.setSubjectpubkey(publicKey_RSA);
// 申请证书
CertResponse response = certRestService.accountCertApply(certParam);
// 下载证书
CertDownloadParamDTO downloadParamDTO = new CertDownloadParamDTO();
downloadParamDTO.setTransactioncode(response.getTransactioncode());
CertResponse download = certRestService.certDownload(downloadParamDTO);
// 加密证书
String enccert = download.getEnccert();
// 签名证书
String signcert = download.getSigncert();
System.out.println(download);
}
}