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");
* 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.Type
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.util.VersionRange
import static io.spring.initializr.util.GroovyTemplate.template
/**
* 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
* document. Used when no particular agent was detected.
@ -46,7 +51,7 @@ class CommandLineHelpGenerator {
String generateGenericCapabilities(InitializrMetadata metadata, String serviceUrl) {
def model = initializeCommandLineModel(metadata, serviceUrl)
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) {
def model = initializeCommandLineModel(metadata, serviceUrl)
model['examples'] = template 'curl-examples.txt', model
model['examples'] = template.process 'curl-examples.txt', model
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) {
def model = initializeCommandLineModel(metadata, serviceUrl)
model['examples'] = template 'httpie-examples.txt', model
model['examples'] = template.process 'httpie-examples.txt', model
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) {
def model = initializeSpringBootCliModel(metadata, serviceUrl)
model['hasExamples'] = false
template('boot-cli-capabilities.txt', model)
template.process('boot-cli-capabilities.txt', model)
}
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.metadata.Dependency
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.util.Version
import org.springframework.beans.factory.annotation.Autowired
@ -28,7 +29,6 @@ import org.springframework.context.ApplicationEventPublisher
import org.springframework.util.Assert
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.
@ -58,6 +58,9 @@ class ProjectGenerator {
@Autowired
ProjectRequestResolver requestResolver
@Autowired
GroovyTemplate groovyTemplate = new GroovyTemplate()
@Autowired
ProjectResourceLocator projectResourceLocator = new ProjectResourceLocator()
@ -321,12 +324,12 @@ class ProjectGenerator {
}
private byte[] doGenerateMavenPom(Map model) {
template 'starter-pom.xml', model
groovyTemplate.process 'starter-pom.xml', model
}
private byte[] doGenerateGradleBuild(Map model) {
template 'starter-build.gradle', model
groovyTemplate.process 'starter-build.gradle', model
}
private void writeGradleWrapper(File dir) {
@ -384,7 +387,7 @@ class ProjectGenerator {
def write(File target, String templateName, def model) {
def tmpl = templateName.endsWith('.groovy') ? templateName + '.tmpl' : templateName
def body = template tmpl, model
def body = groovyTemplate.process tmpl, model
target.write(body)
}

View File

@ -16,36 +16,52 @@
package io.spring.initializr.util
import java.util.concurrent.ConcurrentMap
import groovy.text.GStringTemplateEngine
import groovy.text.Template
import groovy.text.TemplateEngine
import org.codehaus.groovy.control.CompilationFailedException
import org.springframework.util.ConcurrentReferenceHashMap
/**
* @author Dave Syer
* @since 1.0
*/
class GroovyTemplate {
// This is a copy/paste from GroovyTemplate in spring-boot-cli. We should migrate
// to Spring's native support available in 4.1
boolean cache = true
static String template(String name, Map<String, ?> model) throws IOException,
CompilationFailedException, ClassNotFoundException {
template(new GStringTemplateEngine(), name, model)
private final TemplateEngine engine
private final ConcurrentMap<String, Template> templateCaches = new ConcurrentReferenceHashMap<>()
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 {
def writable = getTemplate(engine, name).make(model)
def template = getTemplate(name)
def writable = template.make(model)
def result = new StringWriter()
writable.writeTo(result)
result.toString()
}
static Template getTemplate(TemplateEngine engine, String name)
Template getTemplate(String name)
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)
if (file.exists()) {
return engine.createTemplate(file)
@ -59,4 +75,5 @@ class GroovyTemplate {
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.Type
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder
import io.spring.initializr.util.GroovyTemplate
import org.junit.Test
import static org.hamcrest.CoreMatchers.containsString
@ -30,7 +31,7 @@ import static org.junit.Assert.assertThat
*/
class CommandLineHelpGeneratorTests {
private CommandLineHelpGenerator generator = new CommandLineHelpGenerator()
private CommandLineHelpGenerator generator = new CommandLineHelpGenerator(new GroovyTemplate())
@Test
void generateGenericCapabilities() {

View File

@ -18,6 +18,8 @@ package io.spring.initializr.service
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 org.springframework.boot.SpringApplication
@ -44,8 +46,9 @@ class InitializrService {
@Bean
@SuppressWarnings("deprecation")
LegacyStsController legacyStsController() {
new LegacyStsController()
LegacyStsController legacyStsController(InitializrMetadataProvider metadataProvider,
GroovyTemplate groovyTemplate) {
new LegacyStsController(metadataProvider, groovyTemplate)
}
@Configuration

View File

@ -27,6 +27,7 @@ import io.spring.initializr.metadata.DependencyMetadataProvider
import io.spring.initializr.metadata.InitializrMetadataBuilder
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.metadata.InitializrProperties
import io.spring.initializr.util.GroovyTemplate
import io.spring.initializr.web.project.MainController
import io.spring.initializr.web.support.DefaultDependencyMetadataProvider
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.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.bind.RelaxedPropertyResolver
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.Cache
import org.springframework.cache.CacheManager
@ -43,6 +45,7 @@ import org.springframework.cache.concurrent.ConcurrentMapCache
import org.springframework.cache.support.SimpleCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
import org.springframework.web.client.RestTemplate
/**
@ -72,8 +75,11 @@ class InitializrAutoConfiguration {
@Bean
@ConditionalOnMissingBean
MainController initializrMainController() {
new MainController()
MainController initializrMainController(InitializrMetadataProvider metadataProvider,
GroovyTemplate groovyTemplate,
ProjectGenerator projectGenerator,
DependencyMetadataProvider dependencyMetadataProvider) {
new MainController(metadataProvider, groovyTemplate, projectGenerator, dependencyMetadataProvider)
}
@Bean
@ -88,6 +94,16 @@ class InitializrAutoConfiguration {
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
@ConditionalOnMissingBean
ProjectRequestResolver projectRequestResolver() {

View File

@ -21,14 +21,12 @@ import javax.servlet.http.HttpServletResponse
import io.spring.initializr.generator.InvalidProjectRequestException
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.web.bind.annotation.ExceptionHandler
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
import static io.spring.initializr.util.GroovyTemplate.template
/**
* A base controller that uses a {@link InitializrMetadataProvider}
*
@ -37,11 +35,16 @@ import static io.spring.initializr.util.GroovyTemplate.template
*/
abstract class AbstractInitializrController {
@Autowired
protected InitializrMetadataProvider metadataProvider
protected final InitializrMetadataProvider metadataProvider
private final GroovyTemplate groovyTemplate
private boolean forceSsl
protected AbstractInitializrController(InitializrMetadataProvider metadataProvider,
GroovyTemplate groovyTemplate) {
this.metadataProvider = metadataProvider
this.groovyTemplate = groovyTemplate
}
@PostConstruct
void initialize() {
forceSsl = metadataProvider.get().configuration.env.forceSsl
@ -74,7 +77,7 @@ abstract class AbstractInitializrController {
// Google analytics support
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
import io.spring.initializr.metadata.InitializrMetadataProvider
import io.spring.initializr.util.GroovyTemplate
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
@ -30,6 +33,10 @@ import org.springframework.web.bind.annotation.ResponseBody
@Deprecated
class LegacyStsController extends AbstractInitializrController {
LegacyStsController(InitializrMetadataProvider metadataProvider, GroovyTemplate groovyTemplate) {
super(metadataProvider, groovyTemplate)
}
@RequestMapping(value = '/sts', produces = 'text/html')
@ResponseBody
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.ProjectGenerator
import io.spring.initializr.generator.ProjectRequest
import io.spring.initializr.metadata.InitializrMetadataProvider
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.InitializrMetadataJsonMapper
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')
@Autowired
private ProjectGenerator projectGenerator
@Autowired
private DependencyMetadataProvider dependencyMetadataProvider
private CommandLineHelpGenerator commandLineHelpGenerator = new CommandLineHelpGenerator()
private final ProjectGenerator projectGenerator
private final DependencyMetadataProvider dependencyMetadataProvider
private final CommandLineHelpGenerator 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
BasicProjectRequest projectRequest(@RequestHeader Map<String,String> headers) {