Статьи

Прагматичные веб-сервисы с Apache CXF

Apache CXF [1] — это среда на основе Java с открытым исходным кодом, цель которой — помочь пользователям разрабатывать стандартные, совместимые и надежные веб-сервисы.

Конечная цель написания веб-сервисов состоит в том, чтобы гарантировать, что данная реализация веб-сервиса может использоваться различными клиентами, работающими во время разработки и во время выполнения, совместимым и эффективным образом. В последнее время много написано о том, как должны разрабатываться веб-сервисы. Похоже, что споры в основном закончились, когда сообщества REST и SOAP начали внедрять веб-сервисы так, как они считают нужным.

Apache CXF, реализуя различные стандарты веб-сервисов на основе SOAP и спецификацию JAX-RS 1.0 для написания сервисов RESTful на Java, предоставляет пользователям уникальную среду для разработки расширенных конечных точек веб-сервисов и клиентов с использованием любого стиля, который им нравится или нужен. Нет сомнений в том, что в реальных проектах веб-сервисов очень часто используется ряд сервисов, написанных с учетом REST и / или SOAP, и CXF это прекрасно понимает — это прогрессивная, но прагматическая структура в конце концов.

Эта статья не будет фокусироваться на контрасте стилей REST и SOAP. Так много было написано об этом. Мы также не будем описывать, как создавать и выполнять пошаговую разработку веб-сервисов. Скорее, мы опишем, как Apache CXF упрощает создание конечных точек служб и потребителей как на основе RESTful, так и на основе SOAP XML, что позволяет дольше выдерживать изменения в базовых данных.

Обратно и вперед совместимые веб-сервисы

Существует ряд хорошо известных причин, по которым Интернет преуспел. Несомненно, способность общих потребителей HTTP игнорировать нераспознанные теги HTML и тот факт, что недавно обновленные пользователи HTTP, осведомленные о новых расширениях HTML, могут использовать старые документы HTML, сыграли одну из основных ролей.

Цель написания обратных и переадресованных веб-сервисов одинаково применима как к сервисам REST, так и к SOAP, как на стороне клиента, так и на стороне сервера. Стоит попытаться приложить некоторые усилия заранее для достижения этой цели, и это здорово, когда рамки могут помочь. Обратите внимание, что хотя мы предполагаем, что XML является базовым форматом данных, некоторые из методов, описанных ниже, могут применяться, когда задействованы другие форматы данных.

Так что же означает иметь обратную и прямую совместимую реализацию веб-сервиса?


Обновленная реализация конечной точки веб-службы:
  • обратно совместим со своими старыми клиентами, если он может продолжать потреблять их запросы, без необходимости их перекомпиляции
  • совместимость с более старыми клиентами, если они могут продолжать использовать ответы этой обновленной конечной точки, без необходимости перекомпиляции.

Аналогично, клиент для недавно обновленной конечной точки веб-служб:

  • прямая совместимость со старой конечной точкой, если она может продолжать вызывать ее
  • обратная совместимость со старой конечной точкой, если она может продолжать использовать свои ответы

Стоимость несовместимости минимальна, если существует ограниченное число клиентов и конечных точек, возможно, в контролируемой среде. Он резко возрастает с ростом числа клиентов и конечных точек.

Обратная и прямая совместимость часто считается само собой разумеющейся, когда обычные клиенты с хорошим поведением, такие как браузеры, потребляют данные. При работе с скомпилированными потребителями требуется больше дисциплины.

Часто обратную совместимость легче достичь в тех случаях, когда проверка схемы включена, и было применено непрерывное обновление. Прямая совместимость сводится к игнорированию нераспознанных тегов, но может оказаться сложнее добиться ее, если проверка включена, поскольку для этого может потребоваться некоторый продвинутый дизайн схемы. В других случаях на самом деле может быть небезопасно игнорировать нераспознанные теги.

Разрывное изменение часто обозначается изменением пространства имен, но в реальном мире оно может быть внесено в документ без обновления его пространства имен или, действительно, неразрывное изменение может указываться обновлением пространства имен. Существует много вариантов, и, как и в случае с самими веб-сервисами, всегда будут разные сценарии.

В CXF можно решить многие проблемы на пути написания надежных реализаций веб-сервисов с обратной и прямой совместимостью. Мы начнем с описания примера приложения RESTful, покажем, как можно объединить REST и SOAP в одной конечной точке веб-службы, а затем рассмотрим различные варианты, доступные в CXF для этой конечной точки, чтобы она развивалась и оставалась обратной и прямой совместимостью со своими потребителями.

 

Разработка RESTful сервисов

Apache CXF реализует спецификацию JAX-RS 1.0 (JAX-RS). JAX-RS — это очень хорошее усилие, популяризованное эталонной реализацией JAX-RS. Он описывает, как существующие классы Java могут быть представлены как конечные точки службы RESTful с помощью аннотаций. Он предлагает ряд расширений, как и другие реализации JAX-RS. Среди этих расширений — клиентский API-интерфейс RESTful в вариантах на основе прокси, http- и xml-ориентированных.

Давайте напишем простое приложение BookStore, помня о цели этой статьи. BookStore — это магазин, который позволяет своим клиентам находить книги по некоторым критериям поиска. Обратите внимание, что предполагается, что пользователи предпочитают писать типизированный код Java, который работает с типами, специфичными для домена. Можно избежать многих проблем, обсуждаемых ниже, в зависимости от таких типов, как, например, JAXP Source.

Внедрение сервиса

Вот начальная версия интерфейса BookStore и его реализация:

package org.bookstores;
import javax.ws.rs.*;

@Path(“/bookstore”)
public interface BookStore {
/**
* Find a Book by its id
**/
@Get @Path(“{id}”) @Produces(“application/xml”)
Book getBook(@PathParam(“id”) long id);
}
public class BookStoreImpl implements BookStore {
private Map<String, Book> books = new HashMap<String, Books>();
public Book getBook(long id) {
return books.get(id);
}
private void populateBooks() {}
}

Изначально мы предоставили реализацию BookStore, которая позволит людям искать книги, встраивая идентификатор книги в сегмент пути URI запроса. Например, следующие запросы:

GET /bookstore/1
GET /bookstore/2

В результате 1 и 2 будут сопоставлены с параметром ‘id’ BookStoreImpl # getBook (длинный идентификатор)

Написание RESTful клиента

Теперь, когда у нас есть конечная точка приложения, пришло время написать клиентский код, который будет его использовать. На данный момент CXF предлагает клиентский API-интерфейс RESTful в трех вариантах: на основе прокси, http-centric и xml-centric. Пожалуйста, обратитесь к документации CXF JAX-RS [2] для полного описания клиентского API. Вот как вы можете использовать конечную точку BookStore.

  1. Прокси-подход:

import org.bookstores.*;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
BookStore store = JAXRSClientFactory.create(“http://books/bookstore”);
Book b = store.getBook(1);

В результате store.getBook (1) в конечную точку BookStore отправляется запрос «GET http: // books / bookstore / 1 ». Прокси скрывают детали того, как должен формироваться URI запроса, и преобразуют параметры метода в соответствующие значения компонента URI или заголовки или тело HTTP.

Прокси-серверы имеют свои плюсы и минусы, но для целей этой статьи достаточно сказать, что подход на основе прокси-сервера действительно хорошо тестирует, как соответствующая реализация конечной точки подготовила к неизбежным изменениям в будущем.

Рассмотрим новое требование: найдите книгу по названию. Сначала нам нужно обновить интерфейс BookStore, который потребует перекомпиляции старого клиентского кода, как только он получит обновленный интерфейс на своем пути к классам. Мы могли бы избежать обновления интерфейса, если бы сначала решили обойтись без интерфейса и добавить все аннотации JAX-RS в BookStoreImpl.

Однако это не решило бы всех проблем и ограничило бы способ BookStoreImpl выступать в качестве подресурса. Другими словами, вам, вероятно, придется использовать метод подресурса, возвращающий Object, а не интерфейс BookStore, если вы решите, что BookStoreImpl может также выступать в качестве подресурса JAX-RS — это будет действительный, но не типизированный код Java. Поэтому мы просто идем и обновляем интерфейс BookStore и надеемся, что никогда не сделаем это снова:

@Path(“/bookstore”)
public interface BookStore {
@Get @Path(“{id}”) @Produces(“application/xml”)
Book getBook(@PathParam(“id”) long id);
@Get @Path(“name/{name}”) @Produces(“application/xml”)
Book getBookByName(@PathParam(“name”) String name);
@Get @Path(“{id}/{name}”) @Produces(“application/xml”)
Book getBookByNameAndId(@PathParam(“name”) String name, @PathParam(“id”));
}

Обратите внимание, что в значение BookStore # getBookByName @Path необходимо добавить новый сегмент пути «name», в противном случае оба метода будут соответствовать запросу типа «GET / bookstore / 1» согласно алгоритму диспетчеризации метода JAX-RS. Это не идеальное решение, как вы знаете, в скором времени появится требование найти книги автора.

Также не ясно, что делать, если нам нужно получить книгу по имени и идентификатору, поскольку мы можем вызывать только один метод за раз. Мы добавили составной метод getBookByNameAndById () — он уменьшит, но только отложит проблему — и это будет иметь место, даже если используются параметры запроса.

Более навязчивым подходом было бы иметь пустую подпись и использовать вставленный контекст JAX-RS UriInfo и использовать его для запроса доступных путей, матрицы или параметров запроса — это на самом деле будет работать, но это может быть просто не тот код, который вы используете. после, если вы хотите использовать только определенные для домена типы.

Так как насчет клиента, который хочет найти книгу по имени, по идентификатору или по имени и идентификатору? Люди часто воображают хорошие формы HTML, но скомпилированные клиенты все еще там. Вот один из вариантов:

Map<String, String> argNameValuePairs = getArgumentsFromCmd();
BookStore store = JAXRSClientFactory.create(“http://books/bookstore”);
if (args.containsKey(“id”)) {
Book b = store.getBook(1);
} else if (args.containsKey(“name”)) {
Book b = store.getBookByName(args.get(“name”));
} else if (args.containsKey(“name”) && args.containsKey(“id”)) {
Book b = store.getBookByNameAndId(args.get(“id”), args.get(“name”));
}
// more branches to follow

Как мы можем избежать ветвления каждый раз, когда появляется новое требование поиска? Читайте дальше, пожалуйста.

               2. HTTP-ориентированные клиенты

Вот код веб-клиента с учетом вышеуказанных требований:

import org.bookstores.*;
import org.apache.cxf.jaxrs.client.WebClient;
WebClient store = WebClient.create(“http://books/bookstore”);
if (args.containsKey(“id”)) {
Book b = store.path(args.get(“id”)).get(Book.class);
} else if (args.containsKey(“name”)) {
Book b = store.path(“/name/” + args.get(“name”) ).get(Book.class);
} else if (args.containsKey(“name”) && args.containsKey(“id”)) {
Book b = store.path(args.get(“name”)).path(args.get(“id”)).get(Book.class);
}  

               3. XML-ориентированные клиенты

Клиенты, ориентированные на XML, — это WebClients, использующие служебный класс CXF XMLSource. Они хороши, когда нужно извлечь ожидаемое представление XML из более крупного документа XML, поэтому мы обсудим их позже. Вот простой пример:

WebClient store = WebClient.create(“http://books/bookstore”);
if (args.containsKey(“id”)) {
Book b = store.path(args.get(“id”)).get(XMLSource.class).get(“/book”, Book.class);
}
// continue branching

Объединение сервисов REST и SOAP

Прежде чем приступить к описанию предлагаемых изменений в интерфейсе BookStore, давайте немного сосредоточимся на том, как можно комбинировать аннотации JAX-RS и JAX-WS.

 Многие пользователи CXF продолжают писать сервисы SOAP. Многие из таких сервисов очень продвинуты, поскольку CXF предоставляет разработчикам SOAP множество инструментов для написания сложных приложений на основе SOAP с поддержкой асинхронных и приостановленных вызовов, поддержкой таких стандартов, как WS-Security и WS-SecurityPolicy, его очень гибкой конфигурацией. параметры.

Как мы знаем, все запросы SOAP через HTTP выполняются с использованием POST. Это то, что многие сквозные SOAP-приложения не очень заботят — важно то, что такие приложения просто работают, как и ожидалось. Однако это не так просто для тех конечных точек SOAP, которые хотели бы иметь универсальных WEB-клиентов, чтобы иметь возможность работать с такими конечными точками, чтобы на самом деле это произошло. Конечно, такие инструменты, как Flash или GWT, позволяют пользователям использовать сервисы SOAP из браузеров, но связывание невозможно. Существующая поддержка GET для SOAP может работать, но она не используется широко.

Множество веб-приложений работают просто путем ссылок друг на друга. Другими словами, они используют GET. GET является основным веб-глаголом, который заключается в том, что весь поиск и анализ информации осуществляется в основном с помощью GET. Определенные веб-приложения также будут использовать POST / PUT / и т. Д., Но без GET WEB на самом деле не будет WEB. 

Механизм JAX-WS Provider and Dispatch позволяет разработчикам создавать функциональные приложения RESTful, и, действительно, можно представить, что с его помощью можно комбинировать приложения SOAP и REST. Для пользователей, предпочитающих интерфейсы Java для работы с ожидаемыми типами пользователей, JAX-RS 1.0 идеально подходит.

Основной эффект спецификации JAX-RS 1.0 заключается в том, что она позволяет представить существующий класс Java как веб-сервис, применяя к нему аннотации JAX-RS. Так что, если вы уже сказали, что сервис SOAP, аннотированный JAX-WS, будет просто иметь сервис, который будет получать вызовы REST и JAXWS:

import javax.ws.rs.*;
import javax.jws.*;
@Path(“/bookstore”)
@WebService
public interface BookStore {
@Get @Path(“{id}”) @Produces(“application/xml”)
@WebMethod
Book getBook(@WebParam(name=”id”) @PathParam(“id”) long id) throws BookUnavailableException;
}

Таким образом, активация WEB для данного узла SOAP становится тривиальной задачей. Или SOAP-включение данной конечной точки RESTful — вы никогда не знаете, какие требования могут появиться позже.

Теперь та же проблема, что мы обсуждали ранее, применима и здесь: как нам написать интерфейс службы, который будет предвидеть изменения.

Прогнозирование изменений

Прежде чем пытаться понять, что нужно сделать, чтобы существующий код оставался в живых перед лицом неожиданных изменений данных, всегда стоит немного подумать и попытаться предвидеть изменения. Сложно, сложно, а иногда и почти невозможно написать идеальный код в этом отношении, но если подумать с самого начала, то, скорее всего, вы избавитесь от головной боли в будущем.

Используйте параметры одного сложного метода

Да, Java богата, и это позволяет вам добавлять много методов к данному классу. Но именно Java-код должен работать с миром веб-сервисов, над чем вы работаете. Попробуйте получить один составной входной и возвращаемый параметры только для данного метода — если вы предпочитаете детализированные параметры, вы можете вместо этого делегировать другой внутренний класс.

Давайте изменим BookStore и BookStoreImpl следующим образом:

@Path(“/bookstore”)
@WebService
public interface BookStore {
@Get @Path(“{id}/{name}/”) Produces(“application/xml”)
@WebMethod
Book getBook(@PathParam(“”) BookDescription descr);
@Get @Path(“search”) Produces(“application/xml”)
@WebMethod(exclude=”true”)
Book getBookByQuery(@QueryParam(“”)) BookDescription descr);
}

public class BookStoreImpl implements BookStore {
private Map<String, Book> localBooks = new HashMap<String, Books>();
public Book getBook(BookDescription desc) {
return doGetBook(desc);
}
public Book getBookByQuery(BookDescription desc) {
return doGetBook(desc);
}
public Book doGetBook(BookDescription desc) {
for (Book b : books.values()) {
if (-1 != desc.getId() && b.getId() != desc.getId()) {
continue;
}
if (null != desc.getId() && !b.getName().equals(desc.getName())) {
continue;
}
// reflectively check the values of other getters, if they not null then we can not
// satisfy the search constraints – most likely it a request from a new producer
return b;
}
return null;
}
}
public class BookDescription {
public long id;
public String name;
}

Интерфейс BookStore имеет два метода: один позволяет пользователям указывать параметры поиска в качестве значений сегмента пути, а другой — в качестве параметров запроса. Нет, что оба метода основаны на расширении CXF JAX-RS, которое позволяет вводить все доступные значения параметров «имя-значение» в компонент. Например, в случае PathParams значение «id» PathParam будет вставлено в поле id. Для параметров запроса и матрицы это работает точно так же.

The method which deals with PathParams will not handle queries with an author name – still it will be less intrusive to get a @Path annotation updated only. As for the queries case this code will rarely have to change again – assuming the actual query request is delegated to a database procedure for example – but even if it had to, these changes would be very isolated with no side effects for clients : perhaps we’d only have to update doGetBook() to check for new bean properties. BeanDescription will likely be regenerated from an external schema whenever new search requirements occur.

Note that for a SOAP-based service the trick is to ensure that BookDesciption parameters are not unwrapped into a method signature, make sure the only parameter which is unwrapped is the BookDescription itself.

So what about the clients ?Here is a proxy based one:

BookDescription bookDescription = getBookDescriptionFromArguments();
BookStore store = JAXRSClientFactory.create(“http://books/bookstore”);
BookStore jaxWSstore = createSoapProxy(“http://books/bookstore”);
searchBook(store);
searchBook(jaxWSstore);

private void searchStore(BookStore store, BookDescription desc) {
store.getBook(desc);
store.getBookByQuery();

}

Note that both REST and SOAP proxies can be reused in the same code and this code is unlikely to change often, whatever the new change requirements are. In case of RESTful proxies, BookDescription property values will be unwrapped into URI path values or query parameters, as needed.

Here is an HTTP-centric code:

BookDescription bookDescription = getBookDescriptionFromArguments();
WebClient store = WebClient.create(“http://books/bookstore”);
Book b1 = store.path(bookDescription).get(Book.class);
Book b1 = store.back().query(bookDescription).get(Book.class);

The only explicit knowledge encoded here is .path() and .query() requests, that is we need to know that it has to be a query or path parameters, as opposed to the case with proxies. Other than that this code will unlikely to change often. BookDescription property values will be unwrapped as needed, to either path or queries. Also note store.back() – you can effectively browse with a given WebClient using its back and forward functions.

Toward  backward and forward compatibility

With the above updates, we have backward and forward compatible clients and endpoints. The new requirements on how to search the book will likely affect the BookDescription only and the changes will be driven by updates to a BookDescription schema first.

New clients, those aware of new BookDescription properties, will continue invoking successfully on the old BookStore endpoints and thus will be forward compatible, while the newer BookStore endpoints will continue handling requests from the older ones.

Having accumulated all the various search properties into a single bean we made it possible to drive the updates from the outside, by updating the schema and regenerating the bean definition and minimizing the need to update the actual code.

As if only things were that simple. As it happens, in reality, both  response Book and input BookDescription are actually namespace qualified on the wire and schema validated on the input. Or you may have got a new requirement to search books using an intermediary service which interposes an original BookStore service and wraps the resulting Book in a bigger document.

Apache CXF is well equipped to help you to tackle many of the challenges  on the way to writing cost-effective clients and endpoints which know how to deal with the change.

Typically, one would want to translate the incoming or out coming document to the format expected and in CXF one can do it at different levels, by post-processing a response object which will affect the serialization, by plugging in into an XML instance read and write process, transforming XML or extracting the required XML node.

Using RequestHandler, ResponseHandler and CustomInvokers   

The core of the CXF JAX-RS implementation is implemented by two CXF interceptors[1], handling in and out messages.  CXF JAX-RS also offers filters, RequestHandler and ResponeHandler, which can be assembled into chains and executed before a request body is de-serialized and a response object is serialized respectively.

Thus one somewhat low-level option is to register either RequestHandler and/or RequestHandler implementations and wrap an input or output stream:

public class InRequestHandler implements RequestHandler {
public Response handleRequest(Message m, ClassResourceInfo resource) {
m.putContent(InputStream.class, new CustomFilterInputStream(m.getContent(InputSream.class)));
}
}

Similarly, an output stream can be replaced in a ResponseHandler implementation.One can also update various request headers or indeed a request method if needed.

CXF Invokers is really yet another form of interceptors where one can validate the actual method paramer values and in fact replace either one of such parameters or a response object, with the latter option providing for the actual output customization. Replacing a response object can also be done in a ResponseFilter. Please check CXF JAX-RS documentation for more details.

Using CXF Interceptors

As noted in the previos subsection, CXF implements JAX-RS with the help of a couple of CXF interceptors. One can also register in or out custom CXF interceptors which will further pre and post process a given HTTP request or response, before or after JAX-RS runtime interceptors are invoked. Using CXF interceptors to deal with data changes can be particularly useful when combining SOAP and REST services as the same interceptor instances can be registered and reused with both JAX-WS and JAX-RS endpoints or clients.

Unexpected changes to non-XML formats can be dealt with in either CXF interceptors or JAX-RS filters described above.

Using Stax readers and writers.

It is been a long time since writing SAX handlers was considered to be the most effective XML processing option – STAX is here now and it is performing well and is arguably simpler for people to plug in into. In CXF JAX-RS we will add support for SAX handlers, SAX is still great and fast after all, but STAX is what is supported explicitly by both JAX-WS and JAX-RS runtime in CXF and using custom readers or writers is probably the best option for people writing combined SOAP and REST services and wishing to customize input or output XML messages.

Suppose the task is to translate a namespace. Here is how you can register custom readers and writers from CXF JAX-RS filters :

public class InRequestHandler implements RequestHandler {
public Response handleRequest(Message m, ClassResourceInfo resource) {
XMLStreamReader reader =StaxUtils.createXMLStreamReader(m.getContent(InputStream.class));
m.putContent(XMLStreamReader.class,
new CustomXMLStreamReader(inMessage, reader));
return null;
}
public Response handleResponse(Message outMessage, OperationResourceInfo ori, Response response) {
// did input XMLStreamReader translated a namespace ?
if (outMessage.getExchange().getInMessage().get(@namespace.translated@) == null) {
return;
}
XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(m.getContent(OutputStream.class)); m.setContent(XMLStreamWriter.class, new CustomXmlStreamWriter(writer));
return null;
}

public class CustomXmlStreamWriter extends DelegatingXMLStreamWriter {
private Message outMessage;
public CustomXmlStreamWriter(XMLStreamWriter writer) {
super(writer);
}

@Override
public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
super.writeStartElement("b", local, "");
if ("Book".equals(local)) {
super.writeNamespace("b", "http://oldbooks");

}
}
}

The CXF JAX-RS response filter checks if a namespace translation has occurred for a given message exchange and if yes then it installs a custom XMLStreamWriter which translates a namespace for older clients to continue operating. This is a technique which has been recently tried by one of CXF JAX-WS users, by using CXF interceptors to register custom STAX readers/writers instead.

Stax readers can be very useful when implementing a controlled forward compatibility policy described at [4], when it can be unsafe to ignore a given unknown tag. A custom reader will check a version attribute and will throw an exception which will be mapped to a proper HTTP response or a SOAP fault.

Using XSLT provider

XSLT is a very powerful technology for transforming XML documents. CXF JAX-RS offers a JAXB-based XSLT provider which can translate inbound and outbound XML instances.

In fact, depending on how far would you like to go with XSLT and if you use JAXB, this XSLT provider may be the only one provider ever needed for producing and consuming any types of well-formatted XML documents on both client and server sides, conforming to any XML-aware media types such as application/xml, application/xhtml+xml or application/atom+feed – you only need to have media type specific XSLT templates registered with the provider. Please see the CXF documentation on how to register this provider.

So you can handle incompatible changes at the pure XML level by leveraging the power of XSLT, at the input and output.

When producing, this provider can easily generate non-XML documents as well, for example it can be used toadapt a JAXB-produced XML to a natural JSON format expected by JSON clients.

Using XPath

XPath is another powerful XML technology. XPath is used by XSLT but you can use Xpath in CXF JAX-RS on its own. Explicitly working with XPath can be handy when you want to get a required XML fragment out of the larger parent document or when you simply would like to introspect various bits and pieces of the incoming XML or get to a single property without worrying a lot about its location in the XML document.

CXF JAX-RS introduces a light-weight utility class XMLSource which can be used either on the client or server sides, directly or as a provider, for example :

@Path(“/”)
public class Resource {
@POST
void postBody(XMLSource source) {
// get the very first book in the doc
Book book = source.getNode(‘/*/book[position()=1]’, Book.class);
}
}
WebClient client = WebClient.create(‘http://books’);
XMLSource source = client.get(XMLSource.class);
Book book = source.get(‘/*/book[position()=1]’, Book.class);

 

And finally, using XpathProvider, which is more suitable for out task of saving the client or server Java code from the recompilation:

XPathProvider provider = new XpathProvider();
provider.setExpression(‘/*/book[position()=1]’);
WebClient client = WebClient.create(‘http://books’, Collections.singletonList(provider));
Book book = client.get(Book.class);

Поставщик также может быть зарегистрирован на стороне сервера, как программно, так и из Spring.            

При наличии поставщиков XPath и XSLT и поддержки пользовательских обработчиков STAX CXF может выступать в роли микро-маршрутизатора, который применяет правила преобразования к входящим или исходящим сообщениям.

Пользовательские поставщики тела сообщений JAX-RS.

One can also imagine writing a custom MessageBodyReader or MessageBodyWriter which will do a custom (de)serialization from IntputStream/to OutputStream, adapting the data as expected by the ultimate receiver, by using additional  properties. For example, one can imagine every client sending an HTTP header indicating a version. A message body provider finding a mismatch between the version it is aware of and the one sent by a current client can translate the data as needed – but it does require much more discipline and argubaly is more difficult to implementas one may need to register a number of class-specific providers.

Conclusion.

In this article we tried to focus on the issue which is very often ignored or viewed as not important or simply difficult to deal with. Hopefully, you can now see that achieving backward and forward compatibility is doable and worthy spending time upon and working with CXF does make tackling this challenge like a fun.

Links.

  1. Apache CXF: http://cxf.apache.org/
  2. CXF JAX-RS documentation: http://cwiki.apache.org/CXF20DOC/jax-rs.html
  3. Versioning Techniques and Strategies chapter in a Web Service Contract Design and Versioning book which brings David Orchards posts together: http://www.amazon.com/Contract-Versioning-Prentice-Service-Oriented-Computing/dp/013613517X
  4. A Smoother Change to Version 2.0 by Marc De Graauw http://www.xml.com/pub/a/2007/04/11/a-smoother-change-to-version-20.html

About Author:

Sergey Beryozkin is a Principal Engineer working for Progress Fuse Open Source Division and is an Apache CXF committer. Sergey has been interested in web services and XML technologies for many years. He blogs at http://sberyozkin.blogspot.com