Add script based templating support

This commit adds support for script based templating. Any templating
library running on top of a JSR-223 ScriptEngine that implements
Invocable like Nashorn or JRuby could be used.

For example, in order to render Mustache templates thanks to the Nashorn
Javascript engine provided with Java 8+, you should declare the following
configuration:

@Configuration
@EnableWebMvc
public class MustacheConfig extends WebMvcConfigurerAdapter {

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		registry.scriptTemplate();
	}

	@Bean
	public ScriptTemplateConfigurer configurer() {
		ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
		configurer.setEngineName("nashorn");
		configurer.setScripts("mustache.js");
		configurer.setRenderObject("Mustache");
		configurer.setRenderFunction("render");
		return configurer;
	}
}

The XML counterpart is:

<beans>
	<mvc:annotation-driven />

	<mvc:view-resolvers>
		<mvc:script-template />
	</mvc:view-resolvers>

	<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
		<mvc:script location="mustache.js" />
	</mvc:script-template-configurer>
</beans>

Tested with:
 - Handlebars running on Nashorn
 - Mustache running on Nashorn
 - React running on Nashorn
 - EJS running on Nashorn
 - ERB running on JRuby
 - String templates running on Jython

Issue: SPR-12266
This commit is contained in:
Sebastien Deleuze
2015-03-17 18:14:05 +01:00
parent b6327acec8
commit a3159dfbf2
36 changed files with 1792 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Sebastien Deleuze
* @since 3.0
*/
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
@@ -42,6 +43,7 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport {
registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
/**
* Parse the <mvc:script-template-configurer> MVC namespace element and register a
* {@code ScriptTemplateConfigurer} bean.
*
* @author Sebastien Deleuze
* @since 4.2
*/
public class ScriptTemplateConfigurerBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
public static final String BEAN_NAME = "mvcScriptTemplateConfigurer";
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
return BEAN_NAME;
}
@Override
protected String getBeanClassName(Element element) {
return "org.springframework.web.servlet.view.script.ScriptTemplateConfigurer";
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "script");
if (!childElements.isEmpty()) {
List<String> locations = new ArrayList<String>(childElements.size());
for (Element childElement : childElements) {
locations.add(childElement.getAttribute("location"));
}
builder.addPropertyValue("scripts", locations.toArray(new String[locations.size()]));
}
builder.addPropertyValue("engineName", element.getAttribute("engine-name"));
if (element.hasAttribute("render-object")) {
builder.addPropertyValue("renderObject", element.getAttribute("render-object"));
}
if (element.hasAttribute("render-function")) {
builder.addPropertyValue("renderFunction", element.getAttribute("render-function"));
}
if (element.hasAttribute("charset")) {
builder.addPropertyValue("charset", Charset.forName(element.getAttribute("charset")));
}
if (element.hasAttribute("resource-loader-path")) {
builder.addPropertyValue("resourceLoaderPath", element.getAttribute("resource-loader-path"));
}
}
@Override
protected boolean isEligibleAttribute(String name) {
return (name.equals("engine-name") || name.equals("scripts") || name.equals("render-object") ||
name.equals("render-function") || name.equals("charset") || name.equals("resource-loader-path"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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,7 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.ViewResolverComposite;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
@@ -60,6 +61,8 @@ import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
* @see TilesConfigurerBeanDefinitionParser
* @see FreeMarkerConfigurerBeanDefinitionParser
* @see VelocityConfigurerBeanDefinitionParser
* @see GroovyMarkupConfigurerBeanDefinitionParser
* @see ScriptTemplateConfigurerBeanDefinitionParser
*/
public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
@@ -72,7 +75,7 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
ManagedList<Object> resolvers = new ManagedList<Object>(4);
resolvers.setSource(context.extractSource(element));
String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "groovy", "bean", "ref"};
String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "groovy", "script-template", "bean", "ref"};
for (Element resolverElement : DomUtils.getChildElementsByTagName(element, names)) {
String name = resolverElement.getLocalName();
@@ -106,6 +109,10 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
resolverBeanDef.getPropertyValues().add("suffix", ".tpl");
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
}
else if ("script-template".equals(name)) {
resolverBeanDef = new RootBeanDefinition(ScriptTemplateViewResolver.class);
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
}
else if ("bean-name".equals(name)) {
resolverBeanDef = new RootBeanDefinition(BeanNameViewResolver.class);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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 org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
@@ -233,6 +235,22 @@ public class ViewResolverRegistry {
return registration;
}
/**
* Register a script template view resolver with an empty default view name prefix and suffix.
* @since 4.2
*/
public UrlBasedViewResolverRegistration scriptTemplate() {
if (this.applicationContext != null && !hasBeanOfType(ScriptTemplateConfigurer.class)) {
throw new BeanInitializationException("In addition to a script template view resolver " +
"there must also be a single ScriptTemplateConfig bean in this web application context " +
"(or its parent): ScriptTemplateConfigurer is the usual implementation. " +
"This bean may be given any name.");
}
ScriptRegistration registration = new ScriptRegistration();
this.viewResolvers.add(registration.getViewResolver());
return registration;
}
/**
* Register a bean name view resolver that interprets view names as the names
* of {@link org.springframework.web.servlet.View} beans.
@@ -324,4 +342,12 @@ public class ViewResolverRegistry {
}
}
private static class ScriptRegistration extends UrlBasedViewResolverRegistration {
private ScriptRegistration() {
super(new ScriptTemplateViewResolver());
getViewResolver();
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset;
import javax.script.ScriptEngine;
import org.springframework.core.io.ResourceLoader;
/**
* Interface to be implemented by objects that configure and manage a
* {@link ScriptEngine} for automatic lookup in a web environment.
* Detected and used by {@link ScriptTemplateView}.
*
* @author Sebastien Deleuze
* @since 4.2
*/
public interface ScriptTemplateConfig {
ScriptEngine getEngine();
String getRenderObject();
String getRenderFunction();
Charset getCharset();
ResourceLoader getResourceLoader();
}

View File

@@ -0,0 +1,257 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.view.script;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An implementation of Spring MVC's {@link ScriptTemplateConfig} for creating
* a {@code ScriptEngine} for use in a web application.
*
* <pre class="code">
*
* // Add the following to an &#64;Configuration class
*
* &#64;Bean
* public ScriptTemplateConfigurer mustacheConfigurer() {
*
* ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
* configurer.setEngineName("nashorn");
* configurer.setScripts("mustache.js");
* configurer.setRenderObject("Mustache");
* configurer.setRenderFunction("render");
* return configurer;
* }
* </pre>
*
* @author Sebastien Deleuze
* @since 4.2
* @see ScriptTemplateView
*/
public class ScriptTemplateConfigurer implements ScriptTemplateConfig, ApplicationContextAware, InitializingBean {
private ScriptEngine engine;
private String engineName;
private ApplicationContext applicationContext;
private String[] scripts;
private String renderObject;
private String renderFunction;
private Charset charset = Charset.forName("UTF-8");
private ResourceLoader resourceLoader;
private String resourceLoaderPath = "classpath:";
/**
* Set the {@link ScriptEngine} to use by the view.
* The script engine must implement {@code Invocable}.
* You must define {@code engine} or {@code engineName}, not both.
*/
public void setEngine(ScriptEngine engine) {
Assert.isInstanceOf(Invocable.class, engine);
this.engine = engine;
}
@Override
public ScriptEngine getEngine() {
return this.engine;
}
/**
* Set the engine name that will be used to instantiate the {@link ScriptEngine}.
* The script engine must implement {@code Invocable}.
* You must define {@code engine} or {@code engineName}, not both.
*/
public void setEngineName(String engineName) {
this.engineName = engineName;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
protected ApplicationContext getApplicationContext() {
return this.applicationContext;
}
/**
* Set the scripts to be loaded by the script engine (library or user provided).
* Since {@code resourceLoaderPath} default value is "classpath:", you can load easily
* any script available on the classpath.
*
* For example, in order to use a Javascript library available as a WebJars dependency
* and a custom "render.js" file, you should call
* {@code configurer.setScripts("/META-INF/resources/webjars/library/version/library.js",
* "com/myproject/script/render.js");}.
*
* @see #setResourceLoaderPath(String)
* @see <a href="http://www.webjars.org">WebJars</a>
*/
public void setScripts(String... scriptNames) {
this.scripts = scriptNames;
}
@Override
public String getRenderObject() {
return renderObject;
}
/**
* Set the object where belongs the render function (optional).
* For example, in order to call {@code Mustache.render()}, {@code renderObject}
* should be set to {@code "Mustache"} and {@code renderFunction} to {@code "render"}.
*/
public void setRenderObject(String renderObject) {
this.renderObject = renderObject;
}
@Override
public String getRenderFunction() {
return renderFunction;
}
/**
* Set the render function name (mandatory). This function will be called with the
* following parameters:
* <ol>
* <li>{@code template}: the view template content (String)</li>
* <li>{@code model}: the view model (Map)</li>
* </ol>
*/
public void setRenderFunction(String renderFunction) {
this.renderFunction = renderFunction;
}
/**
* Set the charset used to read script and template files.
* ({@code UTF-8} by default).
*/
public void setCharset(Charset charset) {
this.charset = charset;
}
@Override
public Charset getCharset() {
return this.charset;
}
/**
* Set the resource loader path(s) via a Spring resource location.
* Accepts multiple locations as a comma-separated list of paths.
* Standard URLs like "file:" and "classpath:" and pseudo URLs are supported
* as understood by Spring's {@link org.springframework.core.io.ResourceLoader}.
* Relative paths are allowed when running in an ApplicationContext.
* Default is "classpath:".
*/
public void setResourceLoaderPath(String resourceLoaderPath) {
this.resourceLoaderPath = resourceLoaderPath;
}
public String getResourceLoaderPath() {
return resourceLoaderPath;
}
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.engine == null) {
this.engine = createScriptEngine();
}
Assert.state(this.renderFunction != null, "renderFunction property must be defined.");
this.resourceLoader = new DefaultResourceLoader(createClassLoader());
if (this.scripts != null) {
try {
for (String script : this.scripts) {
this.engine.eval(read(script));
}
}
catch (ScriptException e) {
throw new IllegalStateException("could not load script", e);
}
}
}
protected ClassLoader createClassLoader() throws IOException {
String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath);
List<URL> urls = new ArrayList<URL>();
for (String path : paths) {
Resource[] resources = getApplicationContext().getResources(path);
if (resources.length > 0) {
for (Resource resource : resources) {
if (resource.exists()) {
urls.add(resource.getURL());
}
}
}
}
ClassLoader classLoader = getApplicationContext().getClassLoader();
return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
}
private Reader read(String path) throws IOException {
Resource resource = this.resourceLoader.getResource(path);
Assert.state(resource.exists(), "Resource " + path + " not found.");
return new InputStreamReader(resource.getInputStream());
}
protected ScriptEngine createScriptEngine() throws IOException {
if (this.engine != null && this.engineName != null) {
throw new IllegalStateException("You should define engine or engineName properties, not both.");
}
if (this.engineName != null) {
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(this.engineName);
Assert.state(scriptEngine != null, "No engine \"" + this.engineName + "\" found.");
Assert.state(scriptEngine instanceof Invocable, "Script engine should be instance of Invocable");
this.engine = scriptEngine;
}
Assert.state(this.engine != null, "No script engine found, please specify valid engine or engineName properties.");
return this.engine;
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.view.script;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
/**
* An {@link org.springframework.web.servlet.view.AbstractUrlBasedView AbstractUrlBasedView}
* designed to run any template library based on a JSR-223 script engine.
*
* <p>Nashorn Javascript engine requires Java 8+.
*
* @author Sebastien Deleuze
* @since 4.2
* @see ScriptTemplateConfigurer
* @see ScriptTemplateViewResolver
*/
public class ScriptTemplateView extends AbstractUrlBasedView {
private ScriptEngine engine;
private String renderObject;
private String renderFunction;
private Charset charset;
private ResourceLoader resourceLoader;
/**
* Set the {@link ScriptEngine} to use in this view.
* <p>If not set, the engine is auto-detected by looking up up a single
* {@link ScriptTemplateConfig} bean in the web application context and using
* it to obtain the configured {@code ScriptEngine} instance.
* @see ScriptTemplateConfig
*/
public void setEngine(ScriptEngine engine) {
this.engine = engine;
}
/**
* Set the render function name. This function will be called with the
* following parameters:
* <ol>
* <li>{@code template}: the view template content (String)</li>
* <li>{@code model}: the view model (Map)</li>
* </ol>
* <p>If not set, the function name is auto-detected by looking up up a single
* {@link ScriptTemplateConfig} bean in the web application context and using
* it to obtain the configured {@code functionName} property.
* @see ScriptTemplateConfig
*/
public void setRenderFunction(String functionName) {
this.renderFunction = functionName;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
protected void initApplicationContext(ApplicationContext context) {
super.initApplicationContext(context);
ScriptTemplateConfig viewConfig = autodetectViewConfig();
if (this.engine == null) {
this.engine = viewConfig.getEngine();
Assert.state(this.engine != null, "Script engine should not be null.");
Assert.state(this.engine instanceof Invocable, "Script engine should be instance of Invocable");
}
if (this.resourceLoader == null) {
this.resourceLoader = viewConfig.getResourceLoader();
}
if (this.renderObject == null) {
this.renderObject = viewConfig.getRenderObject();
}
if (this.renderFunction == null) {
this.renderFunction = viewConfig.getRenderFunction();
}
if (this.charset == null) {
this.charset = viewConfig.getCharset();
}
}
protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
try {
return BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(), ScriptTemplateConfig.class, true, false);
}
catch (NoSuchBeanDefinitionException ex) {
throw new ApplicationContextException("Expected a single ScriptTemplateConfig bean in the current " +
"Servlet web application context or the parent root context: ScriptTemplateConfigurer is " +
"the usual implementation. This bean may have any name.", ex);
}
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
Assert.notNull("Render function must not be null", this.renderFunction);
try {
String template = getTemplate(getUrl());
Object html = null;
if (this.renderObject != null) {
Object thiz = engine.eval(this.renderObject);
html = ((Invocable)this.engine).invokeMethod(thiz, this.renderFunction, template, model);
}
else {
html = ((Invocable)this.engine).invokeFunction(this.renderFunction, template, model);
}
response.getWriter().write(String.valueOf(html));
}
catch (Exception e) {
throw new IllegalStateException("failed to render template", e);
}
}
protected String getTemplate(String path) throws IOException {
Resource resource = this.resourceLoader.getResource(path);
Assert.state(resource.exists(), "Resource " + path + " not found.");
return StreamUtils.copyToString(resource.getInputStream(), this.charset);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.view.script;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
/**
* Convenience subclass of
* {@link org.springframework.web.servlet.view.UrlBasedViewResolver}
* that supports {@link ScriptTemplateView} and custom subclasses of it.
*
* <p>The view class for all views created by this resolver can be specified
* via the {@link #setViewClass(Class)} property.
*
* <p><b>Note:</b> When chaining ViewResolvers this resolver will check for the
* existence of the specified template resources and only return a non-null
* View object if a template is actually found.
*
* @author Sebastien Deleuze
* @since 4.2
* @see ScriptTemplateConfigurer
*/
public class ScriptTemplateViewResolver extends UrlBasedViewResolver {
public ScriptTemplateViewResolver() {
setViewClass(requiredViewClass());
}
@Override
protected Class<?> requiredViewClass() {
return ScriptTemplateView.class;
}
}