views:

586

answers:

8

We have some domain objects that are created at runtime - not by Spring. These domain objects need access to some service type beans that are being managed by Spring. How can the domain objects that are created at runtime access Spring beans dynamically (not by DI)?

+6  A: 

You'd have to give them a reference to the ApplicationContext or BeanFactory so they could get the Spring-managed beans.

duffymo
What's the best practice for doing this in a web app? Is it common that dynamically created domain objects need access to Spring beans in this manner?
Marcus
I'd recommend looking at WebApplicationContextUtils: http://static.springsource.org/spring/docs/3.0.0.RC1/javadoc-api/. I think it's more common to let Spring manage the beans. Only local beans inside a method call context would be created using "new".
duffymo
A: 

You could use the approach that @duffymo suggested but in the event that you are not running Spring as a web app you should look at this SO entry. See that the utility class is made thread safe in the most popular answer. Btw the OP and the answer you should get all you need and then you can use this utility class to get a reference to the Spring managed beans.

non sequitor
+5  A: 

@duffymo's answer is the most common solution to this problem, and the one you should probably follow.

However, if you're feeling saucy, and if your situation supports it, then you could consider using Spring's AspectJ support to autowire your non-spring-managed domain objects with spring beans:

[...] contains an annotation-driven aspect that exploits this capability to allow dependency injection of any object. The support is intended to be used for objects created outside of the control of any container. Domain objects often fall into this category because they are often created programmatically using the new operator, or by an ORM tool as a result of a database query.

It's verging on voodoo, this stuff, and it only works on certain appservers, but it might be the tool for you.

skaffman
+2  A: 

Spring has a mechanism called the SingletonBeanFactoryLocator that you can use in places, such as EJB 2.0 applications, to get the bean factory/application context in places where you can't use dependency injection. There's a hook in the existing Spring ContextLoader that you're already using to take advantage of this functionality, though it's somewhat tricky to setup.

You'll need to separate out your application contexts into a parent/child relationship. The parent will contain the service layer objects, while the child is composed of the web-layer specific stuff.

Then you'll have to add a couple of context parameters to your web.xml (like you do for the config location) to tell it to initialize the parent:

<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>

The locatorFactorySelector is a reference to an xml file, BUT (this is where I always get confused) this isn't going to point to the xml that defines your services. It's a bean definition xml that creates an application context bean. That you then reference this bean using the parentContextKey attribute.

So for example, beanRefFactory.xml would then contain:

<beans>
    <bean id="beanRefFactory"
         class="org.springframework.context.support.ClassPathXmlApplicationContext">
        <constructor-arg>
           <list>
                <value>service-context.xml</value>
           </list>
        </constructor-arg>
    </bean>
</beans>

In your non-DIed domain objects, you could then get to the application context with this code:

   BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
   BeanFactoryReference contextRef= locator.useBeanFactory(parentContextKey);
   ApplicatonContext context = (ApplicationContext) contextRef.getFactory();

You can find more information on ContextSingletonBeanFactoryLocator in this blog post. There's also a good description of using this approach in the chapter on EJBs in Java Development with the Spring Framework.

Jason Gritman
A: 

One solution is to use a global static method that returns the Spring application contest (see BeanLocator.)

Other options could be to have your business objects implement the ApplicationContextAware interface. At instantiation, your "integration" code would have to inject the Spring ApplicationContext into the dynamically created bean (maybe by checking if the class implements ApplicationContextAware.) This of course would tie your business code with Spring, but the first option would be the same.

A variation would be to not inject the ApplicationContext directly but reuse the Spring @Autowired annotation. The "integration" code would then inject only the annotated fields.

Vladimir
+1  A: 

Create a Factory and register it with spring, use the factory to create domain object rather than using 'new'

in this case, you have all the goodies available to your DomainObjFactory

Dapeng
A: 

Slightly related question

You could adopt a similar strategy to Hibernate, creating facilities for interceptors in you domain instance factory. You can inject the required services into spring managed interceptors, which are injected into your domain factories

This would untie your application from Spring specific interfaces. The below example could be simplified with generics, but you should get the idea.

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);
  }
}

Then you would configure it something like

<bean id="myServiceInjector" class="MyServiceInjector">
  <property name="myService" ref="someServiceBean" />
</bean>

<bean class="DomainFactory">
  <property name="interceptors">
    <list>
      <ref bean="myServiceInjector"/>
    </list>
  </property>
</bean>
ptomli
A: 

You could make the dependent object a singleton with a static getInstance() method that could be used by the non-Spring managed domain objects. You would then make it available to Spring via org.springframework.beans.factory.config.MethodInvokingFactoryBean like:

<bean id="myObject"
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod">
        <value>com.example.MyObject.getInstance</value>
    </property>
</bean>
Paul Croarkin