«1. Обзор
В этой статье мы рассмотрим новую аннотацию @ServletComponentScan в Spring Boot.
Целью является поддержка следующих аннотаций Servlet 3.0:
-
javax.servlet.annotation.WebFilter javax.servlet.annotation.WebListener javax.servlet.annotation.WebServlet
@WebServlet, @WebFilter и @WebListener аннотированные классы могут быть автоматически зарегистрированы во встроенном контейнере сервлетов путем аннотирования @ServletComponentScan в классе @Configuration и указания пакетов.
Мы представили основы использования @WebServlet в разделе Введение в сервлеты Java и @WebFilter в разделе Введение в шаблон перехвата фильтра в Java. Для @WebListener вы можете взглянуть на эту статью, которая демонстрирует типичный пример использования веб-слушателей.
2. Сервлеты, фильтры и слушатели
Перед тем, как углубиться в @ServletComponentScan, давайте посмотрим, как аннотации: @WebServlet, @WebFilter и @WebListener использовались до того, как @ServletComponentScan вступил в игру.
2.1. @WebServlet
Теперь мы сначала определим сервлет, который обслуживает запросы GET и отвечает «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
Затем фильтр, который отфильтровывает запросы к цели «/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. Развертывание в контейнере сервлетов
Теперь, когда мы создали основные компоненты простого веб-приложения, мы можем упаковать и развернуть его в контейнере сервлетов. Поведение каждого компонента можно легко проверить, развернув упакованный военный файл в Jetty, Tomcat или любые контейнеры сервлетов, поддерживающие Servlet 3.0.
3. Использование @ServletComponentScan в Spring Boot
Вы можете задаться вопросом, поскольку мы можем использовать эти аннотации в большинстве контейнеров сервлетов без какой-либо настройки, зачем нам нужен @ServletComponentScan? Проблема заключается во встроенных контейнерах сервлетов.
Из-за того, что встроенные контейнеры не поддерживают аннотации @WebServlet, @WebFilter и @WebListener, Spring Boot, в значительной степени полагающийся на встроенные контейнеры, представил эту новую аннотацию @ServletComponentScan для поддержки некоторых зависимых jar-файлов, которые используют эти 3 аннотации.
Подробное обсуждение можно найти в этом выпуске на Github.
3.1. Зависимости Maven
Чтобы использовать @ServletComponentScan, нам нужен Spring Boot версии 1.3.0 или выше. Добавим в pom последнюю версию 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
@ServletComponentScan
@SpringBootApplication
public class SpringBootAnnotatedApp {
public static void main(String[] args) {
SpringApplication.run(SpringBootAnnotatedApp.class, args);
}
}
Приложение Spring Boot довольно простое. Мы добавляем @ServletComponentScan, чтобы включить сканирование @WebFilter, @WebListener и @WebServlet:
@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
@ServletComponentScan
@ServletComponentScan("com.baeldung.annotation.components")
@ServletComponentScan(basePackages = "com.baeldung.annotation.components")
@ServletComponentScan(
basePackageClasses =
{AttrListener.class, HelloFilter.class, HelloServlet.class})
Атрибут value по умолчанию является псевдонимом для basePackages.
Предположим, что наше SpringBootAnnotatedApp находится в пакете com.baeldung.annotation, и мы хотим сканировать классы в пакете com.baeldung.annotation.components, созданные в веб-приложении выше, следующие конфигурации эквивалентны:
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);
}
}
}
~ ~~
4. Под капотом
Аннотация @ServletComponentScan обрабатывается ServletComponentRegisteringPostProcessor. После сканирования указанных пакетов на наличие аннотаций @WebFilter, @WebListener и @WebServlet список ServletComponentHandler обработает их атрибуты аннотаций и зарегистрирует отсканированные компоненты:
Как сказано в официальном документе Javadoc, аннотация @ServletComponentScan работает только во встроенных Контейнеры сервлетов, которые по умолчанию поставляются с Spring Boot.