1. 概述
本文将探讨Spring Boot中的新注解@ServletComponentScan
。其目标是支持以下Servlet 3.0
注解:
-
jakarta.servlet.annotation.WebFilter
-
jakarta.servlet.annotation.WebListener
-
jakarta.servlet.annotation.WebServlet
我们已经在Java Servlets入门中介绍了@WebServlet
的基本用法,在Java拦截器模式入门中讨论了@WebFilter
。对于@WebListener
,可以参考这篇文章,它展示了一个典型的web监听器用例。
2. Servlet、过滤器和监听器
在深入讨论@ServletComponentScan
之前,先来看看在它出现之前,如何使用这些注解:@WebServlet
、@WebFilter
和@WebListener
。
2.1. @WebServlet
首先,定义一个处理GET请求并返回"hello"的Servlet:
@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
然后,创建一个过滤器,针对"/hello"请求,并在输出前添加"filtering ":
@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
最后,定义一个监听器,在ServletContext
中设置自定义属性:
@WebListener
public class AttrListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
servletContextEvent
.getServletContext()
.setAttribute("servlet-context-attr", "test");
}
//...
}
2.4. 部署到Servlet容器
现在我们构建了一个简单web应用的基础组件,可以将打包后的war文件部署到诸如Jetty、Tomcat等支持Servlet 3.0
的Servlet容器中,以验证每个组件的行为。
3. 在Spring Boot中使用@ServletComponentScan
你可能会问,既然大多数Servlet容器无需配置即可使用这些注解,为什么还需要@ServletComponentScan
?问题出在嵌入式Servlet容器上。
由于嵌入式容器不支持@WebServlet
、@WebFilter
和@WebListener
注解,依赖于嵌入式容器的Spring Boot引入了这个新的@ServletComponentScan
注解,以支持使用这三种注解的一些依赖库。
详细讨论可在GitHub上的相关issue中找到。
3.1. Maven依赖
要使用@ServletComponentScan
,需要Spring Boot 1.3.0或更高版本。在pom.xml中添加最新的spring-boot-starter-parent
和spring-boot-starter-web
:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
3.2. 使用@ServletComponentScan
Spring Boot应用程序非常简单。只需在类上添加@ServletComponentScan
,即可启用对@WebFilter
、@WebListener
和@WebServlet
的扫描:
@ServletComponentScan
@SpringBootApplication
public class SpringBootAnnotatedApp {
public static void main(String[] args) {
SpringApplication.run(SpringBootAnnotatedApp.class, args);
}
}
无需修改之前的web应用,它就能正常工作:
@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. 指定扫描包
默认情况下,@ServletComponentScan
会从注解类所在的包开始扫描。要指定扫描哪些包,可以使用它的属性:
-
value
-
basePackages
-
basePackageClasses
value
属性是basePackages
的别名。
假设我们的SpringBootAnnotatedApp
位于com.baeldung.annotation
包下,而我们希望扫描上述web应用中创建在com.baeldung.annotation.components
包下的类,以下配置等效:
@ServletComponentScan
@ServletComponentScan("com.baeldung.annotation.components")
@ServletComponentScan(basePackages = "com.baeldung.annotation.components")
@ServletComponentScan(
basePackageClasses =
{AttrListener.class, HelloFilter.class, HelloServlet.class})
4. 内部实现
@ServletComponentScan
注解由ServletComponentRegisteringPostProcessor
(GitHub代码)处理。扫描指定包中的@WebFilter
、@WebListener
和@WebServlet
注解后,会生成一个ServletComponentHandler
列表,处理注解属性并将扫描到的bean注册:
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);
}
}
}
正如官方Javadoc所说,@ServletComponentScan
注解仅在嵌入式Servlet容器中有效,这是Spring Boot默认提供的。
5. 总结
本文介绍了@ServletComponentScan
及其如何用于支持依赖于@WebServlet
、@WebFilter
和@WebListener
的任何应用。
示例的实现和代码可以在GitHub项目中找到。