Why is Spring's ApplicationContext.getBean considered bad?

Solution 1:

I mentioned this in a comment on the other question, but the whole idea of Inversion of Control is to have none of your classes know or care how they get the objects they depend on. This makes it easy to change what type of implementation of a given dependency you use at any time. It also makes the classes easy to test, as you can provide mock implementations of dependencies. Finally, it makes the classes simpler and more focused on their core responsibility.

Calling ApplicationContext.getBean() is not Inversion of Control! While it's still easy to change what implemenation is configured for the given bean name, the class now relies directly on Spring to provide that dependency and can't get it any other way. You can't just make your own mock implementation in a test class and pass that to it yourself. This basically defeats Spring's purpose as a dependency injection container.

Everywhere you want to say:

MyClass myClass = applicationContext.getBean("myClass");

you should instead, for example, declare a method:

public void setMyClass(MyClass myClass) {
   this.myClass = myClass;
}

And then in your configuration:

<bean id="myClass" class="MyClass">...</bean>

<bean id="myOtherClass" class="MyOtherClass">
   <property name="myClass" ref="myClass"/>
</bean>

Spring will then automatically inject myClass into myOtherClass.

Declare everything in this way, and at the root of it all have something like:

<bean id="myApplication" class="MyApplication">
   <property name="myCentralClass" ref="myCentralClass"/>
   <property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>

MyApplication is the most central class, and depends at least indirectly on every other service in your program. When bootstrapping, in your main method, you can call applicationContext.getBean("myApplication") but you should not need to call getBean() anywhere else!

Solution 2:

Reasons to prefer Service Locator over Inversion of Control (IoC) are:

  1. Service Locator is much, much easier for other people to following in your code. IoC is 'magic' but maintenance programmers must understand your convoluted Spring configurations and all the myriad of locations to figure out how you wired your objects.

  2. IoC is terrible for debugging configuration problems. In certain classes of applications the application will not start when misconfigured and you may not get a chance to step through what is going on with a debugger.

  3. IoC is primarily XML based (Annotations improve things but there is still a lot of XML out there). That means developers can't work on your program unless they know all the magic tags defined by Spring. It is not good enough to know Java anymore. This hinders less experience programmers (ie. it is actually poor design to use a more complicated solution when a simpler solution, such as Service Locator, will fulfill the same requirements). Plus, support for diagnosing XML problems is far weaker than support for Java problems.

  4. Dependency injection is more suited to larger programs. Most of the time the additional complexity is not worth it.

  5. Often Spring is used in case you "might want to change the implementation later". There are other ways of achieving this without the complexity of Spring IoC.

  6. For web applications (Java EE WARs) the Spring context is effectively bound at compile time (unless you want operators to grub around the context in the exploded war). You can make Spring use property files, but with servlets property files will need to be at a pre-determined location, which means you can't deploy multiple servlets of the same time on the same box. You can use Spring with JNDI to change properties at servlet startup time, but if you are using JNDI for administrator-modifiable parameters the need for Spring itself lessens (since JNDI is effectively a Service Locator).

  7. With Spring you can lose program Control if Spring is dispatching to your methods. This is convenient and works for many types of applications, but not all. You may need to control program flow when you need to create tasks (threads etc) during initialization or need modifiable resources that Spring didn't know about when the content was bound to your WAR.

Spring is very good for transaction management and has some advantages. It is just that IoC can be over-engineering in many situations and introduce unwarranted complexity for maintainers. Do not automatically use IoC without thinking of ways of not using it first.

Solution 3:

It's true that including the class in application-context.xml avoids the need to use getBean. However, even that is actually unnecessary. If you are writing a standalone application and you DON'T want to include your driver class in application-context.xml, you can use the following code to have Spring autowire the driver's dependencies:

public class AutowireThisDriver {

    private MySpringBean mySpringBean;    

    public static void main(String[] args) {
       AutowireThisDriver atd = new AutowireThisDriver(); //get instance

       ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                  "/WEB-INF/applicationContext.xml"); //get Spring context 

       //the magic: auto-wire the instance with all its dependencies:
       ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,
                  AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);        

       // code that uses mySpringBean ...
       mySpringBean.doStuff() // no need to instantiate - thanks to Spring
    }

    public void setMySpringBean(MySpringBean bean) {
       this.mySpringBean = bean;    
    }
}

I've needed to do this a couple of times when I have some sort of standalone class that needs to use some aspect of my app (eg for testing) but I don't want to include it in application-context because it is not actually part of the app. Note also that this avoids the need to look up the bean using a String name, which I've always thought was ugly.