The beginning of this discussion is purely a condensation of http://martinfowler.com/articles/injection.html.
"Now if I'm using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire for this wonderful functionality and would like a copy of my program? If they also store their movie listings in a colon delimited text file called movies1.txt then everything is wonderful. If they have a different name for their movies file, then it's easy to put the name of the file in a properties file. But what if they have a completely different form of storing their movie listing: a SQL database, an XML file, a web service, or just another format of text file? In this case we need a different class to grab that data. Now because I've defined a MovieFinder interface, this won't alter my moviesDirectedBy() method. But I still need to have some way to get an instance of the right finder implementation into place."
+---------------+ +---------------+ | <interface> | | MovieLister | -----------> | MovieFinder | +---------------+ +---------------+ | ^ | | | | | (creates) +-------------------+ +------------------> | MovieFinderImpl | +-------------------+
"The implementation class for the finder (MovieFinderImpl) isn't linked in at compile time since there's no way to know how friends are are going to use. Instead, a "plug-in" must be created. But how to make the link so that the lister class (MovieLister) can remain ignorant of the implementation class yet still talk to an instance to the work? Inversion of control (IoC)."
Code to consider in this discussion...
public interface MovieFinder { /** * The point of this example is that this method must be independent of how movies are * actually stored. */ public List< String > findAll(); } public class MovieLister { private MovieFinder finder; public MovieLister() { finder = new ColonDelimitedMovieFinder( "movies.txt" ); } /** * Finder object to return an array of movies by a specific director. */ public Movie[] moviesDirectedBy( String director ) { List allMovies = finder.findAll(); for( Iterator it = allMovies.iterator(); it.hasNext(); ) { // return every film known... Movie movie = ( Movie ) it.next(); // if the film was not directed by the specified director, remove it from the list if( !movie.getDirector().equals( directory ) ) it.remove(); } // make an array of these movies directed by the specified director return ( Movie[] ) allMovies.toArray( new Movie[ allMovies.size() ] ); } }
In a user interface, the control of the program is "inverted" in that it's moved away from the caller to the framework. In our example, we obviously cannot instantiate the finder implementation directly because that would have to occur during compilation. A consumer would have to follow a convention that allows a separate assembler module to "inject" the implementation into the lister.
So, "inversion of control" is too generic a term and confusing in this discussion. A better one, one that many are using today, is "dependency injection."
There are three main types of dependency injection, interface injection, setter injection and constructor injection. (In the literature, you often see these respectively as type 1 injection, type 2 injection and type 3 injection.)
This type uses a constructor to decide how to inject a finder implementation into the lister class. The requirement is for the lister class to define a constructor that includes everything it needs injected in order to get the work done.
public class MovieLister { public MovieLister( MovieFinder finder ) { this.finder = finder; } }
And, the finder itself must have the filename containing the list injected.
class ColonMovieFinder { private String filename; public ColonMovieFinder( String filename ) { this.filename = filename; } }
The container must be told which implementation class to associate with each interface, and which string to inject into the finder.
class MutablePicoContainer { private MutablePicoContainer configureContainer() { MutablePicoContainer pico = new DefaultPicoContainer(); Parameter[] finderParams = { new ConstantParameter( "movies1.txt" ) }; pico.registerComponentImplementation( MovieFinder.class, ColonMovieFinder.class, finderParams ); pico.registerComponentImplementation( MovieLister.class ); return pico; } }
The configuration code is typically set up in a separate class. For this example, each friend who uses the lister might write the appropriate configuration code in some set-up class of his own. It's common to hold this sort of configuration information in separate files. you can write a class to read a config file and set up the container appropriately. Such a container would parse, e.g.:, XML and then configure an underlying container. The goal here is to separate the configuration file format from the underlying mechanism.
public TestMutablePicoContainer { public void testWithPico() { MutablePicoContainer pico = configureContainer(); MovieLister lister = ( MovieLister ) pico.getComponentInstance( MovieLister.class ); Movie[] movies = lister.moviesDirectedBy( "Sergio Leone" ); assertEquals( "Once Upon a Time in the West", movies[0].getTitle() ); } }
This is constructor injection.
Spring supports both constructor and setter injection, but Spring developers tend to favor setter injection.
To get the movie lister to accept injection, define a setting method for that service:
public class MovieLister { private MovieFinder finder; public void setFinder( MovieFinder finder ) { this.finder = finder; } }
Define a setter for the filename.
public class ColonMovieFinder { public void setFilename( String filename ) { this.filename = filename; } }
The third step is to set up the configuration for the files. Spring supports configuration through XML files and also through annotation, but we'll use XML here.
<beans> <bean id="MovieLister" class="spring.MovieLister"> <property name="finder"> <ref local="MovieFinder" /> </property> </bean> <bean id="MovieFinder" class="spring.ColonMovieFinder"> <property name="filename"> <value>movies1.txt</value> </property> </bean> </beans>
The test then looks like this.
public TestMutablePicoContainer { public void testWithSpring() throws Exception { ApplicationContext ctx = new FileSystemXmlApplicationContext( "spring.xml" ); MovieLister lister = ( MovieLister ) ctx.getBean( "MovieLister" ); Movie[] movies = lister.moviesDirectedBy( "Sergio Leone" ); assertEquals( "Once Upon a Time in the West", movies[ 0 ].getTitle() ); } }
org.springframework.beans.factory.BeanInitializationException: Property 'person' is required for bean 'CustomerBean'