1. ホーム
  2. java

[解決済み] Spring MVCが404で応答し、「No mapping found for HTTP request with URI [...] in DispatcherServlet」と報告されるのはなぜですか?

2023-01-19 19:36:24

質問

Tomcat上に配置されたSpring MVCアプリケーションを書いています。以下を参照してください。 最小限の、完全で、検証可能な例

public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { };
    }
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { SpringServletConfig.class };
    }
    protected String[] getServletMappings() {
        return new String[] { "/*" };
    }
}

ここで SpringServletConfig

@Configuration
@ComponentScan("com.example.controllers")
@EnableWebMvc
public class SpringServletConfig {
    @Bean
    public InternalResourceViewResolver resolver() {
        InternalResourceViewResolver vr = new InternalResourceViewResolver();
        vr.setPrefix("/WEB-INF/jsps/");
        vr.setSuffix(".jsp");
        return vr;
    }
}

最後に @Controller をパッケージ内で com.example.controllers

@Controller
public class ExampleController {
    @RequestMapping(path = "/home", method = RequestMethod.GET)
    public String example() {
        return "index";
    }
}

私のアプリケーションのコンテキスト名は Example . にリクエストを送ると

http://localhost:8080/Example/home

の場合、アプリケーションはHTTPステータス404で応答し、次のようにログに記録されます。

WARN  o.s.web.servlet.PageNotFound - No mapping found for HTTP request with URI `[/Example/WEB-INF/jsps/index.jsp]` in `DispatcherServlet` with name 'dispatcher'

JSPリソースを /WEB-INF/jsps/index.jsp Spring MVCが私のコントローラを使用してリクエストを処理し、JSPに転送することを期待していたのですが、なぜ404で応答するのでしょうか?


これは、この警告メッセージに関する質問の正規の投稿であることを意図しています。

どのように解決するのですか?

標準的なSpring MVCアプリケーションでは、すべてのリクエストは DispatcherServlet を通してすべてのリクエストを処理します。

DispatcherServlet は、その ApplicationContext であり、利用可能であれば ApplicationContext に登録されている ContextLoaderListener に登録され、 リクエストサービングロジックを設定するために必要な特別な Bean のために使われます。 これらのビーンについては、ドキュメントで説明されている .

おそらく最も重要なのは、ビーンタイプの HandlerMapping マップ

ハンドラへのリクエストとプリポストプロセッサー(ハンドラインターセプター)のリストを (ハンドラインターセプタ) のリストです。 によって異なります。 HandlerMapping の実装によって異なります。最も一般的な実装は はアノテートコントローラをサポートしていますが、他の実装も存在します。 もあります。

この の javadoc を参照してください。 HandlerMapping は、実装がどのように振舞わなければならないかをさらに記述しています。

DispatcherServlet は、このタイプのすべてのビーンを見つけ、 ある順序で登録します (カスタマイズ可能)。リクエストに応答している間 DispatcherServlet はこれらの HandlerMapping オブジェクトをループし、それぞれのオブジェクトを getHandler を使用して、標準的な HttpServletRequest . 4.3.x の時点では が見つからなければ が見つからない場合、それは は警告を記録します。 が表示されます。

<ブロッククオート

URI を持つ HTTP リクエストに対するマッピングが見つかりません [/some/path]DispatcherServlet で、名前 SomeName

のどちらかを NoHandlerFoundException を投げるか、404 Not Found ステータスコードでレスポンスを即座にコミットします。

はなぜ DispatcherServlet を見つけたのでしょうか? HandlerMapping を見つけることができますか?

最も一般的な HandlerMapping の実装は RequestMappingHandlerMapping の登録を処理します。 @Controller ビーンをハンドラとして登録します (実際にはその @RequestMapping アノテーションされたメソッド)。このタイプのビーンを自分で宣言することもできます ( @Bean または <bean> または他のメカニズム)を使用するか、または 内蔵のオプション . これらは

  1. をアノテートします。 @Configuration クラスには @EnableWebMvc .
  2. を宣言します。 <mvc:annotation-driven /> メンバを XML 設定で宣言します。

上のリンクで説明されているように、これらはどちらも RequestMappingHandlerMapping ビーン(および他の多くのもの)を登録します。しかし HandlerMapping はハンドラ無しではあまり役に立ちません。 RequestMappingHandlerMapping が期待するのは、いくつかの @Controller ビーンを期待しますので、それらも @Bean メソッドで宣言する必要があります。 <bean> の宣言や、 コンポーネントスキャンによる @Controller でアノテーションされたクラスのコンポーネントスキャンによって行われます。 これらのビーンが存在することを確認します。

警告メッセージと404が表示され、上記のすべてが正しく構成されている場合。 であれば、間違ったURIにリクエストを送信していることになります。 によって処理されないもの、検出された @RequestMapping アノテーションされたハンドラメソッドによって処理されないものです。

spring-webmvc ライブラリは、他の組み込みの HandlerMapping の実装を提供しています。例えば BeanNameUrlHandlerMapping 地図

<ブロッククオート

URLからスラッシュ("/")で始まる名前のビーンへ。

であり、いつでも自分で書くことができます。明らかに の少なくとも一つにマッチすることを確認する必要があります。 HandlerMapping オブジェクトのハンドラの少なくとも一つに一致することを確認しなければなりません。

もし、暗黙的あるいは明示的に任意の HandlerMapping ビーンを登録しない場合 (あるいは detectAllHandlerMappings true を含む)、その DispatcherServlet はいくつかの のデフォルトを登録します。 . これらは DispatcherServlet.properties と同じパッケージで DispatcherServlet クラスと同じパッケージ内にあります。それらは BeanNameUrlHandlerMapping DefaultAnnotationHandlerMapping (と似ている)。 RequestMappingHandlerMapping に似ていますが、非推奨です)。

デバッギング

Spring MVCは RequestMappingHandlerMapping . 例えば @Controller のように

@Controller
public class ExampleController {
    @RequestMapping(path = "/example", method = RequestMethod.GET, headers = "X-Custom")
    public String example() {
        return "example-view-name";
    }
}

は、INFOレベルで次のようにログを記録します。

Mapped "{[/example],methods=[GET],headers=[X-Custom]}" onto public java.lang.String com.spring.servlet.ExampleController.example()

登録されているマッピングを記述しています。ハンドラが見つからないという警告が表示されたら、そのメッセージのURIとここに記載されているマッピングを比較してください。で指定されたすべての制限は @RequestMapping で指定されたすべての制限は、Spring MVCがハンドラを選択するために一致する必要があります。

その他 HandlerMapping の実装は、マッピングとそれに対応するハンドラのヒントになるような独自のステートメントを記録します。

同様に、DEBUGレベルでSpringのロギングを有効にして、Springが登録するBeanを確認します。どのアノテーションされたクラスを見つけ、どのパッケージをスキャンし、どのBeanを初期化したかが報告されるはずです。もし期待したものが存在しない場合、あなたの ApplicationContext の設定を見直してください。

その他のよくある間違い

A DispatcherServlet は、単に典型的な Java EE Servlet . これを登録すると、典型的な <web.xml> <servlet-class><servlet-mapping> 宣言、あるいは直接 ServletContext#addServlet の中で WebApplicationInitializer で、あるいは Spring boot が使うどんなメカニズムでも構いません。このように、あなたは urlマッピング ロジックに依存しなければなりません。 サーブレット仕様 で指定されたロジックは、12章を参照してください。また

この点を考慮すると、よくある間違いは、ServletのURLを登録する際に DispatcherServlet という url マッピングで /* で、ビューの名前を @RequestMapping ハンドラメソッドからビュー名を返し、JSP がレンダリングされることを期待します。例えば、次のようなハンドラメソッドを考えてみましょう。

@RequestMapping(path = "/example", method = RequestMethod.GET)
public String example() {
    return "example-view-name";
}

InternalResourceViewResolver

@Bean
public InternalResourceViewResolver resolver() {
    InternalResourceViewResolver vr = new InternalResourceViewResolver();
    vr.setPrefix("/WEB-INF/jsps/");
    vr.setSuffix(".jsp");
    return vr;
}

のようなリクエストを期待するかもしれません。 転送される というパスで JSP リソースに転送されます。 /WEB-INF/jsps/example-view-name.jsp . これは起こりません。その代わり、コンテキスト名が Example というコンテキスト名を仮定すると DisaptcherServlet は報告します。

<ブロッククオート

URI を持つ HTTP リクエストのマッピングが見つかりませんでした [/Example/WEB-INF/jsps/example-view-name.jsp]DispatcherServlet を名前 'dispatcher' とする

なぜなら DispatcherServlet にマップされるからです。 /*/* はすべてにマッチしますが(より優先順位の高い完全一致を除く)、このうち DispatcherServlet を処理するために選ばれるでしょう。 forward から JstlView (が返す)。 InternalResourceViewResolver ). ほとんどすべての場合において DispatcherServlet はそのようなリクエストを処理するように設定されていないでしょう。 .

その代わりに、この単純なケースでは DispatcherServlet/ に変更し、デフォルトサーブレットとしてマークします。デフォルトのサーブレットはリクエストに最後にマッチします。これにより、典型的なサーブレットコンテナは内部のサーブレット実装を選択することができ、その実装は *.jsp にマップされた内部のサーブレット実装を選び、 JSP リソースを処理することができます (例えば、Tomcat は JspServlet というように)、デフォルトのサーブレットで試す前に、JSPリソースを処理します。

あなたの例で見ているのはこれです。