Browse Source

Avoid creation of SAXParserFactory for every read operation

Includes JAXBContext locking revision (avoiding synchronization) and consistent treatment of DocumentBuilderFactory (in terms of caching as well as locking).

Closes gh-32851

(cherry picked from commit a4c2f291d9)
pull/33048/head
Juergen Hoeller 2 years ago
parent
commit
4c9de3cbbd
  1. 49
      spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java
  2. 46
      spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java
  3. 23
      spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java
  4. 78
      spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java

49
spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -37,6 +37,8 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
import javax.xml.datatype.Duration; import javax.xml.datatype.Duration;
@ -192,7 +194,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
@Nullable @Nullable
private ClassLoader beanClassLoader; private ClassLoader beanClassLoader;
private final Object jaxbContextMonitor = new Object(); private final Lock jaxbContextLock = new ReentrantLock();
@Nullable @Nullable
private volatile JAXBContext jaxbContext; private volatile JAXBContext jaxbContext;
@ -204,6 +206,12 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
private boolean processExternalEntities = false; private boolean processExternalEntities = false;
@Nullable
private volatile SAXParserFactory schemaParserFactory;
@Nullable
private volatile SAXParserFactory sourceParserFactory;
/** /**
* Set multiple JAXB context paths. The given array of context paths gets * Set multiple JAXB context paths. The given array of context paths gets
@ -426,6 +434,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
*/ */
public void setSupportDtd(boolean supportDtd) { public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd; this.supportDtd = supportDtd;
this.sourceParserFactory = null;
} }
/** /**
@ -450,6 +459,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
if (processExternalEntities) { if (processExternalEntities) {
this.supportDtd = true; this.supportDtd = true;
} }
this.sourceParserFactory = null;
} }
/** /**
@ -497,7 +507,9 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
if (context != null) { if (context != null) {
return context; return context;
} }
synchronized (this.jaxbContextMonitor) {
this.jaxbContextLock.lock();
try {
context = this.jaxbContext; context = this.jaxbContext;
if (context == null) { if (context == null) {
try { try {
@ -521,6 +533,9 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
} }
return context; return context;
} }
finally {
this.jaxbContextLock.unlock();
}
} }
private JAXBContext createJaxbContextFromContextPath(String contextPath) throws JAXBException { private JAXBContext createJaxbContextFromContextPath(String contextPath) throws JAXBException {
@ -587,17 +602,24 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
Assert.notEmpty(resources, "No resources given"); Assert.notEmpty(resources, "No resources given");
Assert.hasLength(schemaLanguage, "No schema language provided"); Assert.hasLength(schemaLanguage, "No schema language provided");
Source[] schemaSources = new Source[resources.length]; Source[] schemaSources = new Source[resources.length];
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true); SAXParserFactory saxParserFactory = this.schemaParserFactory;
saxParserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true); if (saxParserFactory == null) {
saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
this.schemaParserFactory = saxParserFactory;
}
SAXParser saxParser = saxParserFactory.newSAXParser(); SAXParser saxParser = saxParserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader(); XMLReader xmlReader = saxParser.getXMLReader();
for (int i = 0; i < resources.length; i++) { for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i]; Resource resource = resources[i];
Assert.isTrue(resource != null && resource.exists(), () -> "Resource does not exist: " + resource); Assert.isTrue(resource != null && resource.exists(), () -> "Resource does not exist: " + resource);
InputSource inputSource = SaxResourceUtils.createInputSource(resource); InputSource inputSource = SaxResourceUtils.createInputSource(resource);
schemaSources[i] = new SAXSource(xmlReader, inputSource); schemaSources[i] = new SAXSource(xmlReader, inputSource);
} }
SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage); SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage);
if (this.schemaResourceResolver != null) { if (this.schemaResourceResolver != null) {
schemaFactory.setResourceResolver(this.schemaResourceResolver); schemaFactory.setResourceResolver(this.schemaResourceResolver);
@ -886,11 +908,16 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
try { try {
if (xmlReader == null) { if (xmlReader == null) {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); SAXParserFactory saxParserFactory = this.sourceParserFactory;
saxParserFactory.setNamespaceAware(true); if (saxParserFactory == null) {
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); saxParserFactory = SAXParserFactory.newInstance();
String name = "http://xml.org/sax/features/external-general-entities"; saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature(name, isProcessExternalEntities()); saxParserFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
saxParserFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.sourceParserFactory = saxParserFactory;
}
SAXParser saxParser = saxParserFactory.newSAXParser(); SAXParser saxParser = saxParserFactory.newSAXParser();
xmlReader = saxParser.getXMLReader(); xmlReader = saxParser.getXMLReader();
} }

46
spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -83,9 +83,10 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
private boolean processExternalEntities = false; private boolean processExternalEntities = false;
@Nullable @Nullable
private DocumentBuilderFactory documentBuilderFactory; private volatile DocumentBuilderFactory documentBuilderFactory;
private final Object documentBuilderFactoryMonitor = new Object(); @Nullable
private volatile SAXParserFactory saxParserFactory;
/** /**
@ -94,6 +95,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
*/ */
public void setSupportDtd(boolean supportDtd) { public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd; this.supportDtd = supportDtd;
this.documentBuilderFactory = null;
this.saxParserFactory = null;
} }
/** /**
@ -118,6 +121,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
if (processExternalEntities) { if (processExternalEntities) {
this.supportDtd = true; this.supportDtd = true;
} }
this.documentBuilderFactory = null;
this.saxParserFactory = null;
} }
/** /**
@ -137,14 +142,13 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
*/ */
protected Document buildDocument() { protected Document buildDocument() {
try { try {
DocumentBuilder documentBuilder; DocumentBuilderFactory builderFactory = this.documentBuilderFactory;
synchronized (this.documentBuilderFactoryMonitor) { if (builderFactory == null) {
if (this.documentBuilderFactory == null) { builderFactory = createDocumentBuilderFactory();
this.documentBuilderFactory = createDocumentBuilderFactory(); this.documentBuilderFactory = builderFactory;
}
documentBuilder = createDocumentBuilder(this.documentBuilderFactory);
} }
return documentBuilder.newDocument(); DocumentBuilder builder = createDocumentBuilder(builderFactory);
return builder.newDocument();
} }
catch (ParserConfigurationException ex) { catch (ParserConfigurationException ex) {
throw new UnmarshallingFailureException("Could not create document placeholder: " + ex.getMessage(), ex); throw new UnmarshallingFailureException("Could not create document placeholder: " + ex.getMessage(), ex);
@ -179,11 +183,11 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory) protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory)
throws ParserConfigurationException { throws ParserConfigurationException {
DocumentBuilder documentBuilder = factory.newDocumentBuilder(); DocumentBuilder builder = factory.newDocumentBuilder();
if (!isProcessExternalEntities()) { if (!isProcessExternalEntities()) {
documentBuilder.setEntityResolver(NO_OP_ENTITY_RESOLVER); builder.setEntityResolver(NO_OP_ENTITY_RESOLVER);
} }
return documentBuilder; return builder;
} }
/** /**
@ -193,11 +197,17 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
* @throws ParserConfigurationException if thrown by JAXP methods * @throws ParserConfigurationException if thrown by JAXP methods
*/ */
protected XMLReader createXmlReader() throws SAXException, ParserConfigurationException { protected XMLReader createXmlReader() throws SAXException, ParserConfigurationException {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); SAXParserFactory parserFactory = this.saxParserFactory;
saxParserFactory.setNamespaceAware(true); if (parserFactory == null) {
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); parserFactory = SAXParserFactory.newInstance();
saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities()); parserFactory.setNamespaceAware(true);
SAXParser saxParser = saxParserFactory.newSAXParser(); parserFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
parserFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.saxParserFactory = parserFactory;
}
SAXParser saxParser = parserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader(); XMLReader xmlReader = saxParser.getXMLReader();
if (!isProcessExternalEntities()) { if (!isProcessExternalEntities()) {
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER); xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);

23
spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -61,6 +61,7 @@ import org.springframework.util.ClassUtils;
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
* @see MarshallingHttpMessageConverter * @see MarshallingHttpMessageConverter
*/ */
@ -70,6 +71,9 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
private boolean processExternalEntities = false; private boolean processExternalEntities = false;
@Nullable
private volatile SAXParserFactory sourceParserFactory;
/** /**
* Indicate whether DTD parsing should be supported. * Indicate whether DTD parsing should be supported.
@ -77,6 +81,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
*/ */
public void setSupportDtd(boolean supportDtd) { public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd; this.supportDtd = supportDtd;
this.sourceParserFactory = null;
} }
/** /**
@ -97,6 +102,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
if (processExternalEntities) { if (processExternalEntities) {
this.supportDtd = true; this.supportDtd = true;
} }
this.sourceParserFactory = null;
} }
/** /**
@ -156,11 +162,16 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
if (source instanceof StreamSource streamSource) { if (source instanceof StreamSource streamSource) {
InputSource inputSource = new InputSource(streamSource.getInputStream()); InputSource inputSource = new InputSource(streamSource.getInputStream());
try { try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); SAXParserFactory saxParserFactory = this.sourceParserFactory;
saxParserFactory.setNamespaceAware(true); if (saxParserFactory == null) {
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); saxParserFactory = SAXParserFactory.newInstance();
String featureName = "http://xml.org/sax/features/external-general-entities"; saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature(featureName, isProcessExternalEntities()); saxParserFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
saxParserFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.sourceParserFactory = saxParserFactory;
}
SAXParser saxParser = saxParserFactory.newSAXParser(); SAXParser saxParser = saxParserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader(); XMLReader xmlReader = saxParser.getXMLReader();
if (!isProcessExternalEntities()) { if (!isProcessExternalEntities()) {

78
spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -63,6 +63,7 @@ import org.springframework.util.StreamUtils;
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
* @param <T> the converted object type * @param <T> the converted object type
*/ */
@ -75,11 +76,7 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
(publicID, systemID, base, ns) -> InputStream.nullInputStream(); (publicID, systemID, base, ns) -> InputStream.nullInputStream();
private static final Set<Class<?>> SUPPORTED_CLASSES = Set.of( private static final Set<Class<?>> SUPPORTED_CLASSES = Set.of(
DOMSource.class, DOMSource.class, SAXSource.class, StAXSource.class, StreamSource.class, Source.class);
SAXSource.class,
StAXSource.class,
StreamSource.class,
Source.class);
private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
@ -88,10 +85,19 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
private boolean processExternalEntities = false; private boolean processExternalEntities = false;
@Nullable
private volatile DocumentBuilderFactory documentBuilderFactory;
@Nullable
private volatile SAXParserFactory saxParserFactory;
@Nullable
private volatile XMLInputFactory xmlInputFactory;
/** /**
* Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} * Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes}
* to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}. * to {@code text/xml} and {@code application/xml}, and {@code application/*+xml}.
*/ */
public SourceHttpMessageConverter() { public SourceHttpMessageConverter() {
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml")); super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
@ -104,6 +110,9 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
*/ */
public void setSupportDtd(boolean supportDtd) { public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd; this.supportDtd = supportDtd;
this.documentBuilderFactory = null;
this.saxParserFactory = null;
this.xmlInputFactory = null;
} }
/** /**
@ -124,6 +133,9 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
if (processExternalEntities) { if (processExternalEntities) {
this.supportDtd = true; this.supportDtd = true;
} }
this.documentBuilderFactory = null;
this.saxParserFactory = null;
this.xmlInputFactory = null;
} }
/** /**
@ -165,17 +177,21 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
private DOMSource readDOMSource(InputStream body, HttpInputMessage inputMessage) throws IOException { private DOMSource readDOMSource(InputStream body, HttpInputMessage inputMessage) throws IOException {
try { try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory builderFactory = this.documentBuilderFactory;
documentBuilderFactory.setNamespaceAware(true); if (builderFactory == null) {
documentBuilderFactory.setFeature( builderFactory = DocumentBuilderFactory.newInstance();
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); builderFactory.setNamespaceAware(true);
documentBuilderFactory.setFeature( builderFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities()); "http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); builderFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.documentBuilderFactory = builderFactory;
}
DocumentBuilder builder = builderFactory.newDocumentBuilder();
if (!isProcessExternalEntities()) { if (!isProcessExternalEntities()) {
documentBuilder.setEntityResolver(NO_OP_ENTITY_RESOLVER); builder.setEntityResolver(NO_OP_ENTITY_RESOLVER);
} }
Document document = documentBuilder.parse(body); Document document = builder.parse(body);
return new DOMSource(document); return new DOMSource(document);
} }
catch (NullPointerException ex) { catch (NullPointerException ex) {
@ -197,11 +213,17 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
private SAXSource readSAXSource(InputStream body, HttpInputMessage inputMessage) throws IOException { private SAXSource readSAXSource(InputStream body, HttpInputMessage inputMessage) throws IOException {
try { try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); SAXParserFactory parserFactory = this.saxParserFactory;
saxParserFactory.setNamespaceAware(true); if (parserFactory == null) {
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); parserFactory = SAXParserFactory.newInstance();
saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities()); parserFactory.setNamespaceAware(true);
SAXParser saxParser = saxParserFactory.newSAXParser(); parserFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
parserFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.saxParserFactory = parserFactory;
}
SAXParser saxParser = parserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader(); XMLReader xmlReader = saxParser.getXMLReader();
if (!isProcessExternalEntities()) { if (!isProcessExternalEntities()) {
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER); xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
@ -217,11 +239,15 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
private Source readStAXSource(InputStream body, HttpInputMessage inputMessage) { private Source readStAXSource(InputStream body, HttpInputMessage inputMessage) {
try { try {
XMLInputFactory inputFactory = XMLInputFactory.newInstance(); XMLInputFactory inputFactory = this.xmlInputFactory;
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, isSupportDtd()); if (inputFactory == null) {
inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, isProcessExternalEntities()); inputFactory = XMLInputFactory.newInstance();
if (!isProcessExternalEntities()) { inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, isSupportDtd());
inputFactory.setXMLResolver(NO_OP_XML_RESOLVER); inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, isProcessExternalEntities());
if (!isProcessExternalEntities()) {
inputFactory.setXMLResolver(NO_OP_XML_RESOLVER);
}
this.xmlInputFactory = inputFactory;
} }
XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body); XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body);
return new StAXSource(streamReader); return new StAXSource(streamReader);

Loading…
Cancel
Save