L’annotation @ServletComponentScan au démarrage du printemps

L'annotation @ServletComponentScan dans Spring Boot

1. Vue d'ensemble

Dans cet article, nous allons passer en revue la nouvelle annotation@ServletComponentScan enSpring Boot.

L'objectif est de prendre en charge les annotationsServlet 3.0 suivantes:

  • javax.servlet.annotation.WebFilter

  • javax.servlet.annotation.WebListener

  • javax.servlet.annotation.WebServlet

Les classes annotées@WebServlet,@WebFilter et@WebListener peuvent être automatiquement enregistrées avec un conteneurServlet intégré en annotant@ServletComponentScan sur une classe@Configuration et en spécifiant les paquets.

Nous avons introduit l'utilisation de base de@WebServlet dansIntroduction to Java Servlets et@WebFilter dansIntroduction to Intercepting Filter Pattern in Java. Pour@WebListener, vous pouvez jeter un œil àthis article qui illustre un cas d'utilisation typique des écouteurs Web.

2. Servlets,Filters etListeners

Avant de plonger dans@ServletComponentScan, voyons comment les annotations:@WebServlet,@WebFilter et@WebListener étaient utilisées avant que@ServletComponentScan n'entre en jeu.

2.1. @WebServlet

Maintenant, nous allons d'abord définir unServlet qui répond aux requêtesGET et répond aux“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

Puis un filtre qui filtre les requêtes vers la cible“/hello”, et ajoute“filtering “ à la sortie:

@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

Enfin, un écouteur qui définit un attribut personnalisé dansServletContext:

@WebListener
public class AttrListener implements ServletContextListener {

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

2.4. Déployer vers un conteneurServlet

Maintenant que nous avons créé les composants de base d'une application Web simple, nous pouvons l'empaqueter et la déployer dans un conteneurServlet. Le comportement de chaque composant peut être facilement vérifié en déployant le fichier de guerre empaqueté dansJetty,Tomcat ou tout conteneurServlet prenant en chargeServlet 3.0.

3. Utilisation de@ServletComponentScan enSpring Boot

Vous pourriez vous demander puisque nous pouvons utiliser ces annotations dans la plupart des conteneursServlet sans aucune configuration, pourquoi avons-nous besoin de@ServletComponentScan? Le problème réside dans les conteneursServlet intégrés.

Étant donné que les conteneurs intégrés ne prennent pas en charge les annotations@WebServlet,@WebFilter et@WebListener,Spring Boot, s'appuyant largement sur les conteneurs intégrés, a introduit cette nouvelle annotation@ServletComponentScan pour supporte certains fichiers JAR dépendants qui utilisent ces 3 annotations.

La discussion détaillée se trouve dansthis issue on Github.

3.1. Dépendances Maven

Pour utiliser@ServletComponentScan, nous avons besoin deSpring Boot avec la version 1.3.0 ou supérieure. Ajoutons la dernière version despring-boot-starter-parent etspring-boot-starter-web auxpom:


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

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

3.2. Utilisation de@ServletComponentScan

L'applicationSpring Boot est assez simple. Nous ajoutons@ServletComponentScan pour activer la recherche de@WebFilter,@WebListener et@WebServlet:

@ServletComponentScan
@SpringBootApplication
public class SpringBootAnnotatedApp {

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

}

Sans aucune modification de l’application Web précédente, cela fonctionne:

@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. Spécifier les paquets à analyser

Par défaut,@ServletComponentScan analysera à partir du package de la classe annotée. Pour spécifier les paquets à analyser, nous pouvons utiliser ses attributs:

  • valeur

  • Emballages de base

  • basePackageClasses

L'attribut par défautvalue est un alias pourbasePackages.

Supposons que notreSpringBootAnnotatedApp se trouve sous le packagecom.example.annotation et que nous souhaitons analyser les classes du packagecom.example.annotation.components créé dans l'application Web ci-dessus, les configurations suivantes sont équivalentes:

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

4. Sous la capuche

L'annotation@ServletComponentScan est traitée parServletComponentRegisteringPostProcessor. Après avoir analysé les packages spécifiés pour les annotations@WebFilter,@WebListener et@WebServlet, une liste deServletComponentHandlers traitera leurs attributs d'annotation et enregistrera les beans analysés:

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

Comme dit dans lesofficial Javadoc,@ServletComponentScan annotation only works in embedded Servlet containers, qui est ce qui vient avecSpring Boot par défaut.

5. Conclusion

Dans cet article, nous avons présenté@ServletComponentScan et comment il peut être utilisé pour prendre en charge les applications qui dépendent de l'une des annotations:@WebServlet,@WebFilter,@WebListener.

L'implémentation des exemples et du code peut être trouvéein the GitHub project.