【发布时间】:2009-11-28 22:40:06
【问题描述】:
我们有一些在运行时创建的域对象——不是由 Spring 创建的。这些域对象需要访问一些由 Spring 管理的服务类型 bean。在运行时创建的域对象如何动态访问 Spring bean(而不是通过 DI)?
【问题讨论】:
标签: java spring dependency-injection
我们有一些在运行时创建的域对象——不是由 Spring 创建的。这些域对象需要访问一些由 Spring 管理的服务类型 bean。在运行时创建的域对象如何动态访问 Spring bean(而不是通过 DI)?
【问题讨论】:
标签: java spring dependency-injection
@duffymo 的回答是这个问题最常见的解决方案,也是你应该遵循的。
但是,如果你觉得很有趣,并且你的情况支持它,那么你可以考虑使用 Spring 的 AspectJ 支持到 autowire your non-spring-managed domain objects 和 spring bean:
[...] 包含一个注解驱动的 利用这种能力的方面 允许任何依赖注入 目的。该支持旨在 用于在外部创建的对象 任何容器的控制。领域 对象通常属于这一类 因为它们经常被创建 以编程方式使用新的 运算符,或通过 ORM 工具作为 数据库查询的结果。
它正在接近巫毒,这种东西,它只适用于某些应用服务器,但它可能是适合你的工具。
【讨论】:
您必须为他们提供对 ApplicationContext 或 BeanFactory 的引用,以便他们可以获得 Spring 管理的 bean。
【讨论】:
Spring 有一种称为SingletonBeanFactoryLocator 的机制,您可以在诸如 EJB 2.0 应用程序之类的地方使用该机制,以便在无法使用依赖注入的地方获取 bean 工厂/应用程序上下文。现有 Spring ContextLoader 中有一个钩子,您已经在使用它来利用此功能,尽管设置起来有些棘手。
您需要将应用程序上下文分离为父/子关系。父级将包含服务层对象,而子级则由 Web 层特定的东西组成。
然后您必须在 web.xml 中添加几个上下文参数(就像您为配置位置所做的那样)来告诉它初始化父级:
<context-param>
<param-name>locatorFactorySelector</param-name>
<param-value>classpath:beanRefFactory.xml</param-value>
</context-param>
<context-param>
<param-name>parentContextKey</param-name>
<param-value>beanRefFactory</param-value>
</context-param>
locatorFactorySelector 是对 xml 文件的引用,但是(我总是对此感到困惑)这不会指向定义您的服务的 xml。它是一个创建应用程序上下文 bean 的 bean 定义 xml。然后使用parentContextKey 属性引用这个bean。
例如,beanRefFactory.xml 将包含:
<beans>
<bean id="beanRefFactory"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>service-context.xml</value>
</list>
</constructor-arg>
</bean>
</beans>
在您的非 DIed 域对象中,您可以使用以下代码进入应用程序上下文:
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
BeanFactoryReference contextRef= locator.useBeanFactory(parentContextKey);
ApplicatonContext context = (ApplicationContext) contextRef.getFactory();
您可以在this blog post 中找到有关ContextSingletonBeanFactoryLocator 的更多信息。在Java Development with the Spring Framework 的 EJB 章节中也对使用这种方法进行了很好的描述。
【讨论】:
创建工厂并注册到spring,使用工厂创建域对象而不是使用'new'
在这种情况下,您的 DomainObjFactory 拥有所有可用的东西
【讨论】:
略相关question
您可以采用与 Hibernate 类似的策略,在您的域实例工厂中为拦截器创建设施。您可以将所需的服务注入到 Spring 托管的拦截器中,这些拦截器将注入到您的域工厂中
这将使您的应用程序与 Spring 特定接口解开。下面的例子可以用泛型来简化,但你应该明白了。
public interface Interceptor {
public void onCreate(Object entity);
}
public class DomainFactory {
public void setInterceptors(List<Interceptor> interceptors) { ... }
public Object createInstance() {
// iterate interceptors, call onCreate
}
}
public interface MyServiceAware {
public void setMyService(MyService service);
}
public class MyServiceInjector implements Interceptor {
private MyService myService;
public void onCreate(Object entity) {
if (entity instanceof MyServiceAware)
((MyServiceAware) entity).setMyService(myService);
}
}
然后你会像这样配置它
<bean id="myServiceInjector" class="MyServiceInjector">
<property name="myService" ref="someServiceBean" />
</bean>
<bean class="DomainFactory">
<property name="interceptors">
<list>
<ref bean="myServiceInjector"/>
</list>
</property>
</bean>
【讨论】:
您可以使用@duffymo 建议的方法,但如果您没有将 Spring 作为 Web 应用程序运行,您应该查看此 SO entry。看到实用程序类在最流行的答案中是线程安全的。顺便说一句,你应该得到所有你需要的 OP 和答案,然后你可以使用这个实用程序类来获取对 Spring 托管 bean 的引用。
【讨论】:
一种解决方案是使用返回 Spring 应用程序竞赛的全局 static 方法(请参阅 BeanLocator。)
其他选项可能是让您的业务对象实现ApplicationContextAware 接口。在实例化时,您的“集成”代码必须将 Spring ApplicationContext 注入动态创建的 bean(可能通过检查该类是否实现 ApplicationContextAware。)这当然会将您的业务代码与 Spring 绑定,但第一个选项会是一样的。
一种变体是不直接注入ApplicationContext,而是重用Spring @Autowired 注解。然后,“集成”代码将只注入带注释的字段。
【讨论】:
您可以使用静态 getInstance() 方法使依赖对象成为单例,非 Spring 托管域对象可以使用该方法。然后,您可以通过 org.springframework.beans.factory.config.MethodInvokingFactoryBean 使其对 Spring 可用,例如:
<bean id="myObject"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod">
<value>com.example.MyObject.getInstance</value>
</property>
</bean>
【讨论】: