1. 概要

この記事では、Spring Bootの新しい@ServletComponentScanアノテーションについて説明します。

目的は、次のサーブレット3.0アノテーションをサポートすることです。

  • javax.servlet.annotation.WebFilter
  • javax.servlet.annotation.WebListener
  • javax.servlet.annotation.WebServlet

@WebServlet @WebFilter 、および @WebListener アノテーション付きクラスは、 @アノテーションを付けることにより、埋め込まれたサーブレットコンテナーに自動的に登録できます。 @ConfigurationクラスのServletComponentScanとパッケージの指定。

Javaサーブレットの概要@WebServletの基本的な使用法を紹介し、Javaでのインターセプトフィルターパターンの概要@WebFilterを紹介しました。 @WebListener については、Webリスナーの一般的な使用例を示すこの記事をご覧ください。

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. サーブレットコンテナにデプロイする

単純なWebアプリケーションの基本コンポーネントを構築したので、それをパッケージ化してサーブレットコンテナーにデプロイできます。 パッケージ化されたwarファイルをJetty Tomcat 、または Servlet3.0をサポートする任意のServletコンテナーにデプロイすることで、各コンポーネントの動作を簡単に確認できます。

3. Spring Boot@ServletComponentScanを使用する

ほとんどのサーブレットコンテナでこれらのアノテーションを構成なしで使用できるので、なぜ @ServletComponentScanが必要なのか疑問に思われるかもしれません。 問題は、埋め込まれたサーブレットコンテナにあります。

埋め込みコンテナは@WebServlet @WebFilter @WebListener アノテーションをサポートしていないため、 Spring Boot、は大きく依存しています埋め込みコンテナでは、この3つのアノテーションを使用するいくつかの依存jarをサポートするために、この新しいアノテーション@ServletComponentScanが導入されました。

詳細については、Githubこの号をご覧ください。

3.1. Mavenの依存関係

@ServletComponentScan を使用するには、バージョン1.3.0以降の SpringBootが必要です。 spring-boot-starter-parentspring-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を使用する

SpringBootアプリはとてもシンプルです。 @ServletComponentScan を追加して、 @WebFilter @WebListener 、および @WebServlet:のスキャンを有効にします。

@ServletComponentScan
@SpringBootApplication
public class SpringBootAnnotatedApp {

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

}

以前のWebアプリケーションに変更を加えることなく、次のように機能します。

@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
  • basePackageClasses

デフォルトのvalue属性は、basePackagesのエイリアスです。

SpringBootAnnotatedAppがパッケージcom.baeldung.annotationの下にあり、Webアプリケーションで作成されたパッケージcom.baeldung.annotation.componentsのクラスをスキャンするとします。上記では、次の構成は同等です。

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

4. フードの下

@ServletComponentScan アノテーションは、ServletComponentRegisteringPostProcessorによって処理されます。 指定されたパッケージで@WebFilter @WebListener 、および @WebServlet アノテーションをスキャンした後、ServletComponentHandlersのリストがそれらのアノテーション属性を処理します。スキャンした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アノテーションは、デフォルトで SpringBootに付属している組み込みサーブレットコンテナでのみ機能します。

5. 結論

この記事では、 @ServletComponentScan と、アノテーションのいずれかに依存するアプリケーションをサポートするためにそれを使用する方法を紹介しました: @WebServlet @WebFilter 、[ X191X]@WebListener。

例とコードの実装は、GitHubプロジェクトにあります。