SpringアプリケーションでApplicationContextのコピーを静的/グローバルに要求する方法はありますか?
メインクラスが起動してアプリケーションコンテキストを初期化すると仮定すると、それを必要とするクラスにコールスタックを介して渡す必要がありますか。それとも、クラスが以前に作成したコンテキストを要求する方法はありますか。 (どれがシングルトンでなければならないと思いますか?)
コンテナへのアクセスが必要なオブジェクトがコンテナ内のBeanの場合は、 BeanFactoryAware または ApplicationContextAware インタフェースを実装するだけです。
コンテナの外側のオブジェクトがコンテナにアクセスする必要がある場合は、Springコンテナに 標準GoFシングルトンパターン を使用しました。そうすることで、アプリケーションにはシングルトンが1つしかなく、残りはすべてコンテナ内のシングルトンBeanになります。
ApplicationContextAware
を実装することも、単に@Autowired
を使用することもできます。
public class SpringBean {
@Autowired
private ApplicationContext appContext;
}
SpringBean
はApplicationContext
をインジェクトし、その中でこのBeanがインスタンス化されます。たとえば、かなり標準的なコンテキスト階層を持つWebアプリケーションがあるとします。
main application context <- (child) MVC context
そしてSpringBean
はメインコンテキスト内で宣言され、メインコンテキストがインジェクトされます。そうでなければ、MVCコンテキスト内で宣言されていれば、MVCコンテキストが注入されます。
これがいい方法です(私のものではなく、元の参照はこちらです: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html
私はこの方法を使いましたが、うまくいきます。基本的には、アプリケーションコンテキストへの(静的)参照を保持する単純なBeanです。 spring configでそれを参照することで初期化されます。
元の参照を見てください、それは非常に明確です。
SingletonBeanFactoryLocator を使うことができると思います。 beanRefFactory.xmlファイルは実際のapplicationContextを保持します。これは次のようになります。
<bean id="mainContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>../applicationContext.xml</value>
</list>
</constructor-arg>
</bean>
そして、どこからでもapplicationcontextからbeanを取得するコードは次のようになります。
BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bf = bfl.useBeanFactory("mainContext");
SomeService someService = (SomeService) bf.getFactory().getBean("someService");
Springチームはこのクラスとyadayadaの使用を推奨していますが、それを使用したところでは私には適しています。
あなたが他の提案のどれかを実行する前に、あなた自身にこれらの質問をしてください...
これらの質問に対する答えは、特定の種類のアプリケーション(Webアプリケーションなど)では他のアプリケーションよりも簡単ですが、とにかく尋ねる価値があります。
ApplicationContextにアクセスすると、依存性注入の原則全体に違反するようになりますが、選択できない場合があります。
Webアプリケーションを使用する場合は、servletfilterとThreadLocalを使用して、シングルトンを使用せずにアプリケーションコンテキストにアクセスする別の方法もあります。フィルタでは、WebApplicationContextUtilsを使用してアプリケーションコンテキストにアクセスし、アプリケーションコンテキストまたは必要なBeanのいずれかをTheadLocalに格納できます。
注意:ThreadLocalを設定解除するのを忘れた場合、アプリケーションをアンデプロイしようとすると厄介な問題が発生します。したがって、それを設定し、すぐにfinally部のThreadLocalの設定を解除する試行を開始する必要があります。
もちろん、これはまだシングルトン、ThreadLocalを使用しています。しかし、実際の豆はもう必要ではありません。これはリクエストスコープでも可能で、EAR内のライブラリと一緒にアプリケーション内に複数のWARがある場合にもこのソリューションは機能します。それでも、このThreadLocalの使用は、プレーンシングルトンの使用と同じくらい悪いと考えるかもしれません。 ;-)
おそらく、Springはすでに同じような解決策を提供しているのでしょうか。私はそれを見つけられませんでした、しかし私は確かに知りません。
SpringApplicationContext.Java
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Wrapper to always return a reference to the Spring Application
Context from
* within non-Spring enabled beans. Unlike Spring MVC's
WebApplicationContextUtils
* we do not need a reference to the Servlet context for this. All we need is
* for this bean to be initialized during application startup.
*/
public class SpringApplicationContext implements
ApplicationContextAware {
private static ApplicationContext CONTEXT;
/**
* This method is called from within the ApplicationContext once it is
* done starting up, it will stick a reference to itself into this bean.
* @param context a reference to the ApplicationContext.
*/
public void setApplicationContext(ApplicationContext context) throws BeansException {
CONTEXT = context;
}
/**
* This is about the same as context.getBean("beanName"), except it has its
* own static handle to the Spring context, so calling this method statically
* will give access to the beans by name in the Spring application context.
* As in the context.getBean("beanName") call, the caller must cast to the
* appropriate target class. If the bean does not exist, then a Runtime error
* will be thrown.
* @param beanName the name of the bean to get.
* @return an Object reference to the named bean.
*/
public static Object getBean(String beanName) {
return CONTEXT.getBean(beanName);
}
}
ソース: http://sujitpal.blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html
ContextSingletonBeanFactoryLocator を見てください。特定の方法で登録されていると仮定して、Springのコンテキストを取得するための静的アクセサを提供します。
それはきれいではなく、おそらくあなたが望むよりも複雑ではありませんが、うまくいきます。
例えばシングルトンパターンを使用して、現在のApplicationContext
、またはApplicationContext
自体の状態を静的変数に格納することで、Spring-testを使用している場合はテストが不安定になり予測不能になることに注意してください。これは、Spring-testが同じJVM内でアプリケーションコンテキストをキャッシュして再利用するためです。例えば:
@ContextConfiguration({"classpath:foo.xml"})
のアノテーションを付けます。@ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
という注釈が付けられます@ContextConfiguration({"classpath:foo.xml"})
の注釈を付けますテストAが実行されると、ApplicationContext
が作成され、ApplicationContextAware
または自動配線ApplicationContext
を実装するすべてのBeanが静的変数に書き込む可能性があります。
テストBを実行すると同じことが起こり、静的変数はテストBのApplicationContext
を指すようになります。
テストCが実行されると、テストAからのTestContext
(およびここではApplicationContext
)としてのBeanは作成されませんが再利用されます。これで、テスト用のBeanを現在保持しているものとは別のApplicationContext
を指す静的変数が手に入りました。
Springアプリケーションでアプリケーションコンテキストを取得する方法はたくさんあります。それらは以下の通りです:
ApplicationContextAware経由:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class AppContextProvider implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
ここでsetApplicationContext(ApplicationContext applicationContext)
メソッドはapplicationContextを取得します
ApplicationContextAware:
実行中のApplicationContextの通知を受けることを希望する任意のオブジェクトによって実装されるインタフェース。このインタフェースの実装は、たとえばオブジェクトが一連の共同作業するBeanへのアクセスを必要とする場合には意味があります。
Autowired経由:
@Autowired
private ApplicationContext applicationContext;
ここで@Autowired
キーワードはapplicationContextを提供します。 Autowiredにはいくつか問題があります。単体テスト中に問題が発生します。
これがどれほど役立つかは定かではありませんが、アプリを初期化するときにコンテキストを取得することもできます。 @Autowire
の前であっても、これがコンテキストを取得できる最も早い方法です。
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
private static ApplicationContext context;
// I believe this only runs during an embedded Tomcat with `mvn spring-boot:run`.
// I don't believe it runs when deploying to Tomcat on AWS.
public static void main(String[] args) {
context = SpringApplication.run(Application.class, args);
DataSource dataSource = context.getBean(javax.sql.DataSource.class);
Logger.getLogger("Application").info("DATASOURCE = " + dataSource);
以下のようにSpring Beanで自動配線を行います。@Autowired private ApplicationContext appContext;
あなたはapplicationcontextオブジェクトになります。
この質問に答えたことは知っていますが、Spring Contextを取得するために行ったKotlinコードを共有したいと思います。
私はスペシャリストではないので、批評家、レビュー、助言を受け入れます。
https://Gist.github.com/edpichler/9e22309a86b97dbd4cb1ffe011aa69dd
package com.company.web.spring
import com.company.jpa.spring.MyBusinessAppConfig
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.stereotype.Component
import org.springframework.web.context.ContextLoader
import org.springframework.web.context.WebApplicationContext
import org.springframework.web.context.support.WebApplicationContextUtils
import javax.servlet.http.HttpServlet
@Configuration
@Import(value = [MyBusinessAppConfig::class])
@ComponentScan(basePackageClasses = [SpringUtils::class])
open class WebAppConfig {
}
/**
*
* Singleton object to create (only if necessary), return and reuse a Spring Application Context.
*
* When you instantiates a class by yourself, spring context does not autowire its properties, but you can wire by yourself.
* This class helps to find a context or create a new one, so you can wire properties inside objects that are not
* created by Spring (e.g.: Servlets, usually created by the web server).
*
* Sometimes a SpringContext is created inside jUnit tests, or in the application server, or just manually. Independent
* where it was created, I recommend you to configure your spring configuration to scan this SpringUtils package, so the 'springAppContext'
* property will be used and autowired at the SpringUtils object the start of your spring context, and you will have just one instance of spring context public available.
*
*Ps: Even if your spring configuration doesn't include the SpringUtils @Component, it will works tto, but it will create a second Spring Context o your application.
*/
@Component
object SpringUtils {
var springAppContext: ApplicationContext? = null
@Autowired
set(value) {
field = value
}
/**
* Tries to find and reuse the Application Spring Context. If none found, creates one and save for reuse.
* @return returns a Spring Context.
*/
fun ctx(): ApplicationContext {
if (springAppContext!= null) {
println("achou")
return springAppContext as ApplicationContext;
}
//springcontext not autowired. Trying to find on the thread...
val webContext = ContextLoader.getCurrentWebApplicationContext()
if (webContext != null) {
springAppContext = webContext;
println("achou no servidor")
return springAppContext as WebApplicationContext;
}
println("nao achou, vai criar")
//None spring context found. Start creating a new one...
val applicationContext = AnnotationConfigApplicationContext ( WebAppConfig::class.Java )
//saving the context for reusing next time
springAppContext = applicationContext
return applicationContext
}
/**
* @return a Spring context of the WebApplication.
* @param createNewWhenNotFound when true, creates a new Spring Context to return, when no one found in the ServletContext.
* @param httpServlet the @WebServlet.
*/
fun ctx(httpServlet: HttpServlet, createNewWhenNotFound: Boolean): ApplicationContext {
try {
val webContext = WebApplicationContextUtils.findWebApplicationContext(httpServlet.servletContext)
if (webContext != null) {
return webContext
}
if (createNewWhenNotFound) {
//creates a new one
return ctx()
} else {
throw NullPointerException("Cannot found a Spring Application Context.");
}
}catch (er: IllegalStateException){
if (createNewWhenNotFound) {
//creates a new one
return ctx()
}
throw er;
}
}
}
現在、スプリングコンテキストが公開されており、このJavaサーブレットのように、コンテキストとは無関係に同じメソッド(junitテスト、Bean、手動でインスタンス化されたクラス)を呼び出すことができます。
@WebServlet(name = "MyWebHook", value = "/WebHook")
public class MyWebServlet extends HttpServlet {
private MyBean byBean
= SpringUtils.INSTANCE.ctx(this, true).getBean(MyBean.class);
public MyWebServlet() {
}
}
その点に注意してください;以下のコードは、既にロードされているものを使用する代わりに、新しいアプリケーションコンテキストを作成します。
private static final ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
また、beans.xml
はsrc/main/resources
の一部である必要があることに注意してください。これは、実際のアプリケーションはWEB_INF/classes
で言及したapplicationContext.xml
を介してロードされるため、戦争ではWeb.xml
の一部です。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>
ClassPathXmlApplicationContext
コンストラクターでapplicationContext.xml
パスを指定するのは difficult です。 ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")
はファイルを見つけることができません。
したがって、アノテーションを使用して既存のapplicationContextを使用することをお勧めします。
@Component
public class OperatorRequestHandlerFactory {
public static ApplicationContext context;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
}