Cache templates

This commit improves massively class loading performance by caching all
templates used by the generator. GroovyTemplate is now a bean rather than
a private utility.

Templates are cached by default and the cache is disabled automatically
if Devtools is in use (via a check of spring.groovy.template.cache).

Closes gh-288
This commit is contained in:
Stephane Nicoll 2016-09-13 14:34:10 +02:00
parent dd0442b5de
commit 61cc7eb41d
9 changed files with 100 additions and 40 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2015 the original author or authors. * Copyright 2012-2016 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.
@ -18,10 +18,9 @@ package io.spring.initializr.generator
import io.spring.initializr.metadata.InitializrMetadata import io.spring.initializr.metadata.InitializrMetadata
import io.spring.initializr.metadata.Type import io.spring.initializr.metadata.Type
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.util.VersionRange import io.spring.initializr.util.VersionRange
import static io.spring.initializr.util.GroovyTemplate.template
/** /**
* Generate help pages for command-line clients. * Generate help pages for command-line clients.
* *
@ -39,6 +38,12 @@ class CommandLineHelpGenerator {
=========|_|==============|___/=/_/_/_/ =========|_|==============|___/=/_/_/_/
''' '''
private final GroovyTemplate template
CommandLineHelpGenerator(GroovyTemplate template) {
this.template = template
}
/** /**
* Generate the capabilities of the service as a generic plain text * Generate the capabilities of the service as a generic plain text
* document. Used when no particular agent was detected. * document. Used when no particular agent was detected.
@ -46,7 +51,7 @@ class CommandLineHelpGenerator {
String generateGenericCapabilities(InitializrMetadata metadata, String serviceUrl) { String generateGenericCapabilities(InitializrMetadata metadata, String serviceUrl) {
def model = initializeCommandLineModel(metadata, serviceUrl) def model = initializeCommandLineModel(metadata, serviceUrl)
model['hasExamples'] = false model['hasExamples'] = false
template 'cli-capabilities.txt', model template.process 'cli-capabilities.txt', model
} }
/** /**
@ -55,9 +60,9 @@ class CommandLineHelpGenerator {
*/ */
String generateCurlCapabilities(InitializrMetadata metadata, String serviceUrl) { String generateCurlCapabilities(InitializrMetadata metadata, String serviceUrl) {
def model = initializeCommandLineModel(metadata, serviceUrl) def model = initializeCommandLineModel(metadata, serviceUrl)
model['examples'] = template 'curl-examples.txt', model model['examples'] = template.process 'curl-examples.txt', model
model['hasExamples'] = true model['hasExamples'] = true
template 'cli-capabilities.txt', model template.process 'cli-capabilities.txt', model
} }
/** /**
@ -66,9 +71,9 @@ class CommandLineHelpGenerator {
*/ */
String generateHttpieCapabilities(InitializrMetadata metadata, String serviceUrl) { String generateHttpieCapabilities(InitializrMetadata metadata, String serviceUrl) {
def model = initializeCommandLineModel(metadata, serviceUrl) def model = initializeCommandLineModel(metadata, serviceUrl)
model['examples'] = template 'httpie-examples.txt', model model['examples'] = template.process 'httpie-examples.txt', model
model['hasExamples'] = true model['hasExamples'] = true
template 'cli-capabilities.txt', model template.process 'cli-capabilities.txt', model
} }
/** /**
@ -78,7 +83,7 @@ class CommandLineHelpGenerator {
String generateSpringBootCliCapabilities(InitializrMetadata metadata, String serviceUrl) { String generateSpringBootCliCapabilities(InitializrMetadata metadata, String serviceUrl) {
def model = initializeSpringBootCliModel(metadata, serviceUrl) def model = initializeSpringBootCliModel(metadata, serviceUrl)
model['hasExamples'] = false model['hasExamples'] = false
template('boot-cli-capabilities.txt', model) template.process('boot-cli-capabilities.txt', model)
} }
protected Map initializeCommandLineModel(InitializrMetadata metadata, serviceUrl) { protected Map initializeCommandLineModel(InitializrMetadata metadata, serviceUrl) {

View File

@ -20,6 +20,7 @@ import groovy.util.logging.Slf4j
import io.spring.initializr.InitializrException import io.spring.initializr.InitializrException
import io.spring.initializr.metadata.Dependency import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadataProvider import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.util.Version import io.spring.initializr.util.Version
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
@ -28,7 +29,6 @@ import org.springframework.context.ApplicationEventPublisher
import org.springframework.util.Assert import org.springframework.util.Assert
import static io.spring.initializr.metadata.InitializrConfiguration.Env.Maven.ParentPom import static io.spring.initializr.metadata.InitializrConfiguration.Env.Maven.ParentPom
import static io.spring.initializr.util.GroovyTemplate.template
/** /**
* Generate a project based on the configured metadata. * Generate a project based on the configured metadata.
@ -58,6 +58,9 @@ class ProjectGenerator {
@Autowired @Autowired
ProjectRequestResolver requestResolver ProjectRequestResolver requestResolver
@Autowired
GroovyTemplate groovyTemplate = new GroovyTemplate()
@Autowired @Autowired
ProjectResourceLocator projectResourceLocator = new ProjectResourceLocator() ProjectResourceLocator projectResourceLocator = new ProjectResourceLocator()
@ -321,12 +324,12 @@ class ProjectGenerator {
} }
private byte[] doGenerateMavenPom(Map model) { private byte[] doGenerateMavenPom(Map model) {
template 'starter-pom.xml', model groovyTemplate.process 'starter-pom.xml', model
} }
private byte[] doGenerateGradleBuild(Map model) { private byte[] doGenerateGradleBuild(Map model) {
template 'starter-build.gradle', model groovyTemplate.process 'starter-build.gradle', model
} }
private void writeGradleWrapper(File dir) { private void writeGradleWrapper(File dir) {
@ -384,7 +387,7 @@ class ProjectGenerator {
def write(File target, String templateName, def model) { def write(File target, String templateName, def model) {
def tmpl = templateName.endsWith('.groovy') ? templateName + '.tmpl' : templateName def tmpl = templateName.endsWith('.groovy') ? templateName + '.tmpl' : templateName
def body = template tmpl, model def body = groovyTemplate.process tmpl, model
target.write(body) target.write(body)
} }

View File

@ -16,36 +16,52 @@
package io.spring.initializr.util package io.spring.initializr.util
import java.util.concurrent.ConcurrentMap
import groovy.text.GStringTemplateEngine import groovy.text.GStringTemplateEngine
import groovy.text.Template import groovy.text.Template
import groovy.text.TemplateEngine import groovy.text.TemplateEngine
import org.codehaus.groovy.control.CompilationFailedException import org.codehaus.groovy.control.CompilationFailedException
import org.springframework.util.ConcurrentReferenceHashMap
/** /**
* @author Dave Syer * @author Dave Syer
* @since 1.0 * @since 1.0
*/ */
class GroovyTemplate { class GroovyTemplate {
// This is a copy/paste from GroovyTemplate in spring-boot-cli. We should migrate boolean cache = true
// to Spring's native support available in 4.1
static String template(String name, Map<String, ?> model) throws IOException, private final TemplateEngine engine
CompilationFailedException, ClassNotFoundException { private final ConcurrentMap<String, Template> templateCaches = new ConcurrentReferenceHashMap<>()
template(new GStringTemplateEngine(), name, model)
GroovyTemplate(TemplateEngine engine) {
this.engine = engine
} }
static String template(TemplateEngine engine, String name, Map<String, ?> model) GroovyTemplate() {
this(new GStringTemplateEngine())
}
String process(String name, Map<String, ?> model)
throws IOException, CompilationFailedException, ClassNotFoundException { throws IOException, CompilationFailedException, ClassNotFoundException {
def writable = getTemplate(engine, name).make(model) def template = getTemplate(name)
def writable = template.make(model)
def result = new StringWriter() def result = new StringWriter()
writable.writeTo(result) writable.writeTo(result)
result.toString() result.toString()
} }
static Template getTemplate(TemplateEngine engine, String name) Template getTemplate(String name)
throws CompilationFailedException, ClassNotFoundException, IOException { throws CompilationFailedException, ClassNotFoundException, IOException {
if (cache) {
return this.templateCaches.computeIfAbsent(name, { n -> loadTemplate(n) })
}
return loadTemplate(name)
}
protected Template loadTemplate(String name) {
def file = new File("templates", name) def file = new File("templates", name)
if (file.exists()) { if (file.exists()) {
return engine.createTemplate(file) return engine.createTemplate(file)
@ -59,4 +75,5 @@ class GroovyTemplate {
return engine.createTemplate(name) return engine.createTemplate(name)
} }
} }

View File

@ -19,6 +19,7 @@ package io.spring.initializr.generator
import io.spring.initializr.metadata.Dependency import io.spring.initializr.metadata.Dependency
import io.spring.initializr.metadata.Type import io.spring.initializr.metadata.Type
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import io.spring.initializr.util.GroovyTemplate
import org.junit.Test import org.junit.Test
import static org.hamcrest.CoreMatchers.containsString import static org.hamcrest.CoreMatchers.containsString
@ -30,7 +31,7 @@ import static org.junit.Assert.assertThat
*/ */
class CommandLineHelpGeneratorTests { class CommandLineHelpGeneratorTests {
private CommandLineHelpGenerator generator = new CommandLineHelpGenerator() private CommandLineHelpGenerator generator = new CommandLineHelpGenerator(new GroovyTemplate())
@Test @Test
void generateGenericCapabilities() { void generateGenericCapabilities() {

View File

@ -18,6 +18,8 @@ package io.spring.initializr.service
import java.util.concurrent.Executor import java.util.concurrent.Executor
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.web.project.LegacyStsController import io.spring.initializr.web.project.LegacyStsController
import org.springframework.boot.SpringApplication import org.springframework.boot.SpringApplication
@ -44,8 +46,9 @@ class InitializrService {
@Bean @Bean
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
LegacyStsController legacyStsController() { LegacyStsController legacyStsController(InitializrMetadataProvider metadataProvider,
new LegacyStsController() GroovyTemplate groovyTemplate) {
new LegacyStsController(metadataProvider, groovyTemplate)
} }
@Configuration @Configuration

View File

@ -27,6 +27,7 @@ import io.spring.initializr.metadata.DependencyMetadataProvider
import io.spring.initializr.metadata.InitializrMetadataBuilder import io.spring.initializr.metadata.InitializrMetadataBuilder
import io.spring.initializr.metadata.InitializrMetadataProvider import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.metadata.InitializrProperties import io.spring.initializr.metadata.InitializrProperties
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.web.project.MainController import io.spring.initializr.web.project.MainController
import io.spring.initializr.web.support.DefaultDependencyMetadataProvider import io.spring.initializr.web.support.DefaultDependencyMetadataProvider
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider import io.spring.initializr.web.support.DefaultInitializrMetadataProvider
@ -34,6 +35,7 @@ import io.spring.initializr.web.ui.UiController
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.bind.RelaxedPropertyResolver
import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.Cache import org.springframework.cache.Cache
import org.springframework.cache.CacheManager import org.springframework.cache.CacheManager
@ -43,6 +45,7 @@ import org.springframework.cache.concurrent.ConcurrentMapCache
import org.springframework.cache.support.SimpleCacheManager import org.springframework.cache.support.SimpleCacheManager
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
/** /**
@ -72,8 +75,11 @@ class InitializrAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
MainController initializrMainController() { MainController initializrMainController(InitializrMetadataProvider metadataProvider,
new MainController() GroovyTemplate groovyTemplate,
ProjectGenerator projectGenerator,
DependencyMetadataProvider dependencyMetadataProvider) {
new MainController(metadataProvider, groovyTemplate, projectGenerator, dependencyMetadataProvider)
} }
@Bean @Bean
@ -88,6 +94,16 @@ class InitializrAutoConfiguration {
new ProjectGenerator() new ProjectGenerator()
} }
@Bean
@ConditionalOnMissingBean
GroovyTemplate groovyTemplate(Environment environment) {
def resolver = new RelaxedPropertyResolver(environment, 'spring.groovy.template.')
boolean cache = resolver.getProperty('cache', Boolean.class, true)
def groovyTemplate = new GroovyTemplate()
groovyTemplate.cache = cache
groovyTemplate
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
ProjectRequestResolver projectRequestResolver() { ProjectRequestResolver projectRequestResolver() {

View File

@ -21,14 +21,12 @@ import javax.servlet.http.HttpServletResponse
import io.spring.initializr.generator.InvalidProjectRequestException import io.spring.initializr.generator.InvalidProjectRequestException
import io.spring.initializr.metadata.InitializrMetadataProvider import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.GroovyTemplate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.servlet.support.ServletUriComponentsBuilder import org.springframework.web.servlet.support.ServletUriComponentsBuilder
import static io.spring.initializr.util.GroovyTemplate.template
/** /**
* A base controller that uses a {@link InitializrMetadataProvider} * A base controller that uses a {@link InitializrMetadataProvider}
* *
@ -37,11 +35,16 @@ import static io.spring.initializr.util.GroovyTemplate.template
*/ */
abstract class AbstractInitializrController { abstract class AbstractInitializrController {
@Autowired protected final InitializrMetadataProvider metadataProvider
protected InitializrMetadataProvider metadataProvider private final GroovyTemplate groovyTemplate
private boolean forceSsl private boolean forceSsl
protected AbstractInitializrController(InitializrMetadataProvider metadataProvider,
GroovyTemplate groovyTemplate) {
this.metadataProvider = metadataProvider
this.groovyTemplate = groovyTemplate
}
@PostConstruct @PostConstruct
void initialize() { void initialize() {
forceSsl = metadataProvider.get().configuration.env.forceSsl forceSsl = metadataProvider.get().configuration.env.forceSsl
@ -74,7 +77,7 @@ abstract class AbstractInitializrController {
// Google analytics support // Google analytics support
model['trackingCode'] = metadata.configuration.env.googleAnalyticsTrackingCode model['trackingCode'] = metadata.configuration.env.googleAnalyticsTrackingCode
template templatePath, model groovyTemplate.process templatePath, model
} }
/** /**

View File

@ -16,6 +16,9 @@
package io.spring.initializr.web.project package io.spring.initializr.web.project
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.GroovyTemplate
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.ResponseBody
@ -30,6 +33,10 @@ import org.springframework.web.bind.annotation.ResponseBody
@Deprecated @Deprecated
class LegacyStsController extends AbstractInitializrController { class LegacyStsController extends AbstractInitializrController {
LegacyStsController(InitializrMetadataProvider metadataProvider, GroovyTemplate groovyTemplate) {
super(metadataProvider, groovyTemplate)
}
@RequestMapping(value = '/sts', produces = 'text/html') @RequestMapping(value = '/sts', produces = 'text/html')
@ResponseBody @ResponseBody
String stsHome() { String stsHome() {

View File

@ -24,7 +24,9 @@ import io.spring.initializr.generator.BasicProjectRequest
import io.spring.initializr.generator.CommandLineHelpGenerator import io.spring.initializr.generator.CommandLineHelpGenerator
import io.spring.initializr.generator.ProjectGenerator import io.spring.initializr.generator.ProjectGenerator
import io.spring.initializr.generator.ProjectRequest import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.Agent import io.spring.initializr.util.Agent
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.web.mapper.DependencyMetadataV21JsonMapper import io.spring.initializr.web.mapper.DependencyMetadataV21JsonMapper
import io.spring.initializr.web.mapper.InitializrMetadataJsonMapper import io.spring.initializr.web.mapper.InitializrMetadataJsonMapper
import io.spring.initializr.web.mapper.InitializrMetadataV21JsonMapper import io.spring.initializr.web.mapper.InitializrMetadataV21JsonMapper
@ -67,14 +69,17 @@ class MainController extends AbstractInitializrController {
static final MediaType HAL_JSON_CONTENT_TYPE = MediaType.parseMediaType('application/hal+json') static final MediaType HAL_JSON_CONTENT_TYPE = MediaType.parseMediaType('application/hal+json')
@Autowired private final ProjectGenerator projectGenerator
private ProjectGenerator projectGenerator private final DependencyMetadataProvider dependencyMetadataProvider
private final CommandLineHelpGenerator commandLineHelpGenerator
@Autowired
private DependencyMetadataProvider dependencyMetadataProvider
private CommandLineHelpGenerator commandLineHelpGenerator = new CommandLineHelpGenerator()
MainController(InitializrMetadataProvider metadataProvider, GroovyTemplate groovyTemplate,
ProjectGenerator projectGenerator, DependencyMetadataProvider dependencyMetadataProvider) {
super(metadataProvider, groovyTemplate)
this.projectGenerator = projectGenerator
this.dependencyMetadataProvider = dependencyMetadataProvider
this.commandLineHelpGenerator = new CommandLineHelpGenerator(groovyTemplate)
}
@ModelAttribute @ModelAttribute
BasicProjectRequest projectRequest(@RequestHeader Map<String,String> headers) { BasicProjectRequest projectRequest(@RequestHeader Map<String,String> headers) {