序文
この記事では、Spring MVCによって開発されたWebシステム内のリクエストオブジェクトを取得し、そのスレッドの安全性について説明するいくつかの方法を紹介します。以下ではあまり言いません。詳細な紹介を一緒に見てみましょう。
概要
Spring MVCを使用してWebシステムを開発する場合、クライアントIPアドレス、要求されたURL、ヘッダー内の属性(Cookie、承認情報など)、スプリングMVC、コントローラー、サービス、およびリクエストを処理する他のオブジェクトがリクエストを取得する場合、リクエストを取得する場合に最も重要なオブジェクトを取得する場合に依存している場合に最も重要な問題であるため、リクエストを処理する他のオブジェクトなど、リクエストリクエストを処理するときにリクエストオブジェクトを使用する必要があることがよくあります。多数の同時リクエストは、異なるリクエストオブジェクトが異なるリクエスト/スレッドで使用されることを保証できますか?
ここに注意すべき別の質問があります:前述のリクエストオブジェクト「リクエストの処理時に」をどこで使用しますか?リクエストオブジェクトを取得する方法にわずかな違いがあることを考えると、それらは大まかに2つのカテゴリに分けることができます。
1)スプリングビーンズでリクエストオブジェクトを使用します。コントローラー、サービス、リポジトリ、コンポーネントなどの通常のスプリングビーンなどのMVC豆の両方を含めます。説明の便利さのために、次のテキストの春の豆はすべて略して豆と呼ばれます。
2)非ビーンズでリクエストオブジェクトを使用します。通常のJavaオブジェクトの方法、またはクラスの静的方法などです。
さらに、この記事では、リクエストを表すリクエストオブジェクトについて説明しますが、使用される方法は応答オブジェクト、inputstream/reader、outputstream/writerなどにも適用できます。入力Stream/Readerがリクエストのデータを読み取ることができ、OutputStream/Writerが応答にデータを書き込むことができます。
最後に、リクエストオブジェクトを取得する方法は、SpringとMVCのバージョンにも関連しています。この記事については、スプリング4に基づいて説明されており、実行された実験はすべてバージョン4.1.1を使用しています。
スレッドの安全性をテストする方法
リクエストオブジェクトのスレッドの安全性の問題は、以下の議論を容易にするために特別な注意が必要であるため、まずリクエストオブジェクトがスレッドセーフであるかどうかをテストする方法を説明しましょう。
テストの基本的なアイデアは、クライアントに多数の同時リクエストをシミュレートし、リクエストがサーバーで使用されるかどうかを判断することです。
リクエストオブジェクトが同じかどうかを判断する最も直感的な方法は、リクエストオブジェクトのアドレスを印刷することです。同じ場合、同じオブジェクトが使用されることを意味します。ただし、ほぼすべてのWebサーバーの実装では、スレッドプールが使用されます。これにより、同じスレッドで処理される2つのリクエストが続きます。前のリクエストが処理された後、スレッドプールはスレッドを再現し、スレッドをその後のリクエストに再割り当てします。同じスレッドでは、使用される要求オブジェクトは同じである可能性があります(アドレスは同じで、属性は異なります)。したがって、スレッドセーフメソッドであっても、異なる要求で使用されるリクエストオブジェクトアドレスは同じかもしれません。
この問題を回避するために、1つの方法は、リクエスト処理プロセス中にスレッドを数秒間眠らせることです。これにより、各スレッドが異なるリクエストに割り当てられるのと同じスレッドを避けるのに十分な長さになります。もう1つの方法は、リクエストの他の属性(パラメーター、ヘッダー、ボディなどなど)をスレッドセーフかどうかの基礎として使用することです。これは、異なる要求が次々と同じスレッドを使用しても(リクエストオブジェクトのアドレスが同じである)、リクエストオブジェクトが異なる属性を使用して2回構築されている限り、リクエストオブジェクトの使用はスレッド範囲です。このペーパーでは、テストに2番目の方法を使用しています。
クライアントテストコードは次のとおりです(リクエストを個別に送信するために1000スレッドを作成します):
public class test {public static void main(string [] args)throws exception {string prefix = uuid.randomuuid()。toString()。 for(int i = 0; i <1000; i ++){final string value = prefix+i; new shood(){@override public void run(){try {closeablehttpclient httpclient = httpclient.createdefault(); httpget httpget = new httpget( "http:// localhost:8080/test?key =" + value); httpclient.execute(httpget); httpclient.close(); } catch(ioexception e){e.printstacktrace(); } } } } } }。始める(); }}}サーバー内のコントローラーコードは次のとおりです(リクエストオブジェクトを取得するためのコードは一時的に省略されています):
@controllerpublic class testController {//既存のパラメーターを保存して、パラメーターが複製されているかどうかを判断するため、スレッドが安全であるかどうかを判断します。 @RequestMapping( "/test")public void test()throws arturnedexception {// ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… 「/tが繰り返し表示されます。リクエストの並行性は安全ではありません!」); } else {system.out.println(value); set.add(value); } //シミュレーションプログラムは、一定期間実行されています。スレッド(1000); }}リクエストオブジェクトがスレッドセーフの場合、サーバーの印刷結果は次のとおりです。
スレッドの安全性の問題がある場合、サーバーの印刷結果は次のようになる場合があります。
特別な説明がない場合、この記事の後半でテストコードは省略されます。
方法1:コントローラーにパラメーターを追加します
コードの例
この方法は、実装が最も簡単で、コントローラーコードを直接入力します。
@controllerpublic class testController {@RequestMapping( "/test")public void test(httpservletrequest request)Strows interruptedexception {//シミュレーションプログラムは、期間スレッドのために実行されました。 }}この方法の原則は、コントローラーメソッドが要求の処理を開始すると、Springがリクエストオブジェクトをメソッドパラメーターに割り当てることです。リクエストオブジェクトに加えて、この方法で取得できる多くのパラメーターがあります。詳細については、https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methodsを参照してください
コントローラーでリクエストオブジェクトを取得した後、他のメソッド(サービスメソッド、ツールクラスメソッドなど)でリクエストオブジェクトを使用する場合は、これらのメソッドを呼び出すときにリクエストオブジェクトをパラメーターとして渡す必要があります。
スレッドの安全
テスト結果:スレッドの安全性
分析:現時点では、リクエストオブジェクトはメソッドパラメーターであり、ローカル変数と同等であり、間違いなくスレッドセーフです。
長所と短所
このメソッドの主な欠点は、要求オブジェクトが書き込むにはあまりにも冗長であることです。これは主に2つのポイントに反映されています。
1)要求オブジェクトが複数のコントローラーメソッドで必要な場合、各メソッドにリクエストパラメーターを追加する必要があります。
2)リクエストオブジェクトの取得は、コントローラーからのみ開始できます。要求オブジェクトが使用される場所が関数コールレベルのより深い場所にある場合、コールチェーン全体のすべてのメソッドが要求パラメーターを追加する必要があります。
実際、要求処理プロセス全体で、リクエストオブジェクトは要求全体を実行します。つまり、タイマーなどの特別なケースを除き、リクエストオブジェクトはスレッド内のグローバル変数と同等です。この方法は、このグローバル変数を渡すのと同等です。
方法2:自動注入
コードの例
最初にコードをアップロードします。
@controllerpublic class testController {@autowired private httpservletrequestリクエスト; // autowired request @RequestMapping( "/test")public void test()throws arturtedexception {//シミュレーションプログラムは、swree.sleep(1000); }}スレッドの安全
テスト結果:スレッドの安全性
分析:春には、コントローラーの範囲はSingleton(Singleton)です。つまり、Webシステム全体には1つのTestControllerしかありません。しかし、注入されたリクエストはスレッドセーフです。
このようにして、Bean(この例のTestController)が初期化されると、Springは要求オブジェクトを注入しませんが、プロキシを注入します。 Beanがリクエストオブジェクトを使用する必要がある場合、リクエストオブジェクトはプロキシを介して取得されます。
以下は、特定のコードを介したこの実装の説明です。
上記のコードにブレークポイントを追加し、以下の図に示すように、リクエストオブジェクトのプロパティを表示します。
図に見られるように、リクエストは実際にはプロキシです。プロキシの実装は、Autowireutilsの内部クラスに表示されます
ObjectFactoryDelegatingInvocationHandler: /***現在のターゲットオブジェクトへの怠zyなアクセスのためのReflective InvocationHandler。 */@suppresswarnings( "serial")private static class objectfactorydelegatingInvocationhandlerは、serializable {private final objectFactory <?> objectFactory; public ObjectFactoryDelegatingInvocationHandler(objectFactory <?> objectFactory){this.objectFactory = objectFactory; } @OverrideパブリックオブジェクトInvoke(Object Proxy、Method Method、Object [] args)Throws Throwable {// ... omit lerlevant code try {return method.invoke(this.objectactory.getobject()、args); //エージェント実装コアコード} catch(invocationTargetException ex){ex.getTargetException(); }}}言い換えれば、リクエストのメソッドを呼び出すと、実際にObjectFactory.getObject()によって生成されたオブジェクトのメソッドメソッドを呼び出します。 ObjectFactory.getObject()によって生成されたオブジェクトは、実際のリクエストオブジェクトです。
上記の数値を観察し続け、ObjectFactoryタイプがWebApplicationContextutilsの内部クラスのRequestObjectFactoryであることを見つけます。そして、RequestObjectFactoryコードは次のとおりです。
/***現在のリクエストオブジェクトをオンデマンドで公開する工場。 */ @suppresswarnings( "serial")private static class requestObjectFactoryはObjectFactory <ServletRequest>、Serializable {@Override public servletRequest getObject(){return currentRequestattributes()。getRequest(); } @Override public String toString(){return "current httpservletrequest"; }}その中で、Requestオブジェクトを取得するには、RequestAttributesオブジェクトを取得するには、currentRequestattributes()メソッドを呼び出す必要があります。この方法の実装は次のとおりです。
/***現在のRequestAttributesインスタンスをServletRequestattributesとして返します。 */private static servletrequestattributes currentRequestattributes(){requestattributes requestattr = requestContextholder.CurrentRequestattributes(); if(!(requestattr instance of servletrequestattributes))){新しいIllegalStateException(「現在の要求はサーブレットリクエストではない」); } return(servletrequestattributes)requestattr;}requestAttributesオブジェクトを生成するコアコードは、class requestcontextholderにあり、関連するコードが次のとおりです(クラスの無関係なコードは省略されています):
public abstract class requestContextholder {public static requestattributes currentRequestattributes()throws lequentattributes attributes = getRequestattributes(); //ここでは無関係なロジックは省略されています...... return属性; } public static requestattributes getRequestattributes(){requestattributes attributes = requestattributesholder.get(); if(attributes == null){attributes = enternitablerequestattributesholder.get(); } return属性; } private static final threadlocal <requestattributes> requestattributesholder = new namedthreadlocal <requestattributes>( "request attributes"); private static final threadlocal <requestAttributes> exenitablerequestattributesholder = new AngiantInherItableThreadLocal <RequestAttributes>( "Request Context");}このコードから、生成されたrequestAttributesオブジェクトはスレッドローカル変数(threadlocal)であることがわかります。したがって、リクエストオブジェクトはスレッドローカル変数でもあります。これにより、リクエストオブジェクトのスレッドの安全性が保証されます。
長所と短所
この方法の主な利点:
1)注入はコントローラーに限定されません。方法1では、要求パラメーターのみをコントローラーに追加できます。方法2の場合、コントローラーだけでなく、サービス、リポジトリ、普通の豆などの豆にも注入できます。
2)注入されたオブジェクトは、要求に限定されません。リクエストオブジェクトを挿入することに加えて、このメソッドは、応答オブジェクト、セッションオブジェクトなど、リクエストまたはセッションとしてスコープを持つ他のオブジェクトを挿入することもできます。スレッドの安全性を確保します。
3)コードの冗長性を削減:要求オブジェクトを要求オブジェクトを必要とするBeanに注入するだけで、Beanのさまざまな方法で使用できます。これにより、メソッド1と比較してコード冗長性が大幅に削減されます。
ただし、この方法にはコード冗長性もあります。このシナリオを考慮してください:Webシステムには多くのコントローラーがあり、各コントローラーはリクエストオブジェクトを使用します(このシナリオは実際には非常に頻繁です)。現時点では、リクエストを何度も挿入するためにコードを作成する必要があります。応答も挿入する必要がある場合、コードはさらに面倒になります。以下は、自動噴射方法の改善を説明し、その糸の安全性と利点と短所を分析します。
方法3:基本クラスへの自動注入
コードの例
方法2と比較して、コードの注入部分を基本クラスに入れます。
基本クラスコード:
Public Class BaseController {@Autowired Protected HTTPSERVLETREQUESTリクエスト。 }コントローラーコードは次のとおりです。ベースコントローラーの2つの派生クラスを次に示します。現時点ではテストコードが異なるため、サーバーのテストコードは省略されていません。また、クライアントは対応する変更を行う必要があります(同時に2つのURLに多数の同時リクエストを送信します)。
@controllerpublic class testControllerはbasecontrollerを拡張します{//既存のパラメーターを保存して、パラメーター値が繰り返されるかどうかを判断するため、スレッドが安全であるかどうかを判断します。 @RequestMapping( "/test")public void test()throws arturnedexception {string value = request.getParameter( "key"); //スレッドの安全性を確認します(set.contains(value)){system.out.println(value + "/tが繰り返し表示されます、リクエストの並行性は安全ではありません!"); } else {system.out.println(value); set.add(value); } //シミュレーションプログラムは、一定期間スレッド(1000)の期間実行されています。 }} @controllerPublic class test2Controller extends BaseController {@RequestMapping( "/test2")public void test2()throws interruptedexception {string value = request.getParameter( "key"); //スレッドの安全性を判断(testControllerを備えたセットを使用して判断します)if(testController.set.Contains(value)){system.out.println(value + "/tが繰り返し表示されます。 } else {system.out.println(value); testController.set.add(value); } //シミュレーションプログラムは、一定期間実行されています。スレッド(1000); }}スレッドの安全
テスト結果:スレッドの安全性
分析:方法2のスレッドの安全性を理解することに基づいて、メソッド3がスレッドセーフであることを理解するのは簡単です。異なる派生クラスオブジェクトを作成する場合、ベースクラスのドメイン(ここに注入された要求です)は、異なる派生クラスオブジェクトの異なるメモリスペースを占有します。テスト結果もこれを証明しています。
長所と短所
方法2と比較して、異なるコントローラーでのリクエストの繰り返し注入は回避されます。ただし、Javaが1つのベースクラスの継承のみを許可することを考慮すると、コントローラーが他のクラスを継承する必要がある場合、この方法はもはや使いやすくありません。
メソッド2であろうと方法3であろうと、リクエストをBeanにのみ注入できます。他のメソッド(ツールクラスの静的メソッドなど)が要求オブジェクトを使用する必要がある場合、これらのメソッドを呼び出すときにリクエストパラメーターを渡す必要があります。以下に紹介した方法4は、ツールクラスなどの静的な方法で直接使用できます(もちろん、さまざまな豆でも使用できます)。
方法4:手動で呼び出します
コードの例
@controllerpublic class testController {@RequestMapping( "/test")public void test()throws arturnedexception {httpservletrequest request =((servletRequestattributes)((servletRequestattributes)(requestContextholder.CurrentRequestattributes())。 //シミュレーションプログラムは、sweet.sleep(1000)の期間実行されています。 }}スレッドの安全
テスト結果:スレッドの安全性
分析:この方法は、方法2(自動噴射)に似ていますが、方法2の自動噴射を通じて実装されていることを除き、この方法は手動メソッド呼び出しを通じて実装されます。したがって、この方法はスレッドセーフでもあります。
長所と短所
利点:非ビーンで直接入手できます。短所:より多くの場所を使用する場合、コードは非常に面倒です。したがって、他の方法と組み合わせて使用できます。
方法5:@modelattributeメソッド
コードの例
次の方法とそのバリエーション(突然変異:リクエストとサブクラスのバインドリケストを入れます)は、オンラインでよく見られます。
@controllerpublic class testController {private httpservletrequest request; @modelattribute public void bindRequest(httpservletrequest request){this.request = request; } @RequestMapping( "/test")public void test()throws interruptedexception {//シミュレーションプログラムは、swree.sleep(1000); }}スレッドの安全
テスト結果:スレッドは安全ではありません
分析:@modelattributeアノテーションを使用してコントローラーのメソッドを変更する場合、その機能は、コントローラーの各@RequestMappingメソッドが実行される前に実行されることです。したがって、この例では、bindRequest()の関数は、test()が実行される前にリクエストオブジェクトに値を割り当てることです。 BindRequest()自体のパラメーター要求はスレッドセーフですが、TestControllerはSingletonであるため、TestControllerのフィールドとしてリクエストはスレッドセーフティを保証することはできません。
要約します
要約すると、パラメーター(方法1)、自動インジェクション(方法2および方法3)、およびコントローラーの手動呼び出し(方法4)の追加はすべてスレッドセーフであり、すべてを使用して要求オブジェクトを取得できます。リクエストオブジェクトがシステムで使用されない場合、どちらの方法も使用できます。より多く使用される場合は、コード冗長性を減らすために自動噴射(方法2および方法3)を使用することをお勧めします。非ビーンでリクエストオブジェクトを使用する必要がある場合は、上層を呼び出すときにパラメーターを介して渡すか、手動電話で直接取得できます(方法4)。
さて、上記はこの記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。
参照