/ と /* の違いが思い出せませんか?私の答えはあなたにとって忘れられないものになるでしょう

/ と /* の違いが思い出せませんか?私の答えはあなたにとって忘れられないものになるでしょう

[[407627]]

この記事はWeChatの公開アカウント「BAT's Utopia」から転載したもので、著者はYourBatmanです。この記事を転載する場合は、BAT の Utopia パブリックアカウントにご連絡ください。

序文

こんにちは、YourBatmanです。

今回のタイトルはちょっと大袈裟なもので、かなりプレッシャーを感じています。しかし、それは問題ではありません。結局のところ、自慢するためには眠る必要はなく、違法でもありません。情報爆発の時代では、テクノロジー界でさえクリックベイトの見出しがたくさんあります。

  • 30 分で ORM フレームワークを構築する方法を教えます。実際には、これは単なるリフレクションアノテーションの連結文字列です
  • Docker の使い方を 5 分で教えます。えっと、5分以内に仕事を保証できますか?
  • 数十億のトラフィックに対応する高同時実行キャッシュ ソリューションをお試しください。このようなトラフィックレベルに到達できる人は国内でも(あるいは世界でも)ほんの一握りしかいません。本気ですか?
  • ...

私はクリックベイトのタイトルを決して使わないと主張していますが、今回も例外ではありません。この記事では、よくある質問である / と /* の違いを分析します。私はいつも他のブログ記事を読むのを忘れてしまいますが、この記事は違います。この問題に関してはこの記事で十分であり、あなたの永久記憶となるでしょう(またうっかり自慢してしまいました??)

記事の概要

バージョン規約

  • 8 言語
  • サーブレット: 4.x
  • トムキャット: 9.x

文章

どのような答えが忘れられないものになるでしょうか?私が学生だった頃、記憶に関してよく2つの議論を耳にしました。

暗記:効果は早いが、すぐに忘れてしまい、柔軟に活用できないことが多い(指標は根本的な原因を治すものではない)

理解記憶:効果は遅いが、記憶は長続きし、柔軟に使用できる(症状と根本原因の両方を治療する)

あなたならどれを選びますか?

諺にあるように、魚を与えるよりも魚の釣り方を教える方が良い。後者だけが永続的な記憶を形成できます。偶然にも、この記事では、より永続的に記憶できるように、後者の物語方式を使用します。

/ と /* の違いについては、2015年に独学していたときに明確に理解でき、理解を通じて「永久記憶」が形成されていたことを漠然と覚えていますので、それ以来混乱することはありませんでした。私は基礎をそんなに重視しているのでしょうか? (また自慢しちゃってます…)

市場における間違った答えを指摘する

Google で「/ と /* の違い」というキーワードを検索すると、はっきり言って、出てくる答えは基本的にすべて間違っています。間違った姿勢は基本的に同じであり、その理由はご存知でしょう。

さまざまなエラー事例についてお話しします。主流となっている以下の4つの回答を集めて、一つずつアドバイスさせていただきます。

環境の説明: ネイティブ サーブレットを使用し、war パッケージ モードでサーバーとして外部 Tomcat にデプロイ、ポート番号 8080、コンテキスト パス: appcontext

1. / はサーブレットに使用され、/* はフィルターに使用されます

反例:

  1. @WebFilter(urlPatterns = { "/*" })
  2. パブリッククラスFakeServletはHttpServletを拡張します{
  3.  
  4. @オーバーライド
  5. 保護された void doGet(HttpServletRequest req, HttpServletResponse resp) は ServletException、IOException をスローします {
  6. システム。 out .println( "FakeServlet がリクエストを受信しました: " + req.getRequestURI());
  7. }
  8. }

サーバーを起動し、ブラウザにアクセスします: http://localhost:8080/appcontext/api/demo1。コンソール出力は次のとおりです。

  1. FakeServlet はリクエストを受信します: /appcontext/api/demo1

一般的に言えば、/ は確かに Servlet に使用され、/* は Filter に使用されますが、これが正しいことを意味するわけではありません。

説明: フィルタパスモードが使用されている/無効です

2. / は .jsp リクエストに一致しませんが、/* は .jsp リクエストに一致します

この結論は表面的には問題ないように思えますが、さらに深く考えてみると、「/ は .html リクエストと一致しませんが、/* は .html リクエストと一致します。」という結論を導き出すことができます。これを試してみてください:

  1. @WebServlet(urlPatterns = { "/" })
  2. パブリッククラスFakeServletはHttpServletを拡張します{
  3.  
  4. @オーバーライド
  5. 保護された void doGet(HttpServletRequest req, HttpServletResponse resp) は ServletException、IOException をスローします {
  6. システム。 out .println( "FakeServlet がリクエストを受信しました: " + req.getRequestURI());
  7. }
  8. }
  9.  
  10. @WebFilter(urlPatterns = { "/*" })
  11. パブリッククラスFakeFilterはHttpFilterを拡張します{
  12.  
  13. @オーバーライド
  14. 保護された void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) は IOException、ServletException をスローします {
  15. システム。 out .println( "FakeFilter がリクエストを受信しました: " + req.getRequestURI());
  16. super.doFilter(req, res, チェーン);
  17. }
  18. }

サーバーを起動し、ブラウザにアクセスします: http://localhost:8080/appcontext/api/demo1.jsp。コンソール出力は次のとおりです。

  1. FakeFilter がリクエストを受信: /appcontext/api/demo1.jsp

サーブレットは一致しませんが、これは次の結論と一致しているようです: / は .jsp リクエストと一致しませんが、/* は一致します。

ブラウザが再度 http://localhost:8080/appcontext/api/demo1.html にアクセスすると、コンソール出力は次のようになります。

  1. FakeFilter がリクエストを受信: /appcontext/api/demo1.html
  2.  
  3. FakeServlet はリクエストを受信します: /appcontext/api/demo1.html

フィルターとサーブレットは両方とも正常に一致しましたが、壊れています。

したがって、回答を限定すること自体には問題はありませんが、問題は、.jsp サフィックスが特別な要求であり、特別なケースを一般的な結論としてとらえることは絶対に受け入れられないということです。

3. /* は / よりも一致範囲が広い

この記事の以下の説明から、/ の一致範囲が最も広く、/* の範囲はたまたま / と同じであるが、/* の優先順位は / よりも高く、一致範囲が / よりも広いわけではないことがわかります。

4. / はすべての URL (パス + サフィックス) に一致し、/* はパス タイプにのみ一致します

1文で反論する: /* は /api/demo1.html のようなサフィックス型 URL にも一致します (実際、上に例が示されています)

これら 4 つの結論は、検索結果で非常に高いランクにランクされています。彼らは一体何人の子供たちを騙してきたのだろうか。毎回懐疑的になるのではなく、時間をかけてコードを書いて自分で実験してみる方が信頼性が高くなります。私は常に実践的なコーディングを推奨しています。他の人のコードに従うよりも、独自のコードを書いたほうがよいでしょう。

これらの結論を踏まえて、大きなトリックから始めましょう。サーブレット仕様の URL マッチング メカニズムを根本から理解し、/ と /* の違いを理解できるようにします。魚の釣り方を教えます。絶対に忘れないでしょう。

サーブレット urlPatterns パス マッピング

注: この記事で言及されているサーブレットは広範(標準)なので、フィルターのurlPatternsも含まれています。

Servlet/Filter は、HTTP リクエストを処理するために使用されるサーバー側の小さなプログラムです。各サーブレットは 1 つ以上のパスをマップできます。これは、XML 時代では次のように記述できます (url-pattern タグは複数のパスを記述できます)。

  1. <サーブレットマッピング>
  2. <サーブレット>Demo1Servlet</サーブレット>
  3. <url-pattern>/api/demo1</url-pattern>
  4. <url-pattern>/api/demo2</url-pattern>
  5. </サーブレットマッピング>

@WebServlet アノテーションは次のように記述されます。

  1. @WebServlet(urlPatterns = { "/api/demo1" , "/api/demo2" })
  2. パブリッククラス Demo1Servlet は HttpServlet を拡張します { ... }

この時点で、サーブレットはこれら 2 つの URL を処理できます。

問題は、このサーブレットで特定のタイプのリクエストを処理したい場合、どうすれば解決できるかということです。

当然、リクエストの種類を 1 つずつ列挙することはできないため、サーブレット パターン マッチングが必要になります。リテラル文字列の記述に加えて、urlPatterns はパターン文字列もサポートします (属性名からわかるはずです)。

次に、この記事の核となるサーブレットのマッチング方法に焦点を当てて詳しく説明します。

4つのサーブレットマッチング方法

Servlet 仕様では 4 つのマッチング方法が合意されています。例外なく、それぞれの方法は非常に重要であり、よく使用されます。以下、一つずつ紹介します。

1. 完全一致

名前が示すように、urlPatterns はワイルドカードのない正確な文字列です。例:

  1. @WebServlet(urlPatterns = { "/api/demo1" , "/api/demo2" }) // 完全一致
  2. パブリッククラス UrlPatternDemoServlet は HttpServlet を拡張します {
  3. @オーバーライド
  4. 保護された void doGet(HttpServletRequest req, HttpServletResponse resp) は ServletException、IOException をスローします {
  5. システム。 out .printf( "リクエストを受信しました: %s ServletPath:%s PathInfo:%s\n" , req.getRequestURI(), req.getServletPath(), req.getPathInfo());
  6. }
  7. }

ログを比較しやすくするために、servletPath と pathInfo 情報を出力します。

ブラウザは http://localhost:8080/appcontext/api/demo1 および /api/demo2 にアクセスしてリクエストを受信し、コンソールにそれぞれ次のように出力されます。

  1. 受信したリクエスト: /appcontext/api/demo1 ServletPath:/api/demo1 PathInfo: null  
  2. 受信したリクエスト: /appcontext/api/demo2 ServletPath:/api/demo2 PathInfo: null  

2. パスマッチング

パターンルール: / で始まり、/* で終わります。のように:

  1. @WebServlet(urlPatterns = { "/api/*" , "/*" }) // パスのマッチング
  2. パブリッククラス UrlPatternDemoServlet は HttpServlet を拡張します {
  3. @オーバーライド
  4. 保護された void doGet(HttpServletRequest req, HttpServletResponse resp) は ServletException、IOException をスローします {
  5. // 上記と同じ
  6. }
  7. }

ブラウザは http://localhost:8080/appcontext/api/demo1 にアクセスし、コンソール出力 (/api/* に一致) は次のようになります。

  1. 受信したリクエスト: /appcontext/api/demo1 ServletPath:/api PathInfo:/demo1

http://localhost:8080/appcontext/apiapi/demo1 にアクセスすると、コンソール出力(/* に一致するもの)が表示されます。

  1. 受信したリクエスト: /appcontext/apiapi/demo1 ServletPath: PathInfo:/apiapi/demo1

焦点: /* パターンが一致すると、ServletPath の値は空の文字列になりますが、PathInfo の値はより豊富になります。

3. 接尾辞マッチング

パターンルール: * で始まります。 (*. で始まるため、/api/*.jsp は無効であることに注意してください)。のように:

  1. @WebServlet(urlPatterns = { "*.jsp" , "*.*" }) // サフィックス名の一致
  2. パブリッククラス UrlPatternDemoServlet は HttpServlet を拡張します {
  3. @オーバーライド
  4. 保護された void doGet(HttpServletRequest req, HttpServletResponse resp) は ServletException、IOException をスローします {
  5. // 上記と同じ
  6. }
  7. }

http://localhost:8080/appcontext/api/demo1 にアクセスすると、サフィックスがないため、結果は 404 になります。 http://localhost:8080/appcontext/api/demo1.jsp にアクセスすると、コンソール出力 (*.jsp に一致) が表示されます。

  1. 受信したリクエスト: /appcontext/api/demo1.jsp ServletPath:/api/demo1.jsp PathInfo: null  

http://localhost:8080/appcontext/api/demo1.servlet にアクセスすると、urlPatterns に .servlet サフィックスに一致するパターンがないため、結果は 404 になります。 http://localhost:8080/appcontext/api/demo1 にアクセスすると、結果は 404 になります。理由は上記と同じです。 http://localhost:8080/appcontext/api/demo1.* にアクセスすると、コンソールに次のように表示されます (一致する *.*):

  1. 受信したリクエスト: /appcontext/api/demo1.* ServletPath:/api/demo1.* PathInfo: null  

このマッチング方法は非常に「特別」だとお分かりいただけましたか?次の 2 つの点に注意する必要があります。

パターンは *. で始まり、それに続くものはすべて定数で、 * も定数です。たとえば、*.* が一致するサフィックスは .* である必要があり、他のものは使用できません。

このマッチングモードでは、pathInfo は常に null で、servletPath は常に "all" です。

4. デフォルトのマッチング

パターンルール: 固定値 /。のように:

考えてみてください。これは私たちがよく知っている DispatcherServlet の一致するパスではないでしょうか?

  1. @WebServlet(urlPatterns = "/" ) // デフォルトの一致
  2. パブリッククラス UrlPatternDemoServlet は HttpServlet を拡張します {
  3. @オーバーライド
  4. 保護された void doGet(HttpServletRequest req, HttpServletResponse resp) は ServletException、IOException をスローします {
  5. // 上記と同じ
  6. }
  7. }

今回はどのパスにも一致します。

http://localhost:8080/appcontext にアクセスすると、コンソールに次のように表示されます。

  1. 受信したリクエスト: /appcontext/ ServletPath:/ PathInfo: null  

http://localhost:8080/appcontext/api/demo1 にアクセスすると、コンソールに次のように表示されます。

  1. 受信したリクエスト: /appcontext/api/demo1 ServletPath:/api/demo1 PathInfo: null  

http://localhost:8080/appcontext/api/demo1.html にアクセスすると、コンソールに次のように表示されます。

  1. 受信したリクエスト: /appcontext/api/demo1.html ServletPath:/api/demo1.html PathInfo: null  

この一致ルールでは、pathInfo は常に null になり、servletPath は常に "all" になります。

pathInfo について: pathInfo には、サーブレットがパスと一致する場合にのみ値が設定されます。それ以外の場合は常にnullになります

URLマッチングに関する注意事項

サーブレットの URL のマッチングは Ant スタイルでも Regex でもありません。特殊記号は * のみであり、使用場所には厳しい制限があります。勝手に組み合わせないでください。

役に立つと思われる 2 つの典型的な誤解を以下に示します。

  • /api/*.jsp: この urlPatterns は不正であり、起動時に「IllegalArgumentException: サーブレット マッピング内の [/api/*.jsp] が無効です」というエラー メッセージが報告されます。理由は次のとおりです。
    • パスが一致する場合、/* の後に何も記述できません。
    • サフィックス名が一致する場合は*。最初にあるはずだ
  • /api/*/demo: この urlPatterns は有効です。これは完全一致です。つまり、真ん中に * があっても、リクエストパス /api/*/demo にのみ一致します。

マッチング順序

場合によっては、URL が複数の urlPatterns に一致することがありますが、どれが優先されますか?

サーブレットも「国際慣行」に従います。正確であればあるほど、優先度が高くなります。曖昧であればあるほど、優先順位は低くなります。パターン モードの観点から言えば、範囲が小さいほど優先度が高くなります。範囲が広いほど、優先順位は後になります。

したがって、サーブレットの 4 つの一致方法は、小さいものから大きいものへ (優先度の高いものから低いものへ) の順に並べられています: 完全一致 > パス一致 > サフィックス一致 > デフォルト一致。

/ と /* の違い

最後に、本日のメインコースです。

上から下まで読んでみて、この質問の答えが浮かび上がったと思いますか?さて、両者の類似点と相違点をまとめてみましょう。

類似点

ほとんどのシナリオでは、動作は同じです。すべて一致します。

違い

非常に似ているため、区別することが難しいのです。

について/:

  • サーブレット内の特別なマッチングモード(フィルターで使用する場合は無効)
  • デフォルトの一致はすべてのパスに一致することを意味するため、インスタンスは 1 つしか存在できません (複数ある場合は上書きされます)
  • 最も低い優先度 (一番下の行) は、/* との最大の違いです。他の URL パターンは上書きされず、サーブレット コンテナ (Tomcat など) に組み込まれている DefaultServlet のみが上書きされます。

について/*:

  • これは4つのマッチングモードのパスマッチングに属し、サーブレットとフィルタで使用できます。
  • 最高の優先度(完全一致に次ぐ)。したがって、すべてのサフィックス名の一致がカバーされ、404 の問題が簡単に発生する可能性があります。したがって、このモードは非常に「有害」であり、通常はフィルターでのみ使用されます。

DispatcherServlet が .jsp リクエストをインターセプトしない根本原因の分析

/ は Servlet でのみ使用でき、/* は通常 Filter でのみ使用されます。

よく知られている Spring MVC DispatcherServlet のデフォルトの一致パスは / であり、次のリクエストなどのさまざまなリクエストをインターセプトします。

  • デモ1
  • デモ1
  • /static/main.js

ただし、/api/demo1.jsp など、.jsp で終わるリクエストはインターセプトされません。この現象に基づいて、「間違った」記述が表示されます: / は .jsp リクエストをブロックしませんが、/* はそれらをブロックします (/* のスコープは / よりも大きいです)。

この現象の根本的な原因は次のとおりです。サーブレット コンテナー (Tomcat など) には、次の図に示すように、.jsp リクエストに特に一致するサーブレット プロセッサが組み込まれています。

サフィックス名の一致の優先順位はデフォルトの一致よりも高いため、.jsp で終わるリクエストは DispatcherServlet によって「インターセプト」されず、JspServlet によって処理されます。

この一連の分析の後、もう答えを暗記する必要はなくなったのでしょうか?彼らのことを決して忘れないのでしょうか?

要約する

サーブレットの urlPatterns マッチング方法は、Java Web を学習する上で重要な部分であり、Spring MVC の原則を深く理解するための入り口でもあります。結局のところ、Spring MVC は依然としてビジネス開発の第一選択肢であり、今後も長い間その地位を維持するでしょう。

この記事では、以下の内容を含むサーブレット マッチング方法について包括的に説明します。

  • 4つのマッチング方法
  • マッチング順序(優先順位)
  • サーブレットとフィルターのマッチングの違い
  • パターンマッチングにおける / と /* の違いの根本的な原因

この記事が、サーブレットのパターン マッチングで悩まされることがなくなり、一見もっともらしい結論に混乱することがなくなることを願っています。疑問がある場合は、コーディング検証を実行してください。

この記事では、魚の釣り方を教えながら、/ と /* の違いを説明します。皆様の永遠の思い出となることを願っております。やったかな?

<<:  Tekton を使用した自動化パイプラインのリファクタリング

>>:  中国システムが「ワンネットワーク管理」ソリューションとスタープラットフォーム製品をリリース

推薦する

ウェブサイトの最適化について知っておくべき検索エンジンの投票アルゴリズム

現在、インターネット上にはウェブサイトのランキングに関する新しい概念があり、SEO 担当者はこれを投...

Red Hat、オープンエッジコンピューティングの継続的な進化を促進する軽量Kubernetesソリューションを発表

Red Hat Inc. は本日、ロボット、IoT ゲートウェイ、POS、公共交通機関などの小型デバ...

spinservers: 月額 99 ドル、米国サーバー (サンノゼ/ダラス)、2*e5-2630L v3/256g DDR4/3.2T SSD/10Gbps 帯域幅

spinservers は現在、米国独自の独立サーバー 3 台を販売しています。ダラス データ セン...

ウェイト8ウェブサイトの内部ページ最適化の簡単な分析

中国のトップ比較ショッピング サイトの 1 つとして、SmartPoint は誰もが知っていると思い...

ウェブサイトのインクルードの問題はサーバーの問題にあります

ウェブサイトのキーワードランキングを最適化するための基本は、ウェブサイトが組み込まれることです。もち...

Kubernetes の導入戦略を 1 つの記事で理解する

この記事では、Kubernetes のデプロイメントの概念と一般的な戦略について詳しく説明し、それぞ...

インターネット時代:言論の復活

文/ 辛海光当初私は、ドラゴンを倒すことができるスキルであるスピーチは、中国のような国では将来がない...

Baiduの最適化のヒントは品質が鍵となる

ウェブサイト業界は現在かなり人気があるため、大手ウェブサイトの最適化と組み入れの要件はますます厳しく...

新時代を突き進み、革新を求めて、志雄張昊公開講座が武漢にやってきた

月収10万元の起業の夢を実現するミニプログラム起業支援プラン9月は収穫の季節です。私たちは、熊張オー...

千米は「起業の夢と軍隊への愛」起業・就職都市パートナーサミットに出席した。

月収10万元の起業の夢を実現するミニプログラム起業支援プラン国防の強化と軍改革がさらに深化していく中...

raksmartはどうですか?ロサンゼルス国際 BGP ライン VPS シンプルレビュー

raksmartはどうですか? raksmart の VPS はどうですか? raksmart ロサ...

三国志を例に挙げて分散アルゴリズムについて語るのって、気楽なことでしょうか?

[[357046]]序文「三国殺し」は、中国の三国時代を背景に、身分を手がかりにトランプを形にした...

マルチクラウド管理ツールの次の波は何でしょうか?

ほとんどの企業がマルチクラウド戦略を採用する本来の意図は、自社のニーズを満たす一連のサービスを選択す...

短い講演: クラウドネイティブの可観測性の未来

近年、エンタープライズ アプリケーションとインフラストラクチャが進化し続けるにつれて、複雑なシステム...

Google アップデートに対処するためのステップバイステップガイド (I)

最近、友人から、なぜナルトSEOで宣伝したウェブサイトがGoogleペンギンアップデートを無事に乗り...