«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.