How does Spring work I?

11 Aug

One thing I really hate is believing in magic when writing software. It may be possible that we just don’t understand some things, but it is not magic, they are just some things we not know (yet). Just google for “Programming by Coincidence” for more information smiley

There are some frameworks which I often hear about they are doing magic. And I admit, most of them are really not easy to understand. One example is Spring. How can we overcome this? Well, we could read the source. This is actually a very good approach most of the time.  But some Frameworks are just … too complex. So let’s try something different: we implement a very small Dependency Injection (DI) Framework in the style of Spring. This brings us the opportunity to explore the core idea behind Spring. In the first part, we use an XML configuration for this (let’s try Annotations some time in Part 2!). What we want to learn is: how can we build an object graph from an XML file. A word of warning: what we’re doing here is very simple stuff that just explains how things work: We skip things like transaction support etc. But perhaps the first steps are enough to get an idea was is possible and what is not… So let’s start.

I shared the whole project on github. I will talk about the important files here, but have a look at github to see how these files are organized and what glue-code exists.

The Scenario

The first thing we should decide is how the XML file should look. We want to support beans with ids and classes. Beans can have simple values (such as <tt>int</tt>s or Strings) or other beans as property values. The XML schema behind this is very easy:

Okay, now let’s write two beans and a bean definition file:

 

We have both kinds of properties here: simple values and references (they are even circular!). Next we create an acceptance test to define a few expectations (there are more tests on github):

And this is the mentioned Intefaces DIContext:

Well, that really looks like Spring, doesn’t it?

Step 1: Read the XML File

So far, there shouldn’t be anything surprising. The funny stuff is still ahead. Now let’s dive into the real DI work. As this is the really interesting part, we go through it a littlemore in detail. The idea is to build up our object graph in four steps:

  1. Read the provided XML file
  2. Create an object for every specified bean – do nothing with it
  3. Iterate over all beans again: resolve all properties
  4. Call some kind of init-Method

The first step should be clear. But the rest is not that obvious, so let’s go through all of them one by one.

The first question which arises is: how do we parse the XML file. There a several aproaches. The example project is a maven Project, so let’s use jaxb; this will cost us only a single line of code. We tell Maven to generate the model classes for us and use JAXB.unmarshal to load an XML file. This is the pom snippet:

This is the beginning of our DIContext class:

We ignore the DIInitializationException for now, it is just a class which extends RuntimeException, see the github project for more details. JAXB is used to read our XML file. But where does the class Beans come from? This is the place where the defined maven plugin kicks in: in generates the classes Beans, TBean and TProperty from the XSD file (you can run “mvn generate-sources” to generate the files; otherwise maven will generate them, if they are needed). This code is exactly what the first step’s task was: read the XML file.

Step 2: Create the Bean Objects

After we have objects which represent our bean definitions, let’s create the real beans. This is the second item in our task list (this is the continuation the DIXmlContext class):

We extract the responsibility of creating the bean into an own class, but it is very small:

What happens here? The for-Loop reads our list of Beans. Every bean has an id and an class attribute. We have a Map named beans which holds all our beans indexed by id. So we create a bean and put it into the map. How is the bean created? By using java reflections! Class.forName takes a String as argument and returns the corrosponding Class object. This object can be used do dive into the class: we can enumerate the fields and methods, and we can call them! For the moment, we only need newInstance (but we will come back to the Class object in the next step). newInstance call the default constructor of a class. If no default constructor exists, we throw an exception. As we have no idea what we are dealing with, all Beans are Objects.

If we take the above bean definition, this code does the following: it finds two beans, one with the id “bean1” and the class “ de.jowisoftware.myDI.integrationtests.test2.Class1”, and one with “bean2” and the class “ de.jowisoftware.myDI.integrationtests.test2.Class2”. It looks up a class “ de.jowisoftware.myDI.integrationtests.test2.Class1” and creates a new instance of it. Then the instance is stored as “bean1”.  The same happens for “bean2”.

At this moment, the beans are not initialized. There is a reason for this: we have no other way to resolve circular dependencies. bean1 can only be initialized when bean2 is created, and bean2 depends on bean1. If the definition of the beans would be a directed acyclic graph (DAG), then we could initialize the beans bottom up, but we use a simpler approach here. When we now fill the properties of bean1, we can inject a reference to bean2 – which is not initialized yet, but at least it exists. That’s enough for the moment, so we inject it and finish initializing bean1. Then we initialize bean2 and add a reference to the already initialized bean1. At the end, we have both beans initialized.

Go fetch yourself a coffee. Read the article again up to this paragraph. Download the project, start it, debug it up to the call to secondPassInit. Get a feeling what happened so far before reading further.

 

Step 3: Initialize the Beans

So far, we are able to read an XML file and use reflections to create the java objects by their class name. We store them in a map. If you don’t feel insecure, go ahead with this new method in DIXmlContxt:

Another iteration initializes the bean. What does BeanInitializer do? Let’s have a look:

To initialize a bean, we iterate over its properties. First we check that exactly one xml attribute is set: value or ref (we will see the code in a second). Then we search a setter method to inject the value into the bean. Next, we calculate the value to be set. The last step is a call to the setter. Let’s go through the code method by method:

Well, this was simple and only helps us debugging our context. Let’s talk about findSetter:

Let’s assume we have the property “myProperty”. We are looking for a Method which:

  • has the name setMyProperty
  • has no return value (i.e. it is a void method)
  • has exactly one argument

Here, we use our Class-object again. We do not know the type of the first argument so we look through all methods. You may notice that we will run into a problem if there are two methods setMyProperty(String) an setMyProperty(int) – and you’re right. We ignoring this problem for now; it would perhaps be nicer to throw an exceptions if two methods with the same name exists, so be brave and add this functionality if you play with your own project wink

We now have our setter method – again only by looking it up by reflection. Now we need the value. This is much code, sorry

There are two cases: either we have a reference to another bean or we have a value. It may surprise you, but this is the smaller part. If we have a value then… well, we need to transform the value into the right object. We do this by inspecting the setter’s parameter type: is it int or Integer? Then use Integer.parseIt and return an Integer(java’s autoboxing feature does this for us, as we return an Object. There is no need for a call to Integer.valueOf, this is implicitly done here). Is it a Float? Then… okay, just read the code smiley

We do now have a setter method and an object. The object is either a bean or a specified value – we don’t care anymore, it’s just the right object. Now comes the last part: inject the object.

As a method is associated with a class (and not with a specific object) we need to pass the object and the parameters to its invoke method. The rest is just error handling.

We’re mostly done! We do now have a map of initialized beans. As we created all beans in step 1, the order of initializing them was not important.

Step 4: Call Initialization Methods

There is one last step which is useful if the beans needs a constructor. As we call the setter methods after the bean is created, the constructor will only see <kbd>null </kbd>values. So we will provide some kind of init method, which we call after the bean is fully initialized. There are two more features that we will add: telling the bean which name it has and giving the DIContext itself to the bean. These are just examples what is possible.

There are at least three ways we could implement this:

  1. By convention over configuration: If there is a method “ init”, call it
  2. By configuration: provide a init-method attribute in the XML config (like spring does)
  3. By using interfaces

By now, you should have an idea how the first two solutions work, so let’s do the first one. We define some interfaces:

This was not very complicated. Now the DIXmlContext:

And the BeanFunctionCaller:

No more reflections – we can do all this with instanceof. If we implement “ SelfAware” in bean1, it’s setNamemethod is called automatically after all setters are filled.

Obtaining the Beans

One question remains: how do we get our beans? The question is simple: if you know the name, just look it up in the map. We will also provide a method to lookup a method by type (this is the last par of our DIXmlContext):


We’re done. This code (it’s less than 500 LOC + Tests) is enough for a small DI Framework. It does not have the same flexibility that Spring has, and it won’t be able to replace spring in the most cases. But the goal was to get an idea of what spring does internally.

What are the next steps? Spring can do more: one thing I will perhaps blog about is the initializing of beans by using annotations instead of an XML file. Another thing is aspect oriented programming: Spring can wrap Beans into objects which will transparently open and close database transactions before calling your real method. This will only work in beans, not in any other class with is not managed by spring (can you explain why?). A third thing would be factories, which generate the real bean objects on the fly.

I wanted to show that there is no magic, everything can be explained. We dont’s use a customized java compiler, we don’t even generate java bytecode. We only use reflections to achive our goal. I hope it was fun to play around with this feature and perhaps you do your own experiment now, however: keep exploring wink

One Reply to “How does Spring work I?”

  1. Pingback: Using Java’s Proxy Class to Transparently Manage Transactions | JoWiSoftware

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.