Repository: PebbleTemplates/pebble
Pull request #489 (2019-12-03 19:48:08)

Add basic support for Spring 5 WebFlux reactive in Spring Boot 2 (#333)


Patch:
diff --git a/docs/src/orchid/resources/wiki/guide/spring-boot-integration.md b/docs/src/orchid/resources/wiki/guide/spring-boot-integration.md
index 0ed841da..123d5af5 100644
--- a/docs/src/orchid/resources/wiki/guide/spring-boot-integration.md
+++ b/docs/src/orchid/resources/wiki/guide/spring-boot-integration.md
@@ -105,6 +105,15 @@ public PebbleViewResolver pebbleViewResolver() {
    return new PebbleViewResolver();
 }
 ```
+
+For reactive app, you need to use the bean name ``pebbleReactiveViewResolver``:
+```java
+@Bean
+public PebbleReactiveViewResolver pebbleReactiveViewResolver() {
+   return new PebbleReactiveViewResolver(...);
+}
+```
+
 PLEASE NOTE: you need to change the Loader's prefix and suffix to match the custom ViewResolver's values.
 
 ### Using Pebble for other tasks
diff --git a/docs/src/orchid/resources/wiki/guide/spring-integration.md b/docs/src/orchid/resources/wiki/guide/spring-integration.md
index 33b1b03d..c47bc24a 100644
--- a/docs/src/orchid/resources/wiki/guide/spring-integration.md
+++ b/docs/src/orchid/resources/wiki/guide/spring-integration.md
@@ -35,7 +35,7 @@ public class MvcConfig extends WebMvcConfigurerAdapter {
 
     @Bean
     public Loader templateLoader(){
-        return new ServletLoader(servletContext);
+        return new ServletLoader(this.servletContext);
     }
     
     @Bean
@@ -47,7 +47,7 @@ public class MvcConfig extends WebMvcConfigurerAdapter {
     public PebbleEngine pebbleEngine() {
          return new PebbleEngine.Builder()
                 .loader(this.templateLoader())
-                .extension(springExtension())
+                .extension(this.springExtension())
                 .build();
     }
 
@@ -56,7 +56,7 @@ public class MvcConfig extends WebMvcConfigurerAdapter {
         PebbleViewResolver viewResolver = new PebbleViewResolver();
         viewResolver.setPrefix("/WEB-INF/templates/");
         viewResolver.setSuffix(".html");
-        viewResolver.setPebbleEngine(pebbleEngine());
+        viewResolver.setPebbleEngine(this.pebbleEngine());
         return viewResolver;
     }
 
@@ -75,7 +75,7 @@ public class ProfileController {
 	@RequestMapping
 	public ModelAndView getUserProfile(@RequestParam("id") long id) {
 		ModelAndView mav = new ModelAndView();
-		mav.addObject("user", userService.getUser(id));
+		mav.addObject("user", this.userService.getUser(id));
 		mav.setViewName("profile");
 		return mav;
 	}
@@ -168,7 +168,7 @@ To output any error:
 A timer in PebbleView is available to output the time taken to process a template. Just add the following config to your log4j.xml
 
 ```xml
-<Logger name="com.mitchellbosecke.pebble.spring.PebbleView.timer" level="DEBUG" additivity="false">
+<Logger name="com.mitchellbosecke.pebble.spring.servlet.PebbleView.timer" level="DEBUG" additivity="false">
       <AppenderRef ref="STDOUT" />
 </Logger> 
 ```
diff --git a/pebble-spring/pebble-spring-boot-starter/pom.xml b/pebble-spring/pebble-spring-boot-starter/pom.xml
index 979060c0..e3282b42 100644
--- a/pebble-spring/pebble-spring-boot-starter/pom.xml
+++ b/pebble-spring/pebble-spring-boot-starter/pom.xml
@@ -24,6 +24,12 @@
       <version>${boot.version}</version>
       <optional>true</optional>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-webflux</artifactId>
+      <version>${boot.version}</version>
+      <optional>true</optional>
+    </dependency>
     <dependency>
       <groupId>io.pebbletemplates</groupId>
       <artifactId>pebble-spring5</artifactId>
diff --git a/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java b/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java
index 57ecbbc7..fa4bc370 100644
--- a/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java
+++ b/pebble-spring/pebble-spring-boot-starter/src/main/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfiguration.java
@@ -4,23 +4,27 @@ import com.mitchellbosecke.pebble.PebbleEngine;
 import com.mitchellbosecke.pebble.extension.Extension;
 import com.mitchellbosecke.pebble.loader.ClasspathLoader;
 import com.mitchellbosecke.pebble.loader.Loader;
-import com.mitchellbosecke.pebble.spring.PebbleViewResolver;
 import com.mitchellbosecke.pebble.spring.extension.SpringExtension;
-import java.util.List;
-import javax.servlet.Servlet;
+import com.mitchellbosecke.pebble.spring.reactive.PebbleReactiveViewResolver;
+import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
+import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
 import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import java.util.List;
+
 @Configuration
 @ConditionalOnClass(PebbleEngine.class)
-@AutoConfigureAfter(WebMvcAutoConfiguration.class)
+@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
 @EnableConfigurationProperties(PebbleProperties.class)
 public class PebbleAutoConfiguration {
 
@@ -40,7 +44,6 @@ public class PebbleAutoConfiguration {
       loader.setSuffix(this.properties.getSuffix());
       return loader;
     }
-
   }
 
   @Configuration
@@ -82,9 +85,8 @@ public class PebbleAutoConfiguration {
   }
 
   @Configuration
-  @ConditionalOnWebApplication
-  @ConditionalOnClass({Servlet.class})
-  public static class PebbleViewResolverConfiguration {
+  @ConditionalOnWebApplication(type = Type.SERVLET)
+  public static class PebbleWebMvcConfiguration {
 
     @Autowired
     private PebbleProperties properties;
@@ -108,6 +110,33 @@ public class PebbleAutoConfiguration {
     }
   }
 
+  @Configuration
+  @ConditionalOnWebApplication(type = Type.REACTIVE)
+  public static class PebbleReactiveConfiguration {
+
+    @Autowired
+    private PebbleProperties properties;
+
+    @Autowired
+    private PebbleEngine pebbleEngine;
+
+    @Bean
+    @ConditionalOnMissingBean(name = "pebbleReactiveViewResolver")
+    public PebbleReactiveViewResolver pebbleReactiveViewResolver() {
+      String prefix = this.properties.getPrefix();
+      if (this.pebbleEngine.getLoader() instanceof ClasspathLoader) {
+        // classpathloader doesn't like leading slashes in paths
+        prefix = stripLeadingSlash(this.properties.getPrefix());
+      }
+      PebbleReactiveViewResolver resolver = new PebbleReactiveViewResolver(this.pebbleEngine);
+      resolver.setPrefix(prefix);
+      resolver.setSuffix(this.properties.getSuffix());
+      resolver.setViewNames(this.properties.getViewNames());
+      resolver.setRequestContextAttribute(this.properties.getRequestContextAttribute());
+      return resolver;
+    }
+  }
+
   private static String stripLeadingSlash(String value) {
     if (value == null) {
       return null;
diff --git a/pebble-spring/pebble-spring5/pom.xml b/pebble-spring/pebble-spring5/pom.xml
index b917777e..2633066c 100644
--- a/pebble-spring/pebble-spring5/pom.xml
+++ b/pebble-spring/pebble-spring5/pom.xml
@@ -46,6 +46,11 @@
       <artifactId>spring-webmvc</artifactId>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-webflux</artifactId>
+      <scope>provided</scope>
+    </dependency>
 
     <!-- TEST -->
     <dependency>
diff --git a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java
new file mode 100644
index 00000000..c0809e71
--- /dev/null
+++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveView.java
@@ -0,0 +1,89 @@
+package com.mitchellbosecke.pebble.spring.reactive;
+
+import com.mitchellbosecke.pebble.PebbleEngine;
+import com.mitchellbosecke.pebble.error.PebbleException;
+import com.mitchellbosecke.pebble.template.PebbleTemplate;
+
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.MediaType;
+import org.springframework.lang.Nullable;
+import org.springframework.util.MimeType;
+import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
+import org.springframework.web.server.ServerWebExchange;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Locale;
+import java.util.Map;
+
+import static java.util.Optional.ofNullable;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class PebbleReactiveView extends AbstractUrlBasedView {
+
+  private PebbleEngine pebbleEngine;
+  private String templateName;
+
+  @Override
+  public boolean checkResourceExists(Locale locale) {
+    return this.pebbleEngine.getLoader().resourceExists(this.templateName);
+  }
+
+  @Override
+  protected Mono<Void> renderInternal(Map<String, Object> renderAttributes,
+                                      MediaType contentType,
+                                      ServerWebExchange exchange) {
+    DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
+    if (this.logger.isDebugEnabled()) {
+      this.logger.debug(exchange.getLogPrefix() + "Rendering [" + this.getUrl() + "]");
+    }
+
+    Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
+    try {
+      Charset charset = this.getCharset(contentType);
+      Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset);
+      this.evaluateTemplate(renderAttributes, locale, writer);
+    } catch (Exception ex) {
+      DataBufferUtils.release(dataBuffer);
+      return Mono.error(ex);
+    }
+    return exchange.getResponse().writeWith(Flux.just(dataBuffer));
+  }
+
+  private Charset getCharset(@Nullable MediaType mediaType) {
+    return ofNullable(mediaType)
+        .map(MimeType::getCharset)
+        .orElse(this.getDefaultCharset());
+  }
+
+  private void evaluateTemplate(Map<String, Object> model, Locale locale, Writer writer)
+      throws IOException, PebbleException {
+    try {
+      PebbleTemplate template = this.pebbleEngine.getTemplate(this.templateName);
+      template.evaluate(writer, model, locale);
+    } finally {
+      writer.flush();
+    }
+  }
+
+  public PebbleEngine getPebbleEngine() {
+    return this.pebbleEngine;
+  }
+
+  public void setPebbleEngine(PebbleEngine pebbleEngine) {
+    this.pebbleEngine = pebbleEngine;
+  }
+
+  public String getTemplateName() {
+    return this.templateName;
+  }
+
+  public void setTemplateName(String templateName) {
+    this.templateName = templateName;
+  }
+}
diff --git a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java
new file mode 100644
index 00000000..138e7ae7
--- /dev/null
+++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/reactive/PebbleReactiveViewResolver.java
@@ -0,0 +1,34 @@
+package com.mitchellbosecke.pebble.spring.reactive;
+
+import com.mitchellbosecke.pebble.PebbleEngine;
+
+import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
+import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
+
+public class PebbleReactiveViewResolver extends UrlBasedViewResolver {
+
+  private final PebbleEngine pebbleEngine;
+
+  public PebbleReactiveViewResolver(PebbleEngine pebbleEngine) {
+    this.setViewClass(this.requiredViewClass());
+    this.pebbleEngine = pebbleEngine;
+  }
+
+  @Override
+  protected AbstractUrlBasedView createView(String viewName) {
+    PebbleReactiveView view = (PebbleReactiveView) super.createView(viewName);
+    view.setPebbleEngine(this.pebbleEngine);
+    view.setTemplateName(viewName);
+
+    return view;
+  }
+
+  @Override
+  protected Class<?> requiredViewClass() {
+    return PebbleReactiveView.class;
+  }
+
+  public PebbleEngine getPebbleEngine() {
+    return this.pebbleEngine;
+  }
+}
diff --git a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleView.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java
similarity index 96%
rename from pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleView.java
rename to pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java
index f9eb6431..34535f76 100644
--- a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleView.java
+++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleView.java
@@ -4,22 +4,25 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-package com.mitchellbosecke.pebble.spring;
+package com.mitchellbosecke.pebble.spring.servlet;
 
 import com.mitchellbosecke.pebble.PebbleEngine;
 import com.mitchellbosecke.pebble.error.PebbleException;
 import com.mitchellbosecke.pebble.spring.context.Beans;
 import com.mitchellbosecke.pebble.template.PebbleTemplate;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.servlet.support.RequestContextUtils;
+import org.springframework.web.servlet.view.AbstractTemplateView;
+
 import java.io.IOException;
 import java.io.Writer;
 import java.util.Locale;
 import java.util.Map;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.servlet.support.RequestContextUtils;
-import org.springframework.web.servlet.view.AbstractTemplateView;
 
 public class PebbleView extends AbstractTemplateView {
 
@@ -36,7 +39,7 @@ public class PebbleView extends AbstractTemplateView {
    * </p>
    * <p>
    * The value of this constant is
-   * <tt>com.mitchellbosecke.pebble.spring.PebbleView.timer</tt>. This allows
+   * <tt>com.mitchellbosecke.pebble.spring.servlet.PebbleView.timer</tt>. This allows
    * you to set a specific configuration and/or appenders for timing info at your logging system
    * configuration.
    * </p>
diff --git a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleViewResolver.java b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java
similarity index 96%
rename from pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleViewResolver.java
rename to pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java
index 96f3b7f1..0b7a66f4 100644
--- a/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/PebbleViewResolver.java
+++ b/pebble-spring/pebble-spring5/src/main/java/com/mitchellbosecke/pebble/spring/servlet/PebbleViewResolver.java
@@ -4,10 +4,11 @@
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-package com.mitchellbosecke.pebble.spring;
+package com.mitchellbosecke.pebble.spring.servlet;
 
 import com.mitchellbosecke.pebble.PebbleEngine;
 import com.mitchellbosecke.pebble.loader.Loader;
+
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Required;
 import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java
index 55021d90..91c36331 100644
--- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java
+++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ClasspathLoader.java
@@ -10,13 +10,15 @@ package com.mitchellbosecke.pebble.loader;
 
 import com.mitchellbosecke.pebble.error.LoaderException;
 import com.mitchellbosecke.pebble.utils.PathUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.UnsupportedEncodingException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Uses a classloader to find templates located on the classpath.
@@ -47,7 +49,26 @@ public class ClasspathLoader implements Loader<String> {
 
   @Override
   public Reader getReader(String templateName) {
+    String location = this.getLocation(templateName);
+
+    logger.debug("Looking for template in {}.", location);
+
+    // perform the lookup
+    InputStream is = this.rcl.getResourceAsStream(location);
 
+    if (is == null) {
+      throw new LoaderException(null, "Could not find template \"" + location + "\"");
+    }
+
+    try {
+      return new BufferedReader(new InputStreamReader(is, this.charset));
+    } catch (UnsupportedEncodingException e) {
+    }
+
+    return null;
+  }
+
+  private String getLocation(String templateName) {
     // append the prefix and make sure prefix ends with a separator character
     StringBuilder path = new StringBuilder(128);
     if (this.getPrefix() != null) {
@@ -64,22 +85,7 @@ public class ClasspathLoader implements Loader<String> {
     if (this.getSuffix() != null) {
       path.append(this.getSuffix());
     }
-    String location = path.toString();
-    logger.debug("Looking for template in {}.", location);
-
-    // perform the lookup
-    InputStream is = this.rcl.getResourceAsStream(location);
-
-    if (is == null) {
-      throw new LoaderException(null, "Could not find template \"" + location + "\"");
-    }
-
-    try {
-      return new BufferedReader(new InputStreamReader(is, this.charset));
-    } catch (UnsupportedEncodingException e) {
-    }
-
-    return null;
+    return path.toString();
   }
 
   public String getSuffix() {
@@ -118,4 +124,9 @@ public class ClasspathLoader implements Loader<String> {
   public String createCacheKey(String templateName) {
     return templateName;
   }
+
+  @Override
+  public boolean resourceExists(String templateName) {
+    return this.rcl.getResource(this.getLocation(templateName)) != null;
+  }
 }
diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java
index 9e225bbe..35385c15 100644
--- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java
+++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/DelegatingLoader.java
@@ -9,6 +9,7 @@
 package com.mitchellbosecke.pebble.loader;
 
 import com.mitchellbosecke.pebble.error.LoaderException;
+
 import java.io.Reader;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -89,7 +90,7 @@ public class DelegatingLoader implements Loader<DelegatingLoaderCacheKey> {
   @Override
   public void setSuffix(String suffix) {
     this.suffix = suffix;
-    for (Loader<?> loader: this.loaders) {
+    for (Loader<?> loader : this.loaders) {
       loader.setSuffix(suffix);
     }
   }
@@ -101,7 +102,7 @@ public class DelegatingLoader implements Loader<DelegatingLoaderCacheKey> {
   @Override
   public void setPrefix(String prefix) {
     this.prefix = prefix;
-    for (Loader<?> loader: this.loaders) {
+    for (Loader<?> loader : this.loaders) {
       loader.setPrefix(prefix);
     }
   }
@@ -113,7 +114,7 @@ public class DelegatingLoader implements Loader<DelegatingLoaderCacheKey> {
   @Override
   public void setCharset(String charset) {
     this.charset = charset;
-    for (Loader<?> loader: this.loaders) {
+    for (Loader<?> loader : this.loaders) {
       loader.setCharset(charset);
     }
   }
@@ -142,4 +143,14 @@ public class DelegatingLoader implements Loader<DelegatingLoaderCacheKey> {
 
     return new DelegatingLoaderCacheKey(keys, templateName);
   }
+
+  @Override
+  public boolean resourceExists(String templateName) {
+    for (Loader<?> loader : this.loaders) {
+      if (loader.resourceExists(templateName)) {
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java
index dd784f3c..187e2313 100644
--- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java
+++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/FileLoader.java
@@ -10,6 +10,10 @@ package com.mitchellbosecke.pebble.loader;
 
 import com.mitchellbosecke.pebble.error.LoaderException;
 import com.mitchellbosecke.pebble.utils.PathUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
@@ -18,8 +22,6 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.UnsupportedEncodingException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * This loader searches for a file located anywhere on the filesystem. It uses java.io.File to
@@ -39,7 +41,30 @@ public class FileLoader implements Loader<String> {
 
   @Override
   public Reader getReader(String templateName) {
+    // try to load File
+    InputStream is = null;
+    File file = this.getFile(templateName);
+    if (file.exists() && file.isFile()) {
+      try {
+        is = new FileInputStream(file);
+      } catch (FileNotFoundException e) {
+      }
+    }
+
+    if (is == null) {
+      throw new LoaderException(null,
+          "Could not find template \"" + templateName + "\"");
+    }
 
+    try {
+      return new BufferedReader(new InputStreamReader(is, this.charset));
+    } catch (UnsupportedEncodingException e) {
+    }
+
+    return null;
+  }
+
+  private File getFile(String templateName) {
     // add the prefix and ensure the prefix ends with a separator character
     StringBuilder path = new StringBuilder();
     if (this.getPrefix() != null) {
@@ -71,26 +96,7 @@ public class FileLoader implements Loader<String> {
     }
 
     // try to load File
-    InputStream is = null;
-    File file = new File(path.toString(), templateName);
-    if (file.exists() && file.isFile()) {
-      try {
-        is = new FileInputStream(file);
-      } catch (FileNotFoundException e) {
-      }
-    }
-
-    if (is == null) {
-      throw new LoaderException(null,
-          "Could not find template \"" + path.toString() + templateName + "\"");
-    }
-
-    try {
-      return new BufferedReader(new InputStreamReader(is, this.charset));
-    } catch (UnsupportedEncodingException e) {
-    }
-
-    return null;
+    return new File(path.toString(), templateName);
   }
 
   public String getSuffix() {
@@ -129,4 +135,9 @@ public class FileLoader implements Loader<String> {
   public String createCacheKey(String templateName) {
     return templateName;
   }
+
+  @Override
+  public boolean resourceExists(String templateName) {
+    return this.getFile(templateName).exists();
+  }
 }
diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java
index c8901ddd..cc7bf068 100644
--- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java
+++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/Loader.java
@@ -9,6 +9,7 @@
 package com.mitchellbosecke.pebble.loader;
 
 import com.mitchellbosecke.pebble.PebbleEngine;
+
 import java.io.Reader;
 
 /**
@@ -101,4 +102,5 @@ public interface Loader<T> {
    */
   T createCacheKey(String templateName);
 
+  boolean resourceExists(String templateName);
 }
\ No newline at end of file
diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java
index 13e3bb00..e6268fde 100644
--- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java
+++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/ServletLoader.java
@@ -2,14 +2,18 @@ package com.mitchellbosecke.pebble.loader;
 
 import com.mitchellbosecke.pebble.error.LoaderException;
 import com.mitchellbosecke.pebble.utils.PathUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+
 import javax.servlet.ServletContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Loader that uses a servlet context to find templates.
@@ -41,34 +45,18 @@ public class ServletLoader implements Loader<String> {
     Reader reader = null;
 
     InputStream is = null;
+    String location = this.getLocation(templateName);
 
-    // Add the prefix and make sure that it ends with a separator character
-    StringBuilder path = new StringBuilder(128);
-    if (getPrefix() != null) {
-
-      path.append(getPrefix());
-
-      // we do NOT use OS dependent separators here; getResourceAsStream
-      // explicitly requires forward slashes.
-      if (!getPrefix().endsWith(Character.toString(expectedSeparator))) {
-        path.append(expectedSeparator);
-      }
-    }
-    path.append(templateName);
-    if (getSuffix() != null) {
-      path.append(getSuffix());
-    }
-    String location = path.toString();
     logger.debug("Looking for template in {}.", location);
 
-    is = context.getResourceAsStream(location);
+    is = this.context.getResourceAsStream(location);
 
     if (is == null) {
       throw new LoaderException(null, "Could not find template \"" + location + "\"");
     }
 
     try {
-      isr = new InputStreamReader(is, charset);
+      isr = new InputStreamReader(is, this.charset);
       reader = new BufferedReader(isr);
     } catch (UnsupportedEncodingException e) {
     }
@@ -76,8 +64,28 @@ public class ServletLoader implements Loader<String> {
     return reader;
   }
 
+  private String getLocation(String templateName) {
+    // Add the prefix and make sure that it ends with a separator character
+    StringBuilder path = new StringBuilder(128);
+    if (this.getPrefix() != null) {
+
+      path.append(this.getPrefix());
+
+      // we do NOT use OS dependent separators here; getResourceAsStream
+      // explicitly requires forward slashes.
+      if (!this.getPrefix().endsWith(Character.toString(this.expectedSeparator))) {
+        path.append(this.expectedSeparator);
+      }
+    }
+    path.append(templateName);
+    if (this.getSuffix() != null) {
+      path.append(this.getSuffix());
+    }
+    return path.toString();
+  }
+
   public String getSuffix() {
-    return suffix;
+    return this.suffix;
   }
 
   @Override
@@ -86,7 +94,7 @@ public class ServletLoader implements Loader<String> {
   }
 
   public String getPrefix() {
-    return prefix;
+    return this.prefix;
   }
 
   @Override
@@ -95,7 +103,7 @@ public class ServletLoader implements Loader<String> {
   }
 
   public String getCharset() {
-    return charset;
+    return this.charset;
   }
 
   @Override
@@ -105,7 +113,7 @@ public class ServletLoader implements Loader<String> {
 
   @Override
   public String resolveRelativePath(String relativePath, String anchorPath) {
-    return PathUtils.resolveRelativePath(relativePath, anchorPath, expectedSeparator);
+    return PathUtils.resolveRelativePath(relativePath, anchorPath, this.expectedSeparator);
   }
 
   @Override
@@ -113,4 +121,12 @@ public class ServletLoader implements Loader<String> {
     return templateName;
   }
 
+  @Override
+  public boolean resourceExists(String templateName) {
+    try {
+      return this.context.getResource(this.getLocation(templateName)) != null;
+    } catch (MalformedURLException e) {
+      return false;
+    }
+  }
 }
diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java
index a49c221d..921fb8d3 100644
--- a/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java
+++ b/pebble/src/main/java/com/mitchellbosecke/pebble/loader/StringLoader.java
@@ -48,4 +48,8 @@ public class StringLoader implements Loader<String> {
     return templateName;
   }
 
+  @Override
+  public boolean resourceExists(String templateName) {
+    return true;
+  }
 }


Test patch:
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java
new file mode 100644
index 00000000..eca8f60f
--- /dev/null
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/AppConfig.java
@@ -0,0 +1,64 @@
+package com.mitchellbosecke.pebble.boot;
+
+import com.mitchellbosecke.pebble.extension.AbstractExtension;
+import com.mitchellbosecke.pebble.extension.Extension;
+import com.mitchellbosecke.pebble.extension.Function;
+import com.mitchellbosecke.pebble.template.EvaluationContext;
+import com.mitchellbosecke.pebble.template.PebbleTemplate;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ResourceBundleMessageSource;
+import org.springframework.web.servlet.LocaleResolver;
+import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Configuration(proxyBeanMethods = false)
+public class AppConfig {
+  @Bean
+  public MessageSource messageSource() {
+    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
+    messageSource.setBasename("messages");
+    messageSource.setFallbackToSystemLocale(false);
+    return messageSource;
+  }
+
+  @Bean
+  public LocaleResolver localeResolver() {
+    return new AcceptHeaderLocaleResolver();
+  }
+
+  @Bean
+  public Extension testExtension() {
+    return new TestExtension();
+  }
+
+  public static class TestExtension extends AbstractExtension {
+
+    @Override
+    public Map<String, Function> getFunctions() {
+      Map<String, Function> functions = new HashMap<String, Function>();
+      functions.put("testFunction", new TestFunction());
+      return functions;
+    }
+
+    public static class TestFunction implements Function {
+
+      @Override
+      public List<String> getArgumentNames() {
+        return Collections.emptyList();
+      }
+
+      @Override
+      public Object execute(Map<String, Object> args, PebbleTemplate self,
+                            EvaluationContext context, int lineNumber) {
+        return "Tested!";
+      }
+    }
+  }
+}
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java
index a02caf2c..5a8c365d 100644
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Application.java
@@ -1,62 +1,12 @@
 package com.mitchellbosecke.pebble.boot;
 
-import com.mitchellbosecke.pebble.extension.AbstractExtension;
-import com.mitchellbosecke.pebble.extension.Extension;
-import com.mitchellbosecke.pebble.extension.Function;
-import com.mitchellbosecke.pebble.template.EvaluationContext;
-import com.mitchellbosecke.pebble.template.PebbleTemplate;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.MessageSource;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.support.ResourceBundleMessageSource;
-import org.springframework.web.servlet.LocaleResolver;
-import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
 
 @SpringBootApplication
-@ServletComponentScan
 public class Application {
 
   public static void main(String[] args) {
     SpringApplication.run(Application.class, args);
   }
-
-  @Bean
-  public LocaleResolver localeResolver() {
-    return new AcceptHeaderLocaleResolver();
-  }
-
-  @Bean
-  public Extension testExtension() {
-    return new TestExtension();
-  }
-
-  public static class TestExtension extends AbstractExtension {
-
-    @Override
-    public Map<String, Function> getFunctions() {
-      Map<String, Function> functions = new HashMap<String, Function>();
-      functions.put("testFunction", new TestFunction());
-      return functions;
-    }
-
-    public static class TestFunction implements Function {
-
-      @Override
-      public List<String> getArgumentNames() {
-        return Collections.emptyList();
-      }
-
-      @Override
-      public Object execute(Map<String, Object> args, PebbleTemplate self,
-          EvaluationContext context, int lineNumber) {
-        return "Tested!";
-      }
-    }
-  }
-
 }
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java
index d99625b0..52eb0afa 100644
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/Controllers.java
@@ -1,7 +1,6 @@
 package com.mitchellbosecke.pebble.boot;
 
 import org.springframework.stereotype.Controller;
-import org.springframework.ui.ModelMap;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 @Controller
@@ -13,17 +12,17 @@ public class Controllers {
   }
 
   @RequestMapping("/index.action")
-  public String index(ModelMap model) {
+  public String index() {
     return "index";
   }
 
   @RequestMapping("/contextPath.action")
-  public String contextPath(ModelMap model) {
+  public String contextPath() {
     return "contextPath";
   }
 
   @RequestMapping("/extensions.action")
-  public String extensions(ModelMap model) {
+  public String extensions() {
     return "extensions";
   }
 
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java
index 6b4216d0..82268aea 100644
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/NonWebApplication.java
@@ -4,9 +4,6 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.WebApplicationType;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.builder.SpringApplicationBuilder;
-import org.springframework.context.MessageSource;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.support.ResourceBundleMessageSource;
 
 @SpringBootApplication
 public class NonWebApplication {
@@ -17,13 +14,4 @@ public class NonWebApplication {
         .build();
     sa.run(args);
   }
-
-  @Bean
-  public MessageSource messageSource() {
-    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
-    messageSource.setBasename("messages");
-    messageSource.setFallbackToSystemLocale(false);
-    return messageSource;
-  }
-
 }
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ReactiveApplication.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ReactiveApplication.java
new file mode 100644
index 00000000..b505904c
--- /dev/null
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ReactiveApplication.java
@@ -0,0 +1,16 @@
+package com.mitchellbosecke.pebble.boot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import static org.springframework.boot.WebApplicationType.REACTIVE;
+
+@SpringBootApplication
+public class ReactiveApplication {
+
+  public static void main(String[] args) {
+    SpringApplication application = new SpringApplication(Application.class);
+    application.setWebApplicationType(REACTIVE);
+    application.run(args);
+  }
+}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponenRegisteringPostProcessor.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponenRegisteringPostProcessor.java
deleted file mode 100644
index 3bf919dd..00000000
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponenRegisteringPostProcessor.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.mitchellbosecke.pebble.boot;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
-import org.springframework.context.annotation.ScannedGenericBeanDefinition;
-
-class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor,
-    ApplicationContextAware {
-
-  private static final List<ServletComponentHandler> HANDLERS;
-
-  static {
-    List<ServletComponentHandler> handlers = new ArrayList<ServletComponentHandler>();
-    handlers.add(new WebServletHandler());
-    handlers.add(new WebFilterHandler());
-    handlers.add(new WebListenerHandler());
-    HANDLERS = Collections.unmodifiableList(handlers);
-  }
-
-  private final Set<String> packagesToScan;
-
-  private ApplicationContext applicationContext;
-
-  ServletComponentRegisteringPostProcessor(Set<String> packagesToScan) {
-    this.packagesToScan = packagesToScan;
-  }
-
-  @Override
-  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
-      throws BeansException {
-    if (isRunningInEmbeddedContainer()) {
-      ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
-      for (String packageToScan : this.packagesToScan) {
-        scanPackage(componentProvider, packageToScan);
-      }
-    }
-  }
-
-  private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider,
-      String packageToScan) {
-    for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
-      if (candidate instanceof ScannedGenericBeanDefinition) {
-        for (ServletComponentHandler handler : HANDLERS) {
-          handler.handle(((ScannedGenericBeanDefinition) candidate),
-              (BeanDefinitionRegistry) this.applicationContext);
-        }
-      }
-    }
-  }
-
-  private boolean isRunningInEmbeddedContainer() {
-    return this.applicationContext instanceof ServletWebServerApplicationContext
-        && ((ServletWebServerApplicationContext) this.applicationContext).getServletContext()
-        == null;
-  }
-
-  private ClassPathScanningCandidateComponentProvider createComponentProvider() {
-    ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
-        false);
-    for (ServletComponentHandler handler : HANDLERS) {
-      componentProvider.addIncludeFilter(handler.getTypeFilter());
-    }
-    return componentProvider;
-  }
-
-  Set<String> getPackagesToScan() {
-    return Collections.unmodifiableSet(this.packagesToScan);
-  }
-
-  @Override
-  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-    this.applicationContext = applicationContext;
-  }
-
-}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentHandler.java
deleted file mode 100644
index c57882a9..00000000
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentHandler.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.mitchellbosecke.pebble.boot;
-
-import java.lang.annotation.Annotation;
-import java.util.HashMap;
-import java.util.Map;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.context.annotation.ScannedGenericBeanDefinition;
-import org.springframework.core.annotation.AnnotationAttributes;
-import org.springframework.core.type.filter.AnnotationTypeFilter;
-import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.util.Assert;
-
-abstract class ServletComponentHandler {
-
-  private final Class<? extends Annotation> annotationType;
-
-  private final TypeFilter typeFilter;
-
-  protected ServletComponentHandler(Class<? extends Annotation> annotationType) {
-    this.typeFilter = new AnnotationTypeFilter(annotationType);
-    this.annotationType = annotationType;
-  }
-
-  TypeFilter getTypeFilter() {
-    return this.typeFilter;
-  }
-
-  protected String[] extractUrlPatterns(String attribute, Map<String, Object> attributes) {
-    String[] value = (String[]) attributes.get("value");
-    String[] urlPatterns = (String[]) attributes.get("urlPatterns");
-    if (urlPatterns.length > 0) {
-      Assert
-          .state(value.length == 0, "The urlPatterns and value attributes are mutually exclusive.");
-      return urlPatterns;
-    }
-    return value;
-  }
-
-  protected final Map<String, String> extractInitParameters(Map<String, Object> attributes) {
-    Map<String, String> initParameters = new HashMap<String, String>();
-    for (AnnotationAttributes initParam : (AnnotationAttributes[]) attributes.get("initParams")) {
-      String name = (String) initParam.get("name");
-      String value = (String) initParam.get("value");
-      initParameters.put(name, value);
-    }
-    return initParameters;
-  }
-
-  void handle(ScannedGenericBeanDefinition beanDefinition, BeanDefinitionRegistry registry) {
-    Map<String, Object> attributes = beanDefinition.getMetadata()
-        .getAnnotationAttributes(this.annotationType.getName());
-    if (attributes != null) {
-      doHandle(attributes, beanDefinition, registry);
-    }
-  }
-
-  protected abstract void doHandle(Map<String, Object> attributes, BeanDefinition beanDefinition,
-      BeanDefinitionRegistry registry);
-
-}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScan.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScan.java
deleted file mode 100644
index ba3bb336..00000000
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScan.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.mitchellbosecke.pebble.boot;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import org.springframework.context.annotation.Import;
-
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-@Import(ServletComponentScanRegistrar.class)
-public @interface ServletComponentScan {
-
-  /**
-   * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
-   * declarations e.g.: {@code @ServletComponentScan("org.my.pkg")} instead of {@code
-   *
-   * @return the base packages to scan
-   * @ServletComponentScan(basePackages="org.my.pkg")}.
-   */
-  String[] value() default {};
-
-  /**
-   * Base packages to scan for annotated servlet components. {@link #value()} is an alias for (and
-   * mutually exclusive with) this attribute.
-   * <p>
-   * Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
-   *
-   * @return the base packages to scan
-   */
-  String[] basePackages() default {};
-
-  /**
-   * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for
-   * annotated servlet components. The package of each class specified will be scanned.
-   *
-   * @return classes from the base packages to scan
-   */
-  Class<?>[] basePackageClasses() default {};
-
-}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScanRegistrar.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScanRegistrar.java
deleted file mode 100644
index b5c1dade..00000000
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ServletComponentScanRegistrar.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.mitchellbosecke.pebble.boot;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.beans.factory.support.GenericBeanDefinition;
-import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
-import org.springframework.core.annotation.AnnotationAttributes;
-import org.springframework.core.type.AnnotationMetadata;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.ObjectUtils;
-
-class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
-
-  private static final String BEAN_NAME = "servletComponentRegisteringPostProcessor";
-
-  @Override
-  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
-      BeanDefinitionRegistry registry) {
-    Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
-    if (registry.containsBeanDefinition(BEAN_NAME)) {
-      updatePostProcessor(registry, packagesToScan);
-    } else {
-      addPostProcessor(registry, packagesToScan);
-    }
-  }
-
-  private void updatePostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
-    BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME);
-    ValueHolder constructorArguments = definition.getConstructorArgumentValues()
-        .getGenericArgumentValue(Set.class);
-    @SuppressWarnings("unchecked")
-    Set<String> mergedPackages = (Set<String>) constructorArguments.getValue();
-    mergedPackages.addAll(packagesToScan);
-    constructorArguments.setValue(mergedPackages);
-  }
-
-  private void addPostProcessor(BeanDefinitionRegistry registry, Set<String> packagesToScan) {
-    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
-    beanDefinition.setBeanClass(ServletComponentRegisteringPostProcessor.class);
-    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packagesToScan);
-    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
-    registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
-  }
-
-  private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
-    AnnotationAttributes attributes = AnnotationAttributes
-        .fromMap(metadata.getAnnotationAttributes(ServletComponentScan.class.getName()));
-    String[] value = attributes.getStringArray("value");
-    String[] basePackages = attributes.getStringArray("basePackages");
-    Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
-    if (!ObjectUtils.isEmpty(value)) {
-      Assert.state(ObjectUtils.isEmpty(basePackages),
-          "@ServletComponentScan basePackages and value attributes are" + " mutually exclusive");
-    }
-    Set<String> packagesToScan = new LinkedHashSet<String>();
-    packagesToScan.addAll(Arrays.asList(value));
-    packagesToScan.addAll(Arrays.asList(basePackages));
-    for (Class<?> basePackageClass : basePackageClasses) {
-      packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
-    }
-    if (packagesToScan.isEmpty()) {
-      return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
-    }
-    return packagesToScan;
-  }
-
-}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebFilterHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebFilterHandler.java
deleted file mode 100644
index 3d5bd9e9..00000000
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebFilterHandler.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.mitchellbosecke.pebble.boot;
-
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.Map;
-import javax.servlet.DispatcherType;
-import javax.servlet.annotation.WebFilter;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.util.StringUtils;
-
-class WebFilterHandler extends ServletComponentHandler {
-
-  WebFilterHandler() {
-    super(WebFilter.class);
-  }
-
-  @Override
-  public void doHandle(Map<String, Object> attributes, BeanDefinition beanDefinition,
-      BeanDefinitionRegistry registry) {
-    BeanDefinitionBuilder builder = BeanDefinitionBuilder
-        .rootBeanDefinition(FilterRegistrationBean.class);
-    builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
-    builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
-    builder.addPropertyValue("filter", beanDefinition);
-    builder.addPropertyValue("initParameters", extractInitParameters(attributes));
-    String name = determineName(attributes, beanDefinition);
-    builder.addPropertyValue("name", name);
-    builder.addPropertyValue("servletNames", attributes.get("servletNames"));
-    builder.addPropertyValue("urlPatterns", extractUrlPatterns("urlPatterns", attributes));
-    registry.registerBeanDefinition(name, builder.getBeanDefinition());
-  }
-
-  private EnumSet<DispatcherType> extractDispatcherTypes(Map<String, Object> attributes) {
-    DispatcherType[] dispatcherTypes = (DispatcherType[]) attributes.get("dispatcherTypes");
-    if (dispatcherTypes.length == 0) {
-      return EnumSet.noneOf(DispatcherType.class);
-    }
-    if (dispatcherTypes.length == 1) {
-      return EnumSet.of(dispatcherTypes[0]);
-    }
-    return EnumSet
-        .of(dispatcherTypes[0], Arrays.copyOfRange(dispatcherTypes, 1, dispatcherTypes.length));
-  }
-
-  private String determineName(Map<String, Object> attributes, BeanDefinition beanDefinition) {
-    return (String) (StringUtils.hasText((String) attributes.get("filterName")) ? attributes
-        .get("filterName")
-        : beanDefinition.getBeanClassName());
-  }
-
-}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebListenerHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebListenerHandler.java
deleted file mode 100644
index 4658858e..00000000
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebListenerHandler.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.mitchellbosecke.pebble.boot;
-
-import java.util.Map;
-import javax.servlet.annotation.WebListener;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
-
-class WebListenerHandler extends ServletComponentHandler {
-
-  WebListenerHandler() {
-    super(WebListener.class);
-  }
-
-  @Override
-  protected void doHandle(Map<String, Object> attributes, BeanDefinition beanDefinition,
-      BeanDefinitionRegistry registry) {
-    BeanDefinitionBuilder builder = BeanDefinitionBuilder
-        .rootBeanDefinition(ServletListenerRegistrationBean.class);
-    builder.addPropertyValue("listener", beanDefinition);
-    registry.registerBeanDefinition(beanDefinition.getBeanClassName(), builder.getBeanDefinition());
-  }
-
-}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebServletHandler.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebServletHandler.java
deleted file mode 100644
index 740c83bd..00000000
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/WebServletHandler.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.mitchellbosecke.pebble.boot;
-
-import java.util.Map;
-import javax.servlet.annotation.WebServlet;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
-import org.springframework.boot.web.servlet.ServletRegistrationBean;
-import org.springframework.util.StringUtils;
-
-class WebServletHandler extends ServletComponentHandler {
-
-  WebServletHandler() {
-    super(WebServlet.class);
-  }
-
-  @Override
-  public void doHandle(Map<String, Object> attributes, BeanDefinition beanDefinition,
-      BeanDefinitionRegistry registry) {
-    BeanDefinitionBuilder builder = BeanDefinitionBuilder
-        .rootBeanDefinition(ServletRegistrationBean.class);
-    builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
-    builder.addPropertyValue("initParameters", extractInitParameters(attributes));
-    builder.addPropertyValue("loadOnStartup", attributes.get("loadOnStartup"));
-    String name = determineName(attributes, beanDefinition);
-    builder.addPropertyValue("name", name);
-    builder.addPropertyValue("servlet", beanDefinition);
-    builder.addPropertyValue("urlMappings", extractUrlPatterns("urlPatterns", attributes));
-    registry.registerBeanDefinition(name, builder.getBeanDefinition());
-  }
-
-  private String determineName(Map<String, Object> attributes, BeanDefinition beanDefinition) {
-    return (String) (StringUtils.hasText((String) attributes.get("name")) ? attributes.get("name")
-        : beanDefinition.getBeanClassName());
-  }
-
-}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/CoreTests.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java
similarity index 65%
rename from pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/CoreTests.java
rename to pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java
index 9ea36f50..62b124a2 100644
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/CoreTests.java
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/NonWebAppTests.java
@@ -1,17 +1,20 @@
-package com.mitchellbosecke.pebble.boot;
+package com.mitchellbosecke.pebble.boot.autoconfigure;
 
 import com.mitchellbosecke.pebble.PebbleEngine;
-import java.io.StringWriter;
+import com.mitchellbosecke.pebble.boot.NonWebApplication;
+
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.StringWriter;
 
-@RunWith(SpringJUnit4ClassRunner.class)
+@RunWith(SpringRunner.class)
 @SpringBootTest(classes = NonWebApplication.class)
-public class CoreTests {
+public class NonWebAppTests {
 
   @Autowired
   private PebbleEngine pebbleEngine;
@@ -19,7 +22,7 @@ public class CoreTests {
   @Test
   public void testOk() throws Exception {
     StringWriter sw = new StringWriter();
-    pebbleEngine.getTemplate("hello").evaluate(sw);
+    this.pebbleEngine.getTemplate("hello").evaluate(sw);
     Assert.assertTrue(sw.toString() != null && !sw.toString().isEmpty());
   }
 
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java
new file mode 100644
index 00000000..ab7d32ad
--- /dev/null
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/PebbleAutoConfigurationTest.java
@@ -0,0 +1,107 @@
+package com.mitchellbosecke.pebble.boot.autoconfigure;
+
+import com.mitchellbosecke.pebble.PebbleEngine;
+import com.mitchellbosecke.pebble.loader.Loader;
+import com.mitchellbosecke.pebble.spring.extension.SpringExtension;
+import com.mitchellbosecke.pebble.spring.reactive.PebbleReactiveViewResolver;
+import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver;
+
+import org.junit.Test;
+import org.springframework.boot.test.util.TestPropertyValues;
+import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
+import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+import java.util.Locale;
+
+import static java.util.Locale.CHINESE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PebbleAutoConfigurationTest {
+
+  private static final Locale DEFAULT_LOCALE = CHINESE;
+  private AnnotationConfigServletWebApplicationContext webContext;
+
+  private AnnotationConfigReactiveWebApplicationContext reactiveWebContext;
+
+  @Test
+  public void registerBeansForServletApp() {
+    this.loadWithServlet(null);
+    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);
+    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).hasSize(1);
+    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);
+    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);
+  }
+
+  @Test
+  public void registerCompilerForServletApp() {
+    this.loadWithServlet(CustomCompilerConfiguration.class);
+    assertThat(this.webContext.getBeansOfType(Loader.class)).hasSize(1);
+    assertThat(this.webContext.getBeansOfType(SpringExtension.class)).isEmpty();
+    assertThat(this.webContext.getBeansOfType(PebbleEngine.class)).hasSize(1);
+    assertThat(this.webContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE);
+    assertThat(this.webContext.getBeansOfType(PebbleViewResolver.class)).hasSize(1);
+    assertThat(this.webContext.getBeansOfType(PebbleReactiveViewResolver.class)).isEmpty();
+  }
+
+  @Test
+  public void registerBeansForReactiveApp() {
+    this.loadWithReactive(null);
+    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);
+    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).hasSize(1);
+    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);
+    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();
+    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);
+    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();
+  }
+
+  @Test
+  public void registerCompilerForReactiveApp() {
+    this.loadWithReactive(CustomCompilerConfiguration.class);
+    assertThat(this.reactiveWebContext.getBeansOfType(Loader.class)).hasSize(1);
+    assertThat(this.reactiveWebContext.getBeansOfType(SpringExtension.class)).isEmpty();
+    assertThat(this.reactiveWebContext.getBeansOfType(PebbleEngine.class)).hasSize(1);
+    assertThat(this.reactiveWebContext.getBean(PebbleEngine.class).getDefaultLocale()).isEqualTo(DEFAULT_LOCALE);
+    assertThat(this.reactiveWebContext.getBeansOfType(PebbleReactiveViewResolver.class)).hasSize(1);
+    assertThat(this.reactiveWebContext.getBeansOfType(PebbleViewResolver.class)).isEmpty();
+  }
+
+  private void loadWithServlet(Class<?> config) {
+    this.webContext = new AnnotationConfigServletWebApplicationContext();
+    TestPropertyValues.of("pebble.prefix=classpath:/templates/").applyTo(this.webContext);
+    if (config != null) {
+      this.webContext.register(config);
+    }
+    this.webContext.register(BaseConfiguration.class);
+    this.webContext.refresh();
+  }
+
+  private void loadWithReactive(Class<?> config) {
+    this.reactiveWebContext = new AnnotationConfigReactiveWebApplicationContext();
+    TestPropertyValues.of("pebble.prefix=classpath:/templates/").applyTo(this.reactiveWebContext);
+    if (config != null) {
+      this.reactiveWebContext.register(config);
+    }
+    this.reactiveWebContext.register(BaseConfiguration.class);
+    this.reactiveWebContext.refresh();
+  }
+
+  @Configuration(proxyBeanMethods = false)
+  @Import(PebbleAutoConfiguration.class)
+  protected static class BaseConfiguration {
+
+  }
+
+  @Configuration(proxyBeanMethods = false)
+  protected static class CustomCompilerConfiguration {
+
+    @Bean
+    public PebbleEngine pebbleEngine() {
+      return new PebbleEngine.Builder().defaultLocale(DEFAULT_LOCALE).build();
+    }
+
+  }
+
+}
\ No newline at end of file
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java
new file mode 100644
index 00000000..419c7fb5
--- /dev/null
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ReactiveAppTest.java
@@ -0,0 +1,82 @@
+package com.mitchellbosecke.pebble.boot.autoconfigure;
+
+import com.mitchellbosecke.pebble.boot.ReactiveApplication;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = ReactiveApplication.class,
+    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+    properties = "spring.main.web-application-type=reactive")
+public class ReactiveAppTest {
+
+  @Autowired
+  private WebTestClient client;
+
+  @Test
+  public void testOk() throws Exception {
+    String result = this.client.get().uri("/index.action").exchange()
+        .expectStatus().isOk()
+        .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
+        .expectBody(String.class)
+        .returnResult().getResponseBody();
+
+    assertThat(result).isEqualTo("Hello Pebbleworld!");
+  }
+
+  @Test
+  public void testRequestAccess() throws Exception {
+    String result = this.client.get().uri("/contextPath.action").exchange()
+        .expectStatus().isOk()
+        .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
+        .expectBody(String.class)
+        .returnResult().getResponseBody();
+
+    assertThat(result).isEqualTo("ctx path:");
+  }
+
+  @Test
+  public void testEnglishHello() throws Exception {
+    String result = this.client.get().uri("/hello.action")
+        .header("Accept-Language", "en").exchange()
+        .expectStatus().isOk()
+        .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
+        .expectBody(String.class)
+        .returnResult().getResponseBody();
+
+    assertThat(result).isEqualTo("Hello Boot!");
+  }
+
+  @Test
+  public void testSpanishHello() throws Exception {
+    String result = this.client.get().uri("/hello.action")
+        .header("Accept-Language", "es").exchange()
+        .expectStatus().isOk()
+        .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
+        .expectBody(String.class)
+        .returnResult().getResponseBody();
+
+    assertThat(result).isEqualTo("Hola Boot!");
+  }
+
+  @Test
+  public void testAdditionalExtensions() throws Exception {
+    String result = this.client.get().uri("/extensions.action")
+        .header("Accept-Language", "es").exchange()
+        .expectStatus().isOk()
+        .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
+        .expectBody(String.class)
+        .returnResult().getResponseBody();
+
+    assertThat(result).isEqualTo("Hola Boot! Tested!");
+  }
+}
+
diff --git a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ControllerTest.java b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java
similarity index 75%
rename from pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ControllerTest.java
rename to pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java
index 672b3782..7ac51601 100644
--- a/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/ControllerTest.java
+++ b/pebble-spring/pebble-spring-boot-starter/src/test/java/com/mitchellbosecke/pebble/boot/autoconfigure/ServletAppTest.java
@@ -1,27 +1,30 @@
-package com.mitchellbosecke.pebble.boot;
+package com.mitchellbosecke.pebble.boot.autoconfigure;
 
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import com.mitchellbosecke.pebble.boot.Application;
 
-import java.util.Locale;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.http.MediaType;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.SpringRunner;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
 
-@RunWith(SpringJUnit4ClassRunner.class)
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
 @SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
-public class ControllerTest {
+public class ServletAppTest {
 
   @Autowired
   private WebApplicationContext wac;
@@ -35,22 +38,22 @@ public class ControllerTest {
 
   @Test
   public void testOk() throws Exception {
-    mockMvc.perform(get("/index.action")).andExpect(status().isOk())
+    this.mockMvc.perform(get("/index.action")).andExpect(status().isOk())
         .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
         .andExpect(content().string("Hello Pebbleworld!"));
   }
 
   @Test
   public void testRequestAccess() throws Exception {
-    MvcResult result = mockMvc.perform(get("/contextPath.action")).andExpect(status().isOk())
+    MvcResult result = this.mockMvc.perform(get("/contextPath.action")).andExpect(status().isOk())
         .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)).andReturn();
-    Assert.assertEquals("ctx path:" + result.getRequest().getContextPath(),
+    assertEquals("ctx path:" + result.getRequest().getContextPath(),
         result.getResponse().getContentAsString());
   }
 
   @Test
   public void testEnglishHello() throws Exception {
-    mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("en")))
+    this.mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("en")))
         .andExpect(status().isOk())
         .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
         .andExpect(content().string("Hello Boot!"));
@@ -58,7 +61,7 @@ public class ControllerTest {
 
   @Test
   public void testSpanishHello() throws Exception {
-    mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("es")))
+    this.mockMvc.perform(get("/hello.action").locale(Locale.forLanguageTag("es")))
         .andExpect(status().isOk())
         .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
         .andExpect(content().string("Hola Boot!"));
@@ -66,7 +69,7 @@ public class ControllerTest {
 
   @Test
   public void testAdditionalExtensions() throws Exception {
-    mockMvc.perform(get("/extensions.action").locale(Locale.forLanguageTag("es")))
+    this.mockMvc.perform(get("/extensions.action").locale(Locale.forLanguageTag("es")))
         .andExpect(status().isOk())
         .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
         .andExpect(content().string("Hola Boot! Tested!"));
diff --git a/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java b/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java
index e6d12f31..7d0615e6 100644
--- a/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java
+++ b/pebble-spring/pebble-spring5/src/test/java/com/mitchellbosecke/pebble/spring/config/MVCConfig.java
@@ -9,9 +9,10 @@ package com.mitchellbosecke.pebble.spring.config;
 import com.mitchellbosecke.pebble.PebbleEngine;
 import com.mitchellbosecke.pebble.loader.ClasspathLoader;
 import com.mitchellbosecke.pebble.loader.Loader;
-import com.mitchellbosecke.pebble.spring.PebbleViewResolver;
 import com.mitchellbosecke.pebble.spring.bean.SomeBean;
 import com.mitchellbosecke.pebble.spring.extension.SpringExtension;
+import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver;
+
 import org.springframework.context.MessageSource;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
diff --git a/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java b/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java
index 5f597ac0..f41c5f9b 100644
--- a/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java
+++ b/pebble/src/test/java/com/mitchellbosecke/pebble/ConcurrencyTest.java
@@ -8,14 +8,14 @@
  */
 package com.mitchellbosecke.pebble;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
 import com.mitchellbosecke.pebble.error.LoaderException;
 import com.mitchellbosecke.pebble.error.PebbleException;
 import com.mitchellbosecke.pebble.loader.Loader;
 import com.mitchellbosecke.pebble.loader.StringLoader;
 import com.mitchellbosecke.pebble.template.PebbleTemplate;
+
+import org.junit.Test;
+
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
@@ -29,7 +29,9 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicInteger;
-import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 public class ConcurrencyTest {
 
@@ -324,6 +326,10 @@ public class ConcurrencyTest {
         return templateName;
       }
 
+      @Override
+      public boolean resourceExists(String templateName) {
+        return true;
+      }
     };
 
     PebbleEngine engine = new PebbleEngine.Builder().loader(loader).strictVariables(false).build();