A anotação @ServletComponentScan na inicialização do Spring

A anotação @ServletComponentScan na inicialização do Spring

1. Visão geral

Neste artigo, examinaremos a nova anotação@ServletComponentScan emSpring Boot.

O objetivo é oferecer suporte às seguintes anotações deServlet 3.0:

  • javax.servlet.annotation.WebFilter

  • javax.servlet.annotation.WebListener

  • javax.servlet.annotation.WebServlet

As classes anotadas@WebServlet,@WebFilter e@WebListener podem ser registradas automaticamente com um contêinerServlet incorporado anotando@ServletComponentScan em uma classe@Configuration especificando os pacotes.

Introduzimos o uso básico de@WebServlet emIntroduction to Java Servletse@WebFilter emIntroduction to Intercepting Filter Pattern in Java. Para@WebListener, você pode dar uma olhada emthis article, que demonstra um caso de uso típico de ouvintes da web.

2. Servlets,Filters eListeners

Antes de mergulhar em@ServletComponentScan, vamos dar uma olhada em como as anotações:@WebServlet,@WebFiltere@WebListener foram usadas antes de@ServletComponentScan entrar em jogo.

2.1. @WebServlet

Agora vamos primeiro definir umServlet que atende às solicitações deGET e responde“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

Em seguida, um filtro que filtra as solicitações para“/hello” alvo e adiciona“filtering “ à saída:

@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

Por fim, um ouvinte que define um atributo personalizado emServletContext:

@WebListener
public class AttrListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        servletContextEvent
          .getServletContext()
          .setAttribute("servlet-context-attr", "test");
    }
    //...
}

2.4. Implantar em um contêinerServlet

Agora que construímos os componentes básicos de um aplicativo da web simples, podemos empacotá-lo e implantá-lo em um contêinerServlet. O comportamento de cada componente pode ser verificado prontamente implantando o arquivo war empacotado emJetty,Tomcat ou quaisquer contêineresServlet que suportamServlet 3.0.

3. Usando@ServletComponentScan emSpring Boot

Você pode se perguntar, já que podemos usar essas anotações na maioria dos contêineresServlet sem nenhuma configuração, por que precisamos de@ServletComponentScan? O problema está nos contêineresServlet incorporados.

Devido ao fato de que os contêineres incorporados não suportam@WebServlet,@WebFiltere@WebListener anotações,Spring Boot, depende muito de contêineres incorporados, introduziu esta nova anotação@ServletComponentScan para suporta alguns jars dependentes que usam essas 3 anotações.

A discussão detalhada pode ser encontrada emthis issue on Github.

3.1. Dependências do Maven

Para usar@ServletComponentScan, precisamos deSpring Boot com a versão 1.3.0 ou superior. Vamos adicionar a versão mais recente despring-boot-starter-parent espring-boot-starter-web aopom:


    org.springframework.boot
    spring-boot-starter-parent
    1.5.1.RELEASE
     

    
        org.springframework.boot
        spring-boot-starter-web
        1.5.1.RELEASE
    

3.2. Usando@ServletComponentScan

O aplicativoSpring Boot é bastante simples. Adicionamos@ServletComponentScan para permitir a verificação de@WebFilter,@WebListener e@WebServlet:

@ServletComponentScan
@SpringBootApplication
public class SpringBootAnnotatedApp {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootAnnotatedApp.class, args);
    }

}

Sem nenhuma alteração no aplicativo Web anterior, ele simplesmente funciona:

@Autowired private TestRestTemplate restTemplate;

@Test
public void givenServletFilter_whenGetHello_thenRequestFiltered() {

    ResponseEntity 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. Especificar pacotes a serem verificados

Por padrão,@ServletComponentScan fará a varredura do pacote da classe anotada. Para especificar quais pacotes verificar, podemos usar seus atributos:

  • valor

  • basePackages

  • basePackageClasses

O atributovalue padrão é um alias parabasePackages.

Digamos que nossoSpringBootAnnotatedApp esteja sob o pacotecom.example.annotation, e queremos verificar as classes no pacotecom.example.annotation.components criado no aplicativo da web acima, as seguintes configurações são equivalentes:

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

4. Sob o capô

A anotação@ServletComponentScan é processada porServletComponentRegisteringPostProcessor. Depois de escanear pacotes especificados para anotações de@WebFilter,@WebListenere@WebServlet, uma lista deServletComponentHandlers processará seus atributos de anotação e registrará os beans escaneados:

class ServletComponentRegisteringPostProcessor
  implements BeanFactoryPostProcessor, ApplicationContextAware {

    private static final List HANDLERS;

    static {
        List 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);
        }
    }
}

Como dito emofficial Javadoc,@ServletComponentScan annotation only works in embedded Servlet containers, que é o que vem comSpring Boot por padrão.

5. Conclusão

Neste artigo, apresentamos@ServletComponentScan e como ele pode ser usado para oferecer suporte a aplicativos que dependem de qualquer uma das anotações:@WebServlet,@WebFilter,@WebListener.

A implementação dos exemplos e código pode ser encontradain the GitHub project.