1. Overview
In this article, we’ll go through the new @ServletComponentScan annotation in Spring Boot.
The aim is to support the following Servlet 3.0 annotations:
- javax.servlet.annotation.WebFilter
- javax.servlet.annotation.WebListener
- javax.servlet.annotation.WebServlet
@WebServlet, @WebFilter, and @WebListener annotated classes can be automatically registered with an embedded Servlet container by annotating @ServletComponentScan on a @Configuration class and specifying the packages.
We have introduced the basic usage of @WebServlet in Introduction to Java Servlets and @WebFilter in Introduction to Intercepting Filter Pattern in Java. For @WebListener, you can take a peek at this article which demonstrates a typical use case of web listeners.
2. Servlets, Filters, and Listeners
Before diving into @ServletComponentScan, let’s take a look at how the annotations: @WebServlet, @WebFilter and @WebListener were used before @ServletComponentScan came into play.
2.1. @WebServlet
Now we’ll first define a Servlet that serves GET requests and responds “hello”:
@WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) { try { response .getOutputStream() .write("hello"); } catch (IOException e) { e.printStackTrace(); } } }
2.2. @WebFilter
Then a filter that filters requests to target “/hello”, and prepends “filtering “ to the output:
@WebFilter("/hello") public class HelloFilter implements Filter { //... @Override public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { servletResponse .getOutputStream() .print("filtering "); filterChain.doFilter(servletRequest, servletResponse); } //... }
2.3. @WebListener
Finally, a listener that sets a custom attribute in ServletContext:
@WebListener public class AttrListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { servletContextEvent .getServletContext() .setAttribute("servlet-context-attr", "test"); } //... }
2.4. Deploy to a Servlet Container
Now that we’ve built the basic components of a simple web application, we can package and deploy it into a Servlet container. Each component’s behavior can be readily verified by deploying the packaged war file into Jetty, Tomcat or any Servlet containers that support Servlet 3.0.
3. Using @ServletComponentScan in Spring Boot
You might wonder since we can use those annotations in most Servlet containers without any configuration, why do we need @ServletComponentScan? The problem lies in embedded Servlet containers.
Due to the fact that embedded containers do not support @WebServlet, @WebFilter and @WebListener annotations, Spring Boot, relying greatly on embedded containers, introduced this new annotation @ServletComponentScan to support some dependent jars that use these 3 annotations.
The detailed discussion can be found in this issue on Github.
3.1. Maven Dependencies
To use @ServletComponentScan, we need Spring Boot with version 1.3.0 or above. Let’s add the latest version of spring-boot-starter-parent and spring-boot-starter-web to the pom:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.1.RELEASE</version> </dependency> </dependencies>
3.2. Using @ServletComponentScan
The Spring Boot app is pretty simple. We add @ServletComponentScan to enable scanning for @WebFilter, @WebListener and @WebServlet:
@ServletComponentScan @SpringBootApplication public class SpringBootAnnotatedApp { public static void main(String[] args) { SpringApplication.run(SpringBootAnnotatedApp.class, args); } }
Without any change to the previous web application, it just works:
@Autowired private TestRestTemplate restTemplate; @Test public void givenServletFilter_whenGetHello_thenRequestFiltered() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("/hello", String.class); assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); assertEquals("filtering hello", responseEntity.getBody()); }
@Autowired private ServletContext servletContext; @Test public void givenServletContext_whenAccessAttrs_thenFoundAttrsPutInServletListner() { assertNotNull(servletContext); assertNotNull(servletContext.getAttribute("servlet-context-attr")); assertEquals("test", servletContext.getAttribute("servlet-context-attr")); }
3.3. Specify Packages to Scan
By default, @ServletComponentScan will scan from the package of the annotated class. To specify which packages to scan, we can use its attributes:
- value
- basePackages
- basePackageClasses
The default value attribute is an alias for basePackages.
Say our SpringBootAnnotatedApp is under package com.baeldung.annotation, and we want to scan classes in package com.baeldung.annotation.components created in the web application above, the following configurations are equivalent:
@ServletComponentScan
@ServletComponentScan("com.baeldung.annotation.components")
@ServletComponentScan(basePackages = "com.baeldung.annotation.components")
@ServletComponentScan( basePackageClasses = {AttrListener.class, HelloFilter.class, HelloServlet.class})
4. Under the Hood
The @ServletComponentScan annotation is processed by ServletComponentRegisteringPostProcessor. After scanning specified packages for @WebFilter, @WebListener and @WebServlet annotations, a list of ServletComponentHandlers will process their annotation attributes, and register scanned beans:
class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware { private static final List<ServletComponentHandler> HANDLERS; static { List<ServletComponentHandler> handlers = new ArrayList<>(); handlers.add(new WebServletHandler()); handlers.add(new WebFilterHandler()); handlers.add(new WebListenerHandler()); HANDLERS = Collections.unmodifiableList(handlers); } //... private void scanPackage( ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan){ //... for (ServletComponentHandler handler : HANDLERS) { handler.handle(((ScannedGenericBeanDefinition) candidate), (BeanDefinitionRegistry) this.applicationContext); } } }
As said in the official Javadoc, @ServletComponentScan annotation only works in embedded Servlet containers, which is what comes with Spring Boot by default.
5. Conclusion
In this article, we introduced @ServletComponentScan and how it can be used to support applications that depends on any of the annotations: @WebServlet, @WebFilter, @WebListener.
The implementation of the examples and code can be found in the GitHub project.