This is an automated email from the ASF dual-hosted git repository. davydotcom pushed a commit to branch patch-gsp-performance in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 85148d461234e0ed066d562c71f0fa4a01e8633c Author: David Estes <[email protected]> AuthorDate: Fri Oct 3 14:43:54 2025 -0400 First Step in optimizing GSP Performance on Grails 7. Safe small optimizations to start --- .../src/main/groovy/org/grails/gsp/GroovyPage.java | 31 ++++++++++------ .../groovy/org/grails/gsp/GroovyPageMetaInfo.java | 41 ++++++++++++++++++++++ .../groovy/org/grails/gsp/GroovyPageWritable.java | 20 +++++------ .../grails/gsp/compiler/GroovyPageCompiler.groovy | 14 +++++--- .../org/grails/gsp/compiler/GroovyPageParser.java | 7 ++-- 5 files changed, 85 insertions(+), 28 deletions(-) diff --git a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java index d59a5d54d2..cb720573b7 100644 --- a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java +++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java @@ -130,14 +130,19 @@ public abstract class GroovyPage extends Script { throw new IllegalStateException("Setting out in page isn't allowed."); } - public void initRun(Writer target, OutputContext outputContext, GroovyPageMetaInfo metaInfo) { - OutputEncodingStackAttributes.Builder attributesBuilder = new OutputEncodingStackAttributes.Builder(); + public void initCommonRun(GroovyPageMetaInfo metaInfo) { if (metaInfo != null) { + setHtmlParts(metaInfo.getHtmlParts()); + setHtmlPartsSet(metaInfo.getHtmlPartsSet()); setJspTags(metaInfo.getJspTags()); setJspTagLibraryResolver(metaInfo.getJspTagLibraryResolver()); setGspTagLibraryLookup(metaInfo.getTagLibraryLookup()); - setHtmlParts(metaInfo.getHtmlParts()); setPluginContextPath(metaInfo.getPluginPath()); + } + } + public void initRun(Writer target, OutputContext outputContext, GroovyPageMetaInfo metaInfo) { + OutputEncodingStackAttributes.Builder attributesBuilder = new OutputEncodingStackAttributes.Builder(); + if (metaInfo != null) { attributesBuilder.outEncoder(metaInfo.getOutEncoder()); attributesBuilder.staticEncoder(metaInfo.getStaticEncoder()); attributesBuilder.expressionEncoder(metaInfo.getExpressionEncoder()); @@ -507,6 +512,14 @@ public abstract class GroovyPage extends Script { staticOut.write(htmlParts[partNumber]); } + /** + * Shorthand for printHtmlPart to reduce class size + * @param partNumber + */ + public final void h(final int partNumber) { + staticOut.write(htmlParts[partNumber]); + } + /** * Sets the JSP tags used by this GroovyPage instance * @@ -527,14 +540,10 @@ public abstract class GroovyPage extends Script { public void setHtmlParts(String[] htmlParts) { this.htmlParts = htmlParts; - this.htmlPartsSet = new HashSet<>(); - if (htmlParts != null) { - for (String htmlPart : htmlParts) { - if (htmlPart != null) { - htmlPartsSet.add(System.identityHashCode(htmlPart)); - } - } - } + } + + public void setHtmlPartsSet(Set<Integer> htmlPartsSet) { + this.htmlPartsSet = htmlPartsSet; } public final OutputEncodingStack getOutputStack() { diff --git a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java index 0cd8efd298..310a49af04 100644 --- a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java +++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java @@ -22,7 +22,10 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Writer; +import java.lang.ref.SoftReference; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLConnection; @@ -67,14 +70,17 @@ public class GroovyPageMetaInfo implements GrailsApplicationAware { private static final Log LOG = LogFactory.getLog(GroovyPageMetaInfo.class); private TagLibraryLookup tagLibraryLookup; private TagLibraryResolver jspTagLibraryResolver; + private ThreadLocal<SoftReference<GroovyPage>> pageInstance = new ThreadLocal<>(); private boolean precompiledMode = false; private Class<?> pageClass; + private Constructor<?> pageClassConstructor; private long lastModified; private InputStream groovySource; private String contentType; private int[] lineNumbers; private String[] htmlParts; + private Set<Integer> htmlPartsSet; @SuppressWarnings("rawtypes") private Map jspTags = Collections.emptyMap(); private GroovyPagesException compilationException; @@ -115,6 +121,11 @@ public class GroovyPageMetaInfo implements GrailsApplicationAware { this(); precompiledMode = true; this.pageClass = pageClass; + try { + this.pageClassConstructor = pageClass.getConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } contentType = (String) ReflectionUtils.getField(ReflectionUtils.findField(pageClass, GroovyPageParser.CONSTANT_NAME_CONTENT_TYPE), null); jspTags = (Map) ReflectionUtils.getField(ReflectionUtils.findField(pageClass, GroovyPageParser.CONSTANT_NAME_JSP_TAGS), null); lastModified = (Long) ReflectionUtils.getField(ReflectionUtils.findField(pageClass, GroovyPageParser.CONSTANT_NAME_LAST_MODIFIED), null); @@ -282,8 +293,26 @@ public class GroovyPageMetaInfo implements GrailsApplicationAware { return pageClass; } + public GroovyPage getPageClassInstance() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + SoftReference<GroovyPage> pageSoftRef = pageInstance.get(); + + GroovyPage pageCacheEntry = pageSoftRef != null ? pageSoftRef.get() : null; + if(pageCacheEntry == null) { + LOG.info("Loading Page: " + pageClass.getName()); + pageCacheEntry = (GroovyPage) pageClassConstructor.newInstance(); + pageCacheEntry.initCommonRun(this); + pageInstance.set(new SoftReference<>(pageCacheEntry)); + } + return pageCacheEntry; + } + public void setPageClass(Class<?> pageClass) { this.pageClass = pageClass; + try { + this.pageClassConstructor = pageClass.getConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } initializePluginPath(); } @@ -359,6 +388,18 @@ public class GroovyPageMetaInfo implements GrailsApplicationAware { public void setHtmlParts(String[] htmlParts) { this.htmlParts = htmlParts; + this.htmlPartsSet = new HashSet<>(); + if (htmlParts != null) { + for (String htmlPart : htmlParts) { + if (htmlPart != null) { + htmlPartsSet.add(System.identityHashCode(htmlPart)); + } + } + } + } + + Set<Integer> getHtmlPartsSet() { + return this.htmlPartsSet; } public void applyLastModifiedFromResource(Resource resource) { diff --git a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java index 6fb297db3c..4f04b9e043 100644 --- a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java +++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java @@ -26,13 +26,10 @@ import java.io.Reader; import java.io.Writer; import java.util.LinkedHashMap; import java.util.Map; - import groovy.lang.Binding; import groovy.lang.Writable; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import grails.util.Environment; import org.grails.taglib.AbstractTemplateVariableBinding; import org.grails.taglib.TemplateVariableBinding; @@ -47,7 +44,8 @@ import org.grails.taglib.encoder.OutputContextLookup; * @since 0.5 */ public class GroovyPageWritable implements Writable { - private static final Log LOG = LogFactory.getLog(GroovyPageWritable.class); + //get SLF4J logger instead + private static final Logger LOG = LoggerFactory.getLogger(GroovyPageWritable.class); private static final String GSP_NONE_CODEC_NAME = "none"; private GroovyPageMetaInfo metaInfo; private OutputContextLookup outputContextLookup; @@ -125,9 +123,7 @@ public class GroovyPageWritable implements Writable { // only try to set content type when evaluating top level GSP boolean contentTypeAlreadySet = outputContext.isContentTypeAlreadySet(); if (!contentTypeAlreadySet) { - if (LOG.isDebugEnabled()) { - LOG.debug("Writing output with content type: " + metaInfo.getContentType()); - } + LOG.debug("Writing output with content type: {}", metaInfo.getContentType()); outputContext.setContentType(metaInfo.getContentType()); // must come before response.getWriter() } } @@ -139,7 +135,9 @@ public class GroovyPageWritable implements Writable { GroovyPage page = null; try { - page = (GroovyPage) metaInfo.getPageClass().newInstance(); + + page = metaInfo.getPageClassInstance(); + } catch (Exception e) { throw new GroovyPagesException("Problem instantiating page class", e); } @@ -294,4 +292,6 @@ public class GroovyPageWritable implements Writable { in.close(); } } + + } diff --git a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy index 3a9917a415..ba91149922 100644 --- a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy +++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy @@ -31,8 +31,10 @@ import org.codehaus.groovy.control.CompilationUnit import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.Phases -import org.apache.commons.logging.Log +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.commons.logging.LogFactory +import org.slf4j.Logger import org.springframework.core.CollectionFactory @@ -42,9 +44,12 @@ import org.grails.config.CodeGenConfig import org.grails.gsp.GroovyPageMetaInfo import org.grails.gsp.compiler.transform.GroovyPageInjectionOperation import org.grails.taglib.encoder.OutputEncodingSettings - +import org.grails.gsp.GroovyPage /** - * Used to compile GSP files into a specified target directory. + * Used to compile GSP files into a specified target directory. The compiler creates 3 files per page. + * Firstly, it generates a {@link GroovyPage} derived class which is then compiled to a .class file. + * It also will generate a "_html.data" and a "_linenumbers.data" file which contain the static HTML parts of the page. + * These are read at runtime by the {@link org.grails.gsp.GroovyPagesTemplateEngine} class. * * @author Graeme Rocher * @since 1.2 @@ -52,7 +57,7 @@ import org.grails.taglib.encoder.OutputEncodingSettings @CompileStatic class GroovyPageCompiler { - private static final Log LOG = LogFactory.getLog(GroovyPageCompiler) + private static final Logger LOG = LoggerFactory.getLogger(GroovyPageCompiler) private Map compileGSPRegistry = [:] private Object mutexObject = new Object() @@ -238,7 +243,6 @@ class GroovyPageCompiler { CompilationUnit unit = new CompilationUnit(compilerConfig, null, classLoader) unit.addPhaseOperation(operation, Phases.CANONICALIZATION) unit.addSource(gspgroovyfile.name, gsptarget.toString()) - // unit.addSource(gspgroovyfile) unit.compile() } } diff --git a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java index 2f7040d24a..92b5672033 100644 --- a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java +++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java @@ -66,7 +66,10 @@ import org.grails.taglib.encoder.OutputEncodingSettings; /** * NOTE: Based on work done by the GSP standalone project (https://gsp.dev.java.net/). * <p> - * Parsing implementation for GSP files + * Parsing implementation for GSP files. This class is responsible for parsing .gsp extension files + * and converting them to Groovy source code that extends the {@link GroovyPage} base class. It also gathers + * taglib references and html parts (contants with no modification) and writes them to a separate file. + * For improved debugging, line number references are also stored for easier exception tracing. * * @author Troy Heninger * @author Graeme Rocher @@ -643,7 +646,7 @@ public class GroovyPageParser implements Tokens { } private void htmlPartPrintlnRaw(int partNumber) { - out.print("printHtmlPart("); + out.print("h("); out.print(String.valueOf(partNumber)); out.print(")"); out.println();
