Перейти к основному содержимому

Аннотация @ServletComponentScan в Spring Boot

· 4 мин. чтения

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 и отвечает «привет» :

@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. @Веб-фильтр

Затем фильтр, который отфильтровывает запросы к цели «/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 или выше. Давайте добавим последнюю версию spring-boot-starter-parent и spring-boot-starter-web в pom :

<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);
}

}

Без каких-либо изменений в предыдущем веб-приложении оно просто работает:

@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 будет сканировать из пакета аннотированного класса. Чтобы указать, какие пакеты сканировать, мы можем использовать его атрибуты:

  • ценность
  • базовые пакеты
  • базовые классы пакетов

Атрибут значения по умолчанию — это псевдоним для basePackages .

Допустим, наше SpringBootAnnotatedApp находится в пакете com.foreach.annotation , и мы хотим сканировать классы в пакете com.foreach.annotation.components , созданные в веб-приложении выше, следующие конфигурации эквивалентны:

@ServletComponentScan
@ServletComponentScan("com.foreach.annotation.components")
@ServletComponentScan(basePackages = "com.foreach.annotation.components")
@ServletComponentScan(
basePackageClasses =
{AttrListener.class, HelloFilter.class, HelloServlet.class})

4. Под капотом

Аннотация @ServletComponentScan обрабатывается ServletComponentRegisteringPostProcessor . После сканирования указанных пакетов на наличие аннотаций @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 работает только во встроенных контейнерах сервлетов , которые по умолчанию поставляются с Spring Boot .

5. Вывод

В этой статье мы представили @ServletComponentScan и то, как его можно использовать для поддержки приложений, зависящих от любой из аннотаций: @WebServlet , @WebFilter , @WebListener .

Реализацию примеров и код можно найти в проекте GitHub .