セッションごとに一意にしたいコントローラーがあります。春のドキュメントによると、実装には2つの詳細があります。
1。初期Web設定
要求、セッション、およびグローバルセッションレベル(WebスコープのBean)でBeanのスコープをサポートするには、Beanを定義する前にいくつかのマイナーな初期構成が必要です。
ドキュメントに示されているように、次をweb.xml
に追加しました。
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
2。依存関係としてスコープされたBean
HTTP要求スコープBeanを(たとえば)別のBeanに注入する場合、スコープBeanの代わりにAOPプロキシを注入する必要があります。
以下に示すように、@Scope
でproxyMode
を提供してBeanに注釈を付けました。
@Controller
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class ReportBuilder implements Serializable {
...
...
}
問題
上記の構成にもかかわらず、次の例外が発生します。
org.springframework.beans.factory.BeanCreationException:「scopedTarget.reportBuilder」という名前のBeanを作成中にエラーが発生しました。スコープ「セッション」は現在のスレッドに対してアクティブではありません。シングルトンから参照する場合は、このBeanのスコーププロキシを定義することを検討してください。ネストされた例外はJava.lang.IllegalStateExceptionです:スレッドバインドリクエストが見つかりません:実際のWebリクエスト以外のリクエスト属性を参照していますか、それとも元の受信スレッド外でリクエストを処理していますか?実際にWebリクエスト内で操作しているにもかかわらずこのメッセージを受信する場合、コードはおそらくDispatcherServlet/DispatcherPortletの外部で実行されています。この場合、RequestContextListenerまたはRequestContextFilterを使用して現在のリクエストを公開します。
更新1
以下は私のコンポーネントスキャンです。 web.xml
には次のものがあります。
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>org.example.AppConfig</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
そしてAppConfig.Java
の次は:
@Configuration
@EnableAsync
@EnableCaching
@ComponentScan("org.example")
@ImportResource("classpath:applicationContext.xml")
public class AppConfig implements AsyncConfigurer {
...
...
}
更新2
再現可能なテストケースを作成しました。これは非常に小さなプロジェクトなので、違いはありますが、同じエラーが発生します。かなりの数のファイルがあるので、tar.gz
として megafileupload にアップロードしました。
私は自分の質問に答えています。なぜなら、それが原因と可能な解決策のより良い概要を提供するからです。彼が原因を突き止めたので、@ Martinにボーナスを授与しました。
原因
@Martinが示唆するように、原因は複数のスレッドの使用です。 Spring Guide で説明されているように、リクエストオブジェクトはこれらのスレッドでは使用できません。
DispatcherServlet
、RequestContextListener
、およびRequestContextFilter
はすべてまったく同じことを行います。つまり、HTTP要求オブジェクトを、その要求を処理しているスレッドにバインドします。これにより、リクエストスコープおよびセッションスコープのBeanがコールチェーンのさらに下で利用可能になります。
ソリューション1
要求オブジェクトを他のスレッドで使用可能にすることは可能ですが、システムにいくつかの制限があり、すべてのプロジェクトで機能しない場合があります。このソリューションは マルチスレッドWebアプリケーションでスコープされたリクエストBeanにアクセスする :から取得しました。
私はこの問題をなんとか乗り越えました。
SimpleAsyncTaskExecutor
/WorkManagerTaskExecutor
の代わりにThreadPoolExecutorFactoryBean
を使用し始めました。利点は、SimpleAsyncTaskExecutor
がスレッドを再利用しないことです。それは解決策の半分に過ぎません。解決策の残りの半分は、RequestContextFilter
の代わりにRequestContextListener
を使用することです。RequestContextFilter
(およびDispatcherServlet
)には、基本的に子スレッドが親コンテキストを継承できるthreadContextInheritable
プロパティがあります。
ソリューション2
他の唯一のオプションは、リクエストスレッド内でセッションスコープBeanを使用することです。私の場合、これは次の理由で不可能でした。
@Async
の注釈が付けられます。問題はSpringアノテーションではなく、デザインパターンにあります。異なるスコープとスレッドを混在させます:
シングルトンはどこでも使用できますが、大丈夫です。ただし、セッション/リクエストスコープは、リクエストにアタッチされているスレッドの外部では使用できません。
非同期ジョブは、リクエストまたはセッションがもう存在しない場合でも実行できるため、リクエスト/セッション依存のBeanを使用することはできません。また、別のスレッドでジョブを実行している場合、どのスレッドが発信元リクエストであるかを知る方法もありません(この場合、aop:proxyは役に立たないことを意味します)。
コードは、ReportController、ReportBuilder、UselessTask、ReportPageの間にcontractを作成したいように見えると思います。 UselessTaskからのデータを保存し、ReportControllerまたはReportPageでデータを読み取るために単純なクラス(POJO)を使用する方法はありますか?ReportBuilderは使用しないでくださいもうありませんか?
他の誰かが同じ点にこだわっていた場合、以下は私の問題を解決しました。
Web.xmlで
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
セッション中コンポーネント
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
Pom.xmlで
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
ドキュメントごと :
Spring Web MVC内、つまりSpring DispatcherServletまたはDispatcherPortletによって処理されるリクエスト内でスコープBeanにアクセスする場合、特別な設定は必要ありません:DispatcherServletとDispatcherPortletはすべての関連する状態を既に公開しています。
Spring MVCの外で実行している場合(DispatchServletによって処理されない)、RequestContextListener
だけでなくContextLoaderListener
を使用する必要があります。
Web.xmlに以下を追加します
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
そのスコープでBeanを維持するために、Springにセッションを提供します
更新:他の回答によると、@Controller
は、Spring MVCコンテキストを使用しているときにのみ意味があるため、@ Controllerはコードの実際の目的を果たしていません。それでも、セッションスコープ/リクエストスコープでどこにでもBeanを注入できます(特定のスコープでBeanを注入するだけでSpring MVC/Controllerは必要ありません)。
更新: RequestContextListener は、現在のスレッドのみにリクエストを公開します。
2つの場所でReportBuilderを自動配線しました
1。 ReportPage
-ここでは、SpringがReport Builderを適切に挿入していることがわかります。これは、まだ同じWebスレッドにいるためです。 ReportBuilderがこのようにReportPageに挿入されるように、コードの順序を変更しました。
log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();
私は追加されたデバッグ目的のために、ログがあなたのロジックに従って追跡する必要があることを知っていました。
2。 UselessTasklet
-ここでは例外が発生しました。これは、Spring Batchによって作成された別のスレッドで、RequestContextListener
によって要求が公開されていないためです。
Spring BatchにReportBuilder
インスタンスを作成およびインジェクトするための異なるロジックが必要です(Spring Batchパラメーターを使用し、Future<ReportBuilder>
を使用して、今後の参照用に返すことができます)
ファイルに次のコードを追加して、この問題を修正しました。
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
XML構成-
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
上記のJava設定を使用して行うことができます-
@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}
no-xml設定でRequestContextListenerを追加する方法?
私は春バージョン5.1.4.RELEASEとno needを使用してpomに以下の変更を追加しています。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
私の答えは、OPが説明する一般的な問題の特別なケースを指しますが、誰かが助けになる場合に備えて追加します。
@EnableOAuth2Sso
を使用すると、SpringはOAuth2RestTemplate
をアプリケーションコンテキストに配置します。このコンポーネントは、スレッドにバインドされたサーブレット関連のものを想定しています。
私のコードには、自動配線RestTemplate
を使用する非同期メソッドがスケジュールされています。これはDispatcherServlet
の内部では実行されていませんが、SpringはOAuth2RestTemplate
を挿入していたため、OPが記述するエラーが発生しました。
解決策は、名前ベースの注入を行うことでした。 Java構成:
@Bean
public RestTemplate pingRestTemplate() {
return new RestTemplate();
}
そしてそれを使用するクラスで:
@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;
これで、Springは意図したサーブレットフリーRestTemplate
を注入します。
プロトタイプを除き、デフォルトのシングルトンスコープとは異なるスコープが必要なBeanで定義する必要があります。例えば:
<bean id="shoppingCart"
class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
<aop:scoped-proxy/>
</bean>