Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting rid of getBean in standalone java Spring spring app (Non web app, no container)

Tags:

java

spring

In web applications we don't really need to do ..

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
ctx.getBean("beanId");

because the general practice is to load the context files and inject all the beans with dependencies using ContextLoaderServlet in web.xml like this..

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring-context.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- or use the ContextLoaderServlet instead of the above listener
<servlet>
  <servlet-name>context</servlet-name>
  <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
--> 

However in a standalone java app with no container, I end up doing ctx.getBean("xyz");. Is there a clean way to do this, could not find an example online.

I checked .. Simple Spring, use of ClasspathApplicationContext for standalone apps, how to reuse? , which talks about using SingletonBeanFactoryLocator, but it ultimately uses context.getBean().

I also looked at ServiceLocatorFactoryBean, but thats again getting beans on demand thru using proxy.

I am looking for a solution to load context file (all beans) from the main() program of my standalone java app so that I dont want to get beans on demand.

Example Code:

public interface IReader {
    public String read();
}

public class TextFileReader implements IReader {

    private StringBuilder builder = null;
    private Scanner scanner = null;

    public TextFileReader(String fileName) throws FileNotFoundException {
        scanner = new Scanner(new File(fileName));
        builder = new StringBuilder();
    }

    public String read() {
        while (scanner.hasNext()) {
            builder.append(scanner.next());
            builder.append(",");
        }
        return builder.toString();
    }
}



 public class SpringNoConextDataReaderClient {

    private IReader reader = null;

    public void setReader(TextFileReader reader) {
        this.reader = reader;
    }

    private String fetchDataOne() {
        return reader.read();
    }

    private String fetchDataTwo() {
        return reader.read();
    }

    public static void main(String[] args) {

        final ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        String fetchedData = context.getBean(SpringNoConextDataReaderClient.class).fetchDataOne(); // <-- reader is injected as TextFileReader in fetchDataOne which reads the file

        SpringNoConextDataReaderClient client = new SpringNoConextDataReaderClient();
        client.fetchDataOne(); // <--  reader is null and throws NPE, probably its lifetime ended with previous call?

        System.out.println("Example 1.1: Got data without context: " + fetchDataOne);
    }

}

spring-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="reader" class="com.himalay.spring.core.basic.readers.TextFileReader">
        <constructor-arg value="src/main/resources/data.txt" />
    </bean>

    <bean id="springNoConextDataReaderClient" class="com.himalay.spring.core.basic.SpringNoConextDataReaderClient">
        <property name="reader"><ref bean = "reader" /></property>
    </bean>

</beans>

Thank you.

like image 893
Himalay Majumdar Avatar asked Aug 15 '12 14:08

Himalay Majumdar


1 Answers

In a standalone application you need to create an instance of ApplicationContext yourself and use it to load at least one bean. But the one bean you load can use all the Spring magic like @Autowired etc. and doesn't need to use getBean any more. So you can have one bootstrap bean that you load using getBean and then let this one bean do everything else. Something like:

@Component
public class Main
{
    @Autowired
    protected MyDependencyClass2 someClass1;
    @Autowired
    protected MyDependencyClass2 someClass2;
    // ...
    // or if you need an entity manager
    @PersistenceContext
    protected EntityManager em;
    // etc.

    protected void mainInternal(String[] args)
        throws Exception
    {
        // do everything here
        // all dependencies are initialized
        // ...
    }

    public static void main(String[] args)
        throws Exception
{
        // Bootstrap Spring and let it create and configure beans.
        final ApplicationContext context =
            new ClassPathXmlApplicationContext("spring-context.xml");
        context.getBean(Main.class).mainInternal(args);
    }
}

Note: Generally it's safer to use the variants of getBean(Class) or getBean(String,Class) that take a Class<T> parameter.


If you just call new Main(), the dependencies won't get initialized. Spring doesn't know about instances you create using new, only about instances it creates itself. This is a key concept to Spring. Not only it creates an instance of the class, it manages its dependencies with other beans, can process the created instance using aspects etc. This is not possible on an instance you create yourself using new.

The point here is that if you move all your code from main to mainInternal, all your required dependencies will be initialized. Not only Main, but also its dependencies, their dependencies etc. etc. So if your application is constructed properly using Spring and if it only manages dependencies using Spring features (such as @Autowired) then you'll get an environment similar to the one you have in a web application.

So in this case, the proper procedure is: Make all beans that are required for application startup dependencies of Main. They'll get initialized together with all their dependencies, and you can safely use them in mainInternal or whatever it calls.


Edit: Commenting your example. As I explained, Spring only manages objects that it creates, not objects that you create using new. In your example, you use

SpringNoConextDataReaderClient client = new SpringNoConextDataReaderClient();

so client won't be managed by Spring, and no its dependencies will be set or resolved. Think of it this way: If you create an object yourself, how can Spring know about it?

Also, your example is not well designed. The main idea of Spring is to manage program components and wire them together usin Inversion of control principle. In majority of cases such program components are meant to be singleton objects that exist during the whole lifetime of an application. (It's also possible to have components with a shorter life span, such as one HTTP request or one HTTP session scope but this is outside the scope of this question.) The important part is that such singleton components should not change their internal state once they're initialized.

On the other hand, Spring is not meant to manage your data objects, such as a IReader. IReader isn't a component of a program, it's an object you create, read from a file and dispose afterwards. A better design would be:

  • Have a singleton bean that provides you an IReader on demand, like

    public class TextFileReaderProvider {
        public IReader createReader() { ... }
    }
    
  • Wire this provider into SpringNoConextDataReaderClient like

    public class SpringNoConextDataReaderClient {
        @Autowired
        protected TextFileReaderProvider readerProvider;
    
        public SomeResult doMyComputation() {
            IReader r = readerProvider.createReader();
            try {
                // compute the result
                return theResult;
            } finally {
                r.close();
            }
        }
    }
    

    (or instead of @Autowired configure the dependency manually in XML).

  • In main, let Spring get you an instance of SpringNoConextDataReaderClient and call doMyComputation() on this.

Such design leads you to separate your software into distinct components that can be reused across the whole application, and without having concurrency issues.

like image 179
Petr Avatar answered Oct 25 '22 02:10

Petr