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 .