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. 43
      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. 21
      spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java
  4. 66
      spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java

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

@ -1,5 +1,5 @@ @@ -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");
* you may not use this file except in compliance with the License.
@ -37,6 +37,8 @@ import java.util.Calendar; @@ -37,6 +37,8 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.xml.XMLConstants;
import javax.xml.datatype.Duration;
@ -192,7 +194,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -192,7 +194,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
@Nullable
private ClassLoader beanClassLoader;
private final Object jaxbContextMonitor = new Object();
private final Lock jaxbContextLock = new ReentrantLock();
@Nullable
private volatile JAXBContext jaxbContext;
@ -204,6 +206,12 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -204,6 +206,12 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
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
@ -426,6 +434,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -426,6 +434,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
this.sourceParserFactory = null;
}
/**
@ -450,6 +459,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -450,6 +459,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
if (processExternalEntities) {
this.supportDtd = true;
}
this.sourceParserFactory = null;
}
/**
@ -497,7 +507,9 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -497,7 +507,9 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
if (context != null) {
return context;
}
synchronized (this.jaxbContextMonitor) {
this.jaxbContextLock.lock();
try {
context = this.jaxbContext;
if (context == null) {
try {
@ -521,6 +533,9 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -521,6 +533,9 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
}
return context;
}
finally {
this.jaxbContextLock.unlock();
}
}
private JAXBContext createJaxbContextFromContextPath(String contextPath) throws JAXBException {
@ -587,17 +602,24 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -587,17 +602,24 @@ 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];
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParserFactory saxParserFactory = this.schemaParserFactory;
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();
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);
InputSource inputSource = SaxResourceUtils.createInputSource(resource);
schemaSources[i] = new SAXSource(xmlReader, inputSource);
}
SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage);
if (this.schemaResourceResolver != null) {
schemaFactory.setResourceResolver(this.schemaResourceResolver);
@ -886,11 +908,16 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi @@ -886,11 +908,16 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
try {
if (xmlReader == null) {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParserFactory saxParserFactory = this.sourceParserFactory;
if (saxParserFactory == null) {
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());
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();
xmlReader = saxParser.getXMLReader();
}

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

@ -1,5 +1,5 @@ @@ -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");
* you may not use this file except in compliance with the License.
@ -83,9 +83,10 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { @@ -83,9 +83,10 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
private boolean processExternalEntities = false;
@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 { @@ -94,6 +95,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
this.documentBuilderFactory = null;
this.saxParserFactory = null;
}
/**
@ -118,6 +121,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { @@ -118,6 +121,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
if (processExternalEntities) {
this.supportDtd = true;
}
this.documentBuilderFactory = null;
this.saxParserFactory = null;
}
/**
@ -137,14 +142,13 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { @@ -137,14 +142,13 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
*/
protected Document buildDocument() {
try {
DocumentBuilder documentBuilder;
synchronized (this.documentBuilderFactoryMonitor) {
if (this.documentBuilderFactory == null) {
this.documentBuilderFactory = createDocumentBuilderFactory();
}
documentBuilder = createDocumentBuilder(this.documentBuilderFactory);
DocumentBuilderFactory builderFactory = this.documentBuilderFactory;
if (builderFactory == null) {
builderFactory = createDocumentBuilderFactory();
this.documentBuilderFactory = builderFactory;
}
return documentBuilder.newDocument();
DocumentBuilder builder = createDocumentBuilder(builderFactory);
return builder.newDocument();
}
catch (ParserConfigurationException ex) {
throw new UnmarshallingFailureException("Could not create document placeholder: " + ex.getMessage(), ex);
@ -179,11 +183,11 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { @@ -179,11 +183,11 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory)
throws ParserConfigurationException {
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
DocumentBuilder builder = factory.newDocumentBuilder();
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 { @@ -193,11 +197,17 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
* @throws ParserConfigurationException if thrown by JAXP methods
*/
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();
SAXParserFactory parserFactory = this.saxParserFactory;
if (parserFactory == null) {
parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
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();
if (!isProcessExternalEntities()) {
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);

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

@ -1,5 +1,5 @@ @@ -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");
* you may not use this file except in compliance with the License.
@ -61,6 +61,7 @@ import org.springframework.util.ClassUtils; @@ -61,6 +61,7 @@ import org.springframework.util.ClassUtils;
* @author Arjen Poutsma
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.0
* @see MarshallingHttpMessageConverter
*/
@ -70,6 +71,9 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa @@ -70,6 +71,9 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
private boolean processExternalEntities = false;
@Nullable
private volatile SAXParserFactory sourceParserFactory;
/**
* Indicate whether DTD parsing should be supported.
@ -77,6 +81,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa @@ -77,6 +81,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
this.sourceParserFactory = null;
}
/**
@ -97,6 +102,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa @@ -97,6 +102,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
if (processExternalEntities) {
this.supportDtd = true;
}
this.sourceParserFactory = null;
}
/**
@ -156,11 +162,16 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa @@ -156,11 +162,16 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
if (source instanceof StreamSource streamSource) {
InputSource inputSource = new InputSource(streamSource.getInputStream());
try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParserFactory saxParserFactory = this.sourceParserFactory;
if (saxParserFactory == null) {
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";
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();
XMLReader xmlReader = saxParser.getXMLReader();
if (!isProcessExternalEntities()) {

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

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

Loading…
Cancel
Save