前段时间接到新的任务,需要对一个很有年代感的集成系统做新版升级,这套老系统下游有一个小的推送系统通过一个相对独立的小程序定时访问老系统的webservice服务获取数据。由于年代久远,推送系统相关的东西已经没人维护,只能寄希望于新集成下的的ESB按照老的webservice提供一模一样的服务。
新系统下的ESB基于camel、osgi、fuse,因此决定采用camel cxf对外提供webservice。
camel-cxf 参考资料:https://camel.apache.org/components/latest/cxf-component.html
http://people.apache.org/~dkulp/camel/cxf.html
本次接口的主要jar
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cxf</artifactId>
<version>2.15.0</version>
<optional>true</optional>
</dependency>
第一步,camel.xml中的endpoint配置,address对应接口发布后的服务地址,serviceClass对应想要发布的接口实现,properties中的entry是针对返回值的xml的标签名做的改造,ns_1对应命名空间。
<camel-cxf:cxfEndpoint id="testEndpoint"
address="http://127.0.0.1:8080/xxx/xxx"
serviceClass="com.webservice.impl.TestServiceImpl">
<camel-cxf:properties>
<entry key="soap.env.ns.map">
<map>
<entry key="ns_1" value="http://www.test.com/com/xxx/xxx"/>
<entry key="soapenv" value="http://schemas.xmlsoap.org/soap/envelope/"/>
<entry key="xsi" value="http://www.w3.org/2001/XMLSchema-instance"/>
</map>
</entry>
<entry key="disable.outputstream.optimization" value="true"/>
</camel-cxf:properties>
</camel-cxf:cxfEndpoint>
第二步,创建TestService,示例对应三个返回值,写法与传统的webservice定义一致。
@WebService(targetNamespace = "http://www.test.com/com/xxx/xxx")
public interface TestService {
/**
* @param result 取值结果 0成功,1失败
* @param errMsg 错误或警告信息
* @param outData 返回数据
*/
@WebMethod
@ResponseWrapper(localName = "getTestOutMsg", targetNamespace = "http://www.test.com/com/xxx/xxx")
void getTestData(@WebParam(name = "result", targetNamespace = "http://www.test.com/com/xxx/xxx", mode = WebParam.Mode.OUT) Holder<Integer> result,
@WebParam(name = "errMsg", targetNamespace = "http://www.test.com/com/xxx/xxx", mode = WebParam.Mode.OUT) Holder<String> errMsg,
@WebParam(name = "outData", targetNamespace = "http://www.test.com/com/xxx/xxx", mode = WebParam.Mode.OUT) Holder<String> outData
);
}
第三步,TestServiceImpl
@WebService(targetNamespace = "http://www.test.com/com/xxx/xxx")
@Singleton
@OsgiServiceProvider(classes = TestService.class)
public class TestServiceImpl implements TestService {
@Override
public void getTestData(Holder<Integer> result, Holder<String> errMsg, Holder<String> outData) {
result.value = 0;
outData.value = dataStr;
errMsg.value = "";
}
}
第四步,创建路由,回到之前的camel.xml中创建路由。testProcess是自定义的数据处理器,可以方便debug接口或者修改返回值,非必需,可以省略。
<bean id = "testRouter" class="com.route.TestRouter">
<property name="testProcess" ref="testProcess"/>
</bean>
第五步,创建router,这里面引入的endpoint对应开头定义的cxfEndpoint 的id ,.process这一步可以省略。
public class TestRouter extends RouteBuilder {
private TestProcess testProcess;
@Override
public void configure() throws Exception {
from("cxf:bean:testEndpoint?loggingFeatureEnabled=true")
.process(testProcess)
.log("request: ${body}");
}
public TestProcess getTestProcess() {
return testProcess;
}
public void setTestProcess(TestProcess testProcess) {
this.testProcess = testProcess;
}
}
第六步,创建process,这里面在process里面newserviceImpl也是无奈之举,这一步有的人是把它放在router定义的时候,
.process(new TestProcess(new TestServiceImpl()))用这样的方式引入,但是我在实际应用时,这种方式没法通过 @Inject
@OsgiService注解引入别的模块接口。因此暂定目前这种写法,受限于个人水平,没有找到代替的方法。
@Singleton
public class TestProcess implements Processor {
private Object instance = null;
private Map<String, Method> operationMap = new ConcurrentHashMap<>();
@Inject
@OsgiService
private TestOtherService testOtherService;
private TestServiceImpl testService;
@Override
public void process(Exchange exchange) throws Exception {
if(null == testService){
testService = new TestServiceImpl();
}
instance = testService;
initOperationMap(testService.getClass());
String inputMessage = exchange.getIn().getBody(String.class);
log.info("inputMessage : " + inputMessage);
String operationName = exchange.getIn().getHeader(CxfConstants.OPERATION_NAME, String.class);
Method method = operationMap.get(operationName);
if (method == null) {
throw new RuntimeException("找不到" + operationName + "对应的方法!");
}
try {
Object body = exchange.getIn().getBody();
if (body == null) {
method.invoke(instance);
} else {
exchange.getIn().setBody(body);
method.invoke(instance, exchange.getIn().getBody(Object[].class));
}
} catch (Exception e) {
throw new RuntimeException("方法调用异常", e);
}
}
private void initOperationMap(Class<?> beanClass) {
WebService webService = beanClass.getDeclaredAnnotation(WebService.class);
if (webService != null && !org.apache.cxf.common.util.StringUtils.isEmpty(webService.endpointInterface())) {
try {
Class serviceClass = Class.forName(webService.endpointInterface());
processServiceClass(serviceClass);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else {
processServiceClass(beanClass);
}
}
private void processServiceClass(Class serviceClass) {
for (Method method : serviceClass.getDeclaredMethods()) {
String operationName = method.getName();
WebMethod webMethod = method.getAnnotation(WebMethod.class);
if (webMethod != null && !org.apache.cxf.common.util.StringUtils.isEmpty(webMethod.operationName())) {
operationName = webMethod.operationName();
}
log.info("operationName: {} - method: {}", operationName, method);
operationMap.put(operationName, method);
}
}
}