При создании веб-сервисов многие люди возражают против использования 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, таких как настраиваемый аудит, реализованный в прослушивании в нашем примере.
Если вы заинтересованы в коде, я обязательно загрузю его. В противном случае спасибо за это время!