При создании веб-сервисов многие люди возражают против использования JAXB для привязки XML к объектам. Это может быть вызвано соображениями производительности, аллергией на сгенерированный код или просто философским убеждением, что вам не следует смешивать сервисы, ориентированные на документы, с объектно-ориентированной моделью. Независимо от причин, по которым вы не используете JAXB, я постараюсь описать возможное альтернативное решение с использованием Camel.
Я создал эту лабораторию после просмотра отличных разговоров Адриана Тренамана об использовании CXF на верблюде, поэтому я могу и не буду брать кредит на все, что вы увидите. В этом выступлении он продемонстрировал небольшую хитрость в том, что вы можете использовать Velocity в качестве движка шаблонов для генерации ваших мыльных ответов, не выполняя какой-либо сортировки или анализа XML-документов.
Его полный сеанс можно найти
здесь , но обратите внимание, что вам нужна регистрация на fusesource.com, чтобы увидеть это. Это бесплатно, поэтому, если вам интересно, я призываю вас проверить это.
Определение нашего контракта
Как говорят в основном все SOA-архитекторы, вам всегда следует начинать с определения wsdl, создавая сервис сверху вниз. В этом примере нашей предполагаемой услугой будет очень простая служба проверки кредитоспособности, которая принимает клиента и предоставляет кредитную оценку и подробное описание причин, лежащих в основе оценки.
Наш сервис на данный момент имеет только одну операцию, которая называется PerformCreditCheck:
Ответ представляет собой простое сообщение, содержащее поле Score и поле Details:
Верблюжий контекст
Я решил реализовать маршрут с использованием Java DSL, поэтому после настройки конечной точки CXF в контексте верблюда я указываю сканирование пакета на пакет java, где находится мой маршрут. Полный camel-context.xml выглядит следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cxf="http://camel.apache.org/schema/cxf"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml"/>
<cxf:cxfEndpoint xmlns:c="http://billy.laborations/CreditCheckService/" id="creditCheckEndpoint"
address="http://localhost:9000/creditcheck"
wsdlURL="META-INF/wsdl/CreditCheckService.wsdl"
serviceName="c:CreditCheckService"
endpointName="c:CreditCheckServiceSOAP"
serviceClass="billy.laborations.creditcheck.DummyService">
<cxf:properties>
<entry key="schema-validation-enabled" value="true" />
</cxf:properties>
</cxf:cxfEndpoint>
<camelContext xmlns="http://camel.apache.org/schema/spring">
<package>billy.laborations.creditcheck</package>
</camelContext>
</beans>
Как видите, я предоставляю wsdl для сервиса, его адрес конечной точки, а также класс реализации.
Конечная точка JAX-WS
Глядя на этот класс, все становится интересным. Поскольку я не хочу иметь дело с JAXB, я выбрал тип сервиса JAX-WS Provider, который принимает произвольные сообщения. Любой тип сообщений, попадающих в эту службу, будет вызывать метод invoke (), оставляя анализ XML и т. Д. На усмотрение разработчика. Однако, если вы посмотрите на метод, то заметите, что он настроен так, чтобы всегда выдавать исключение, как он может работать? Что ж, похоже, что Camel взломал этот метод и взломал маршрут, но этот класс все еще должен существовать для инициализации среды выполнения CXF.
package billy.laborations.creditcheck;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Provider;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;
@WebServiceProvider(portName="CreditCheckPort", serviceName="CreditCheckService", targetNamespace="http://billy.laborations/CreditCheckService/")
@ServiceMode(Mode.PAYLOAD)
public class DummyService implements Provider<StreamSource> {
public StreamSource invoke(StreamSource request) {
throw new UnsupportedOperationException("Not implemented yet.");
}
}
Маршрутизация
Маршрут реализован так:
package billy.laborations.creditcheck;
import javax.xml.transform.stream.StreamSource;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.xml.Namespaces;
import org.apache.cxf.message.MessageContentsList;
public class CreditCheckRoute extends RouteBuilder {
public void configure() {
// lets define the namespaces we'll need in our filters
Namespaces ns = new Namespaces("c", "http://billy.laborations/CreditCheckService/");
from("cxf:bean:creditCheckEndpoint").routeId("CreditCheckRoute")
.process(new Processor(){
public void process(Exchange exchange) throws Exception {
//Cxf stores requests in a MessageContentList. The actual message is the first entry
MessageContentsList messageList = (MessageContentsList)exchange.getIn().getBody();
exchange.getIn().setBody(messageList.get(0), String.class);
}
})
.wireTap("seda:audit")
.setHeader("FirstName", xpath("/c:Customer/FirstName").resultType(String.class).namespaces(ns))
.setHeader("LastName", xpath("/c:Customer/LastName").resultType(String.class).namespaces(ns))
.setHeader("SocialNr", xpath("/c:Customer/SocialNr").resultType(Integer.class).namespaces(ns))
.process(new CreditCheckCalculator())
.to("velocity:META-INF/velocity/creditCheckResponse.vm")
.convertBodyTo(StreamSource.class)
;
from("seda:audit")
.inOnly()
.to("log:audit")
.to("file:target/audit/?fileName=${date:now:yyyyMMdd}.txt&fileExist=Append");
}
}
Как видите, маршрут использует XPath для установки некоторых заголовков на основе входящих значений. Это сделано для того, чтобы наш сервис (CreditCheckCalculator) имел к ним легкий доступ.
Бизнес-сервис
В этом случае я сохранил простой сервис, но интересная часть заключается в том, что он использует заголовки верблюдов для получения и установки данных. Вам не нужно реализовывать сервис как верблюжий процессор, существует множество способов вызова, т.е. существующего POJO с использованием ссылок на bean-компоненты, но я оставлю это сейчас.
package billy.laborations.creditcheck;
import org.apache.camel.Exchange;
public class CreditCheckCalculator implements org.apache.camel.Processor{
public void process(Exchange exchange) throws Exception {
String firstName = (String)exchange.getIn().getHeader("FirstName");
String lastName = (String)exchange.getIn().getHeader("LastName");
int socialNr = (Integer)exchange.getIn().getHeader("SocialNr");
int creditScore;
String details;
if(firstName.equalsIgnoreCase("Billy")){
creditScore = 99;
details = firstName + " is a really good customer!";
}else{
creditScore = 1;
details = firstName + " should not be trusted!";
}
exchange.getIn().setHeader("CreditScore", creditScore);
exchange.getIn().setHeader("Details", details);
}
}
Генерация ответа
Почему заголовки являются хорошим местом для хранения результатов? Ну, в нашем случае это потому, что у нас есть легкий доступ к ним из шаблона скорости:
<CreditScore xmlns="http://billy.laborations/CreditCheckService/"> <Score>$headers.CreditScore</Score> <Details>$headers.Details</Details> </CreditScore>
Хитрость в том, что мы шаблонируем наш ответ, не касаясь каких-либо XML API. Шаблон скорости просто вернет объект String, который мы затем можем преобразовать в StreamSource, прежде чем передать его обратно в конечную точку JAX-WS.
Последние мысли
Ну, это подводит итог моего эксперимента. Это может выглядеть как сложная установка для очень простого сервиса, но преимущества использования шаблонов следующие:
* Производительность — Нет необходимости анализировать запросы или ответные сообщения
* Стек WS — Предоставление маршрута в качестве конечных точек CXF дает нам всю мощь обработчики мыла и инжекторы CXF, т.е. для повышения безопасности и т. д.
* Добавление функциональности к маршруту. Маршрут можно легко расширить для поддержки других шаблонов EIP, таких как настраиваемый аудит, реализованный в прослушивании в нашем примере.
Если вы заинтересованы в коде, я обязательно загрузю его. В противном случае спасибо за это время!

