INFO - Пример кода
Я установил образец кода (SSCCE), чтобы вы могли отслеживать проблему:
https://github.com/ljader/test-cxf-base64-marshall
Проблема
Я интегрируюсь со сторонним JAX-WS-сервисом, поэтому я не могу изменить WSDL.
Сторонний вебсервис ожидает, что закодированные байты Base64 будут выполнять некоторую операцию над ними - они ожидают, что клиент отправит все байты в сообщении SOAP. Они не хотят, чтобы менялся на MTOM/XOP, поэтому я придерживался текущих требований.
Я решил использовать CXF для простой настройки образца клиента, и он работал нормально для небольших файлов.
Но когда я пытаюсь отправить BIG-данные, т.е. 200 МБ, CXF/JAXB выдает исключение:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.sun.xml.bind.v2.util.ByteArrayOutputStreamEx.readFrom(ByteArrayOutputStreamEx.java:75)
at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.get(Base64Data.java:196)
at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.writeTo(Base64Data.java:312)
at com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:312)
at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:356)
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$PcdataImpl.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:191)
at com.sun.xml.bind.v2.runtime.MimeTypedTransducer.writeLeafElement(MimeTypedTransducer.java:96)
at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:254)
at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:130)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:360)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:155)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:130)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:332)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:339)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:75)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:251)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:95)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:617)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:241)
at org.apache.cxf.jaxb.io.DataWriterImpl.write(DataWriterImpl.java:237)
at org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor.writeParts(AbstractOutDatabindingInterceptor.java:117)
at org.apache.cxf.wsdl.interceptors.BareOutInterceptor.handleMessage(BareOutInterceptor.java:68)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:514)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:423)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:324)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:277)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:139)
Мои результаты
Я отслеживал ошибку, которая на основе xsd типа "base64Binary",
com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl
решает, что
com.sun.xml.bind.v2.runtime.unmarshaller.Base64Datap >
должен обрабатывать сортировку данных из
javax.activation.DataHandler
Во время сортировки данные WHOLE из базового InputStream пытаются читать http://grepcode.com/file/repo1.maven.org/maven2/com.sun.xml.bind/jaxb-impl/2.2.11/com/sun/xml/bind/v2/runtime/unmarshaller/Base64Data.java/#311, что вызывает исключение OOME.
Проблема
CXF использует JAXB во время сортировки объектов Java в сообщения SOAP - при сортировке InputStream входной поток WHOLE считывается в память перед преобразованием в двоичный файл Base64.
Итак, я хочу отправлять данные с клиента на сервер в фрагменты (так как OutputSteam в marshaller обернута напрямую HttpURLConnection), поэтому мой клиент может обрабатывать отправку любого количества данных.
Особенно, когда многие потоки будут использовать мой клиент, потоковая передача IMHO очень желательна.
У меня нет хорошего знания JAX-WS/CXF/JAXB, поэтому вопрос.
Единственными материалами, которые я нашел и которые могут быть полезны, являются:
Может ли JAXB анализировать большие XML файлы в кусках
http://rezarahim.blogspot.com/2010/05/chunking-out-big-xml-with-stax-and-jaxb.html
Вопросы
-
Почему CXF/JAXB загружает весь InputStream в память - не является ли чистая память DataHandler для предотвращения такой реализации?
-
Знаете ли вы какой-либо способ изменить поведение JAXB по-разному, маршаллируйте InputStream?
-
Знаете ли вы разных маршаллеров, которые могут обрабатывать такие большие сортировки данных?
-
Как последнее средство, возможно, у вас есть ссылки на некоторые материалы, как создать собственный маршаллер, который будет передавать данные непосредственно на сервер?