Replace XMLReaderFactory with SAXParserFactory
XMLReaderFactory has been marked as deprecated and without additional configuration, and it's slower than SAXParserFactory. Previously `XMLReaderFactory.createXMLReader()` is called upon every request. This is an anti-pattern as mentioned in [1] and it can be very slow since it loads the jar service file unless a parser has been pre-assigned [2] (e.g. by setting org.xml.sax.driver). SAXParserFactory uses a FactoryFinder [3] instead, which takes advantage of a thread-local cache provided by ServiceLoader. Developers can still pre-assign a factory by setting javax.xml.parsers.SAXParserFactory to make it faster. [1] https://bugs.openjdk.java.net/browse/JDK-6925410 [2]c8add223a1/src/java.xml/share/classes/org/xml/sax/helpers/XMLReaderFactory.java (L144-L148)
[3]66c653c561/src/java.xml/share/classes/javax/xml/parsers/SAXParserFactory.java (L181-L185)
See gh-27239
This commit is contained in:
parent
32359c52b4
commit
baed0785fd
|
@ -21,6 +21,8 @@ import java.io.StringWriter;
|
|||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.dom.DOMResult;
|
||||
|
@ -63,9 +65,11 @@ abstract class AbstractStaxHandlerTests {
|
|||
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("deprecation") // on JDK 9
|
||||
void createXMLReader() throws Exception {
|
||||
xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
xmlReader = saxParser.getXMLReader();
|
||||
xmlReader.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.springframework.util.xml;
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.transform.Transformer;
|
||||
|
@ -64,10 +66,12 @@ abstract class AbstractStaxXMLReaderTests {
|
|||
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("deprecation") // on JDK 9
|
||||
void setUp() throws Exception {
|
||||
inputFactory = XMLInputFactory.newInstance();
|
||||
standardReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
standardReader = saxParser.getXMLReader();
|
||||
standardContentHandler = mockContentHandler();
|
||||
standardReader.setContentHandler(standardContentHandler);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.io.StringReader;
|
|||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -62,13 +64,15 @@ class DomContentHandlerTests {
|
|||
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("deprecation") // on JDK 9
|
||||
void setUp() throws Exception {
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
result = documentBuilder.newDocument();
|
||||
xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
xmlReader = saxParser.getXMLReader();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ import javax.xml.XMLConstants;
|
|||
import javax.xml.datatype.Duration;
|
||||
import javax.xml.datatype.XMLGregorianCalendar;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.stream.XMLEventReader;
|
||||
import javax.xml.stream.XMLEventWriter;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
|
@ -575,8 +578,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IOException, SAXException {
|
||||
private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IOException, SAXException, ParserConfigurationException {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Setting validation schema to " +
|
||||
StringUtils.arrayToCommaDelimitedString(this.schemaResources));
|
||||
|
@ -584,8 +586,11 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
|
|||
Assert.notEmpty(resources, "No resources given");
|
||||
Assert.hasLength(schemaLanguage, "No schema language provided");
|
||||
Source[] schemaSources = new Source[resources.length];
|
||||
XMLReader xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
saxParserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
XMLReader xmlReader = saxParser.getXMLReader();
|
||||
for (int i = 0; i < resources.length; i++) {
|
||||
Resource resource = resources[i];
|
||||
Assert.isTrue(resource != null && resource.exists(), () -> "Resource does not exist: " + resource);
|
||||
|
@ -854,7 +859,6 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Source processSource(Source source) {
|
||||
if (StaxUtils.isStaxSource(source) || source instanceof DOMSource) {
|
||||
return source;
|
||||
|
@ -881,17 +885,20 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
|
|||
|
||||
try {
|
||||
if (xmlReader == null) {
|
||||
xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
String name = "http://xml.org/sax/features/external-general-entities";
|
||||
saxParserFactory.setFeature(name, isProcessExternalEntities());
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
xmlReader = saxParser.getXMLReader();
|
||||
}
|
||||
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
String name = "http://xml.org/sax/features/external-general-entities";
|
||||
xmlReader.setFeature(name, isProcessExternalEntities());
|
||||
if (!isProcessExternalEntities()) {
|
||||
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
|
||||
}
|
||||
return new SAXSource(xmlReader, inputSource);
|
||||
}
|
||||
catch (SAXException ex) {
|
||||
catch (SAXException | ParserConfigurationException ex) {
|
||||
logger.info("Processing of external entities could not be disabled", ex);
|
||||
return source;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import java.io.Writer;
|
|||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.stream.XMLEventReader;
|
||||
import javax.xml.stream.XMLEventWriter;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
|
@ -188,12 +190,15 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
|
|||
* Create an {@code XMLReader} that this marshaller will when passed an empty {@code SAXSource}.
|
||||
* @return the XMLReader
|
||||
* @throws SAXException if thrown by JAXP methods
|
||||
* @throws ParserConfigurationException if thrown by JAXP methods
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected XMLReader createXmlReader() throws SAXException {
|
||||
XMLReader xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
|
||||
protected XMLReader createXmlReader() throws SAXException, ParserConfigurationException {
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
XMLReader xmlReader = saxParser.getXMLReader();
|
||||
if (!isProcessExternalEntities()) {
|
||||
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
|
||||
}
|
||||
|
@ -431,7 +436,7 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
|
|||
try {
|
||||
saxSource.setXMLReader(createXmlReader());
|
||||
}
|
||||
catch (SAXException ex) {
|
||||
catch (SAXException | ParserConfigurationException ex) {
|
||||
throw new UnmarshallingFailureException("Could not create XMLReader for SAXSource", ex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.io.StringReader;
|
|||
import javax.xml.namespace.QName;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.stream.XMLEventReader;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
|
@ -98,9 +100,11 @@ public abstract class AbstractUnmarshallerTests<U extends Unmarshaller> {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation") // on JDK 9
|
||||
public void unmarshalSAXSource() throws Exception {
|
||||
XMLReader reader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
XMLReader reader = saxParser.getXMLReader();
|
||||
SAXSource source = new SAXSource(reader, new InputSource(new StringReader(INPUT_STRING)));
|
||||
Object flights = unmarshaller.unmarshal(source);
|
||||
testFlights(flights);
|
||||
|
|
|
@ -18,6 +18,9 @@ package org.springframework.http.converter.xml;
|
|||
|
||||
import java.io.StringReader;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.sax.SAXSource;
|
||||
|
@ -149,21 +152,23 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
protected Source processSource(Source source) {
|
||||
if (source instanceof StreamSource streamSource) {
|
||||
InputSource inputSource = new InputSource(streamSource.getInputStream());
|
||||
try {
|
||||
XMLReader xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
String featureName = "http://xml.org/sax/features/external-general-entities";
|
||||
xmlReader.setFeature(featureName, isProcessExternalEntities());
|
||||
saxParserFactory.setFeature(featureName, isProcessExternalEntities());
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
XMLReader xmlReader = saxParser.getXMLReader();
|
||||
if (!isProcessExternalEntities()) {
|
||||
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
|
||||
}
|
||||
return new SAXSource(xmlReader, inputSource);
|
||||
}
|
||||
catch (SAXException ex) {
|
||||
catch (SAXException | ParserConfigurationException ex) {
|
||||
logger.warn("Processing of external entities could not be disabled", ex);
|
||||
return source;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ import java.util.Set;
|
|||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLResolver;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
|
@ -197,19 +199,21 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private SAXSource readSAXSource(InputStream body, HttpInputMessage inputMessage) throws IOException {
|
||||
try {
|
||||
XMLReader xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
|
||||
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
|
||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||
saxParserFactory.setNamespaceAware(true);
|
||||
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
|
||||
saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
|
||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||
XMLReader xmlReader = saxParser.getXMLReader();
|
||||
if (!isProcessExternalEntities()) {
|
||||
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
|
||||
}
|
||||
byte[] bytes = StreamUtils.copyToByteArray(body);
|
||||
return new SAXSource(xmlReader, new InputSource(new ByteArrayInputStream(bytes)));
|
||||
}
|
||||
catch (SAXException ex) {
|
||||
catch (SAXException | ParserConfigurationException ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"Could not parse document: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue