Putting a Spring into Hello World

Hopefully by this point in the book you appreciate that Spring is a solid, well-supported project that has all the makings of a great tool for application development. However, one thing is missing—we haven't shown you any code yet. We are sure that you are dying to see Spring in action, and because we can not go any longer without getting into the code, let us do just that. Do not worry if you do not fully understand all the code in this section; we go into much more detail on all the topics as we proceed through the book.

Now, we are sure you are all familiar with the traditional Hello World example, but just in case you have been living on the Moon for the last 30 years, Listing 2-1 shows the Java version in all its glory.

Listing 2-1: Typical Hello World Example

image from book
package com.apress.prospring.ch2; public class HelloWorld {           public static void main(String[] args) {         System.out.println("Hello World!");     } }
image from book

As far as examples go, this one is pretty simple—it does the job but it is not very extensible. What if we want to change the message? What if we want to output the message differently, maybe to stderr instead of stdout, or enclosed in HTML tags rather than as plain text?

We are going to redefine the requirements for the sample application and say that it must support a simple, flexible mechanism for changing the message, and it must be simple to change the rendering behavior. In the basic Hello World example, you can make both of these changes quickly and easily by just changing the code as appropriate. However, in a bigger application, recompiling takes time and it requires the application to be fully tested again. No, a better solution is to externalize the message content and read it in at runtime, perhaps from the command line arguments shown in Listing 2-2.

Listing 2-2: Using Command Line Arguments with Hello World

image from book
   package com.apress.prospring.ch2;       public class HelloWorldWithCommandLine {           public static void main(String[] args) {         if(args.length > 0) {             System.out.println(args[0]);         } else {             System.out.println("Hello World!");         }     } }
image from book

This example accomplishes what we wanted—we can now change the message without changing the code. However, there is still a problem with this application: the component responsible for rendering the message is also responsible for obtaining the message. Changing how the message is obtained means changing the code in the renderer. Add to this the fact that we still cannot change the renderer easily; doing so means changing the class that launches the application.

If we take this application a step further, away from the basics of Hello World, a better solution is to refactor the rendering and message retrieval logic into separate components. Plus, if we really want to make our application flexible, we should have these components implement interfaces and define the interdependencies between the components and the launcher using these interfaces.

By refactoring the message retrieval logic, we can define a simple MessageProvider interface with a single method, getMessage(), as shown in Listing 2-3.

Listing 2-3: The MessageProvider Interface

image from book
package com.apress.prospring.ch2;       public interface MessageProvider {       public String getMessage(); }
image from book

As you can see in Listing 2-4, the MessageRenderer interface is implemented by all components that can render messages.

Listing 2-4: The MessageRenderer Interface

image from book
package com.apress.prospring.ch2;       public interface MessageRenderer {           public void render();          public void setMessageProvider(MessageProvider provider);     public MessageProvider getMessageProvider(); }
image from book

As you can see, the MessageRenderer interface has a single method, render(), and also a single JavaBean-style property, MessageProvider. Any MessageRenderer implementations are decoupled from message retrieval and delegate it instead to the MessageProvider with which they are supplied. Here MessageProvider is a dependency of MessageRenderer. Creating simple implementations of these interfaces is easy (see Listing 2-5).

Listing 2-5: The HelloWorldMessageProvider Class

image from book
    package com.apress.prospring.ch2;       public class HelloWorldMessageProvider implements MessageProvider {           public String getMessage() {               return "Hello World!";     }       }
image from book

In Listing 2-5, you can see that we have created a simple MessageProvider that always returns "Hello World!" as the message. The StandardOutMessageRenderer class (shown in Listing 2-6) is just as simple.

Listing 2-6: The StandardOutMessageRenderer Class

image from book
package com.apress.prospring.ch2;       public class StandardOutMessageRenderer implements MessageRenderer {           private MessageProvider messageProvider = null;           public void render() {         if (messageProvider == null) {             throw new RuntimeException(                     "You must set the property messageProvider of class:"                             + StandardOutMessageRenderer.class.getName());         }               System.out.println(messageProvider.getMessage());     }           public void setMessageProvider(MessageProvider provider) {         this.messageProvider = provider;     }           public MessageProvider getMessageProvider() {         return this.messageProvider;     } }
image from book

Now all that remains is to rewrite the main() of our entry class as shown in Listing 2-7.

Listing 2-7: Refactored Hello World

image from book
   package com.apress.prospring.ch2;       public class HelloWorldDecoupled {           public static void main(String[] args) {         MessageRenderer mr = new StandardOutMessageRenderer();         MessageProvider mp = new HelloWorldMessageProvider();         mr.setMessageProvider(mp);         mr.render();     } }
image from book

The code here is fairly simple: we instantiate instances of HelloWorldMessageProvider and StandardOutMessageRenderer, although the declared types are MessageProvider and MessageRenderer, respectively. Then we pass the MessageProvider to the MessageRenderer and invoke MessageRenderer.render(). Here is the output from this example, as expected:

Hello World!

Now this example is more like what we are looking for, but there is one small problem. Changing the implementation of either the MessageRenderer or MessageProvider interfaces means a change to the code. To get around this, we can create a simple factory class that reads the implementation class names from a properties file and instantiates them on behalf of the application (see Listing 2-8).

Listing 2-8: The MessageSupportFactory Class

image from book
package com.apress.prospring.ch2;       import java.io.FileInputStream; import java.util.Properties;       public class MessageSupportFactory {           private static MessageSupportFactory instance = null;           private Properties props = null;           private MessageRenderer renderer = null;           private MessageProvider provider = null;           private MessageSupportFactory() {         props = new Properties();               try {             props.load(new FileInputStream("ch2/src/conf/msf.properties"));                   // get the implementation classes             String rendererClass = props.getProperty("renderer.class");             String providerClass = props.getProperty("provider.class");                   renderer = (MessageRenderer)                                   Class.forName(rendererClass).newInstance();             provider = (MessageProvider)                                   Class.forName(providerClass).newInstance();         } catch (Exception ex) {             ex.printStackTrace();         }     }           static {         instance = new MessageSupportFactory();     }           public static MessageSupportFactory getInstance() {         return instance;     }           public MessageRenderer getMessageRenderer() {         return renderer;     }           public MessageProvider getMessageProvider() {         return provider;     } }
image from book

The implementation here is trivial and naïve, the error handling is simplistic, and the name of the configuration file is hard coded, but we already have a substantial amount of code.

A fuller implementation, one suitable for a production system, would have much more code, but for the purposes of this chapter, the example code is adequate. The configuration file for this class is quite simple:

renderer.class=com.apress.prospring.ch2.StandardOutMessageRenderer provider.class=com.apress.prospring.ch2.HelloWorldMessageProvider

A simple modification to the main() method (as shown in Listing 2-9) and we are in business.

Listing 2-9: Using MessageSupportFactory

image from book
    package com.apress.prospring.ch2;       public class HelloWorldDecoupledWithFactory {           public static void main(String[] args) {         MessageRenderer mr =                        MessageSupportFactory.getInstance().getMessageRenderer();         MessageProvider mp =                        MessageSupportFactory.getInstance().getMessageProvider();         mr.setMessageProvider(mp);         mr.render();     } }
image from book

Before we move on to see how we can introduce Spring into this application, let's quickly recap what we have done. Starting with the simple Hello World application, we defined two additional requirements that the application must fulfill. The first was that changing the message should be simple, and the second was that changing the rendering mechanism should also be simple. To meet these requirements, we introduced two interfaces: MessageProvider and MessageRenderer. The MessageRenderer interface depends on an implementation of the MessageProvider interface to be able to retrieve a message to render. Finally, we added a simple factory class to retrieve the names of the implementation classes and instantiate them as applicable.

Refactoring with Spring

The final example from the previous section met the goals we laid out for our sample application, but there are still problems with it. The first problem is that we had to write a lot of glue code to piece the application together, while at the same time keeping the components loosely coupled. The second problem was that we still had to provide the implementation of MessageRenderer with an instance of MessageProvider manually. We can solve both of these problems using Spring.

To solve the problem of too much glue code, we can completely remove the MessageSupportFactory class from the application and replace it with a Spring class, DefaultListableBeanFactory. Don't worry too much about this class; for now it is enough to know that this class acts as a more generic version of our MessageSupportFactory with a few extra tricks up its sleeve (see Listing 2-10).

Listing 2-10: Using DefaultListableBeanFactory

image from book
package com.apress.prospring.ch2;      import java.io.FileInputStream; import java.util.Properties;      import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;      public class HelloWorldSpring {          public static void main(String[] args) throws Exception {              // get the bean factory         BeanFactory factory = getBeanFactory();              MessageRenderer mr = (MessageRenderer) factory.getBean("renderer");         MessageProvider mp = (MessageProvider) factory.getBean("provider");              mr.setMessageProvider(mp);         mr.render();     }          private static BeanFactory getBeanFactory() throws Exception {         // get the bean factory         DefaultListableBeanFactory factory = new DefaultListableBeanFactory();              // create a definition reader         PropertiesBeanDefinitionReader rdr = new PropertiesBeanDefinitionReader(                 factory);              // load the configuration options         Properties props = new Properties();         props.load(new FileInputStream("./ch2/src/conf/beans.properties"));              rdr.registerBeanDefinitions(props);              return factory;     } }
image from book

In Listing 2-10, you can see that the main() method obtains an instance of DefaultListableBeanFactory, typed as BeanFactory, and from this, it obtains the MessageRenderer and MessageProvider instances using the BeanFactory.getBean() method.

Don't worry too much about the getBeanFactory() method for now; just know that this method reads the BeanFactory configuration from a properties file and then returns the configured instance. This properties file is identical to the one we used for MessageSupportFactory:

renderer.class=com.apress.prospring.ch2.StandardOutMessageRenderer provider.class=com.apress.prospring.ch2.HelloWorldMessageProvider

The BeanFactory interface and its implementing classes form the core of Spring's DI container. As mentioned in Chapter 1, Spring makes extensive use of JavaBeans conventions for its DI container, and for this reason, Spring refers to any managed component as a bean, hence the getBean() method.

Now although we have written more code in the startup class, we have removed the need to create any factory code at all, and at the same time, we have gained a much more robust factory implementation with better error handling and a fully decoupled configuration mechanism. However, there is still a small problem with this code. The startup class must have knowledge of the MessageRenderer's dependencies and must obtain these dependencies and pass them to the MessageRenderer. In Listing 2-10, Spring acts as no more than a sophisticated factory class, creating and supplying instances of classes as needed. By finally employing Spring's DI capabilities, we can glue the application together externally using the BeanFactory configuration. This requires a minor modification to the configuration file:

# The MessageRenderer renderer.class=com.apress.prospring.ch2.StandardOutMessageRenderer renderer.messageProvider(ref)=provider      # The MessageProvider provider.class=com.apress.prospring.ch2.HelloWorldMessageProvider

Notice that, aside from the comments, all we have added is the line that assigns the provider bean to the MessageProvider property of the renderer bean. The (ref) keyword means that the value of the property is to be treated as a reference to another bean and not as a literal value. With this configuration, we can remove any references to the MessageProvider interface in the startup class, as shown in Listing 2-11.

Listing 2-11: Using Dependency Injection

image from book
package com.apress.prospring.ch2;      import java.io.FileInputStream; import java.util.Properties;      import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;      public class HelloWorldSpringWithDI {          public static void main(String[] args) throws Exception {         // get the bean factory         BeanFactory factory = getBeanFactory();              MessageRenderer mr = (MessageRenderer) factory.getBean("renderer");         mr.render();     }          private static BeanFactory getBeanFactory() throws Exception {         // get the bean factory         DefaultListableBeanFactory factory = new DefaultListableBeanFactory();              // create a definition reader         PropertiesBeanDefinitionReader rdr = new PropertiesBeanDefinitionReader(                 factory);              // load the configuration options         Properties props = new Properties();         props.load(new FileInputStream("./ch2/src/conf/beans.properties"));              rdr.registerBeanDefinitions(props);              return factory;     } }
image from book

As you can see, the main() method now just obtains the MessageRenderer bean and calls render(); Spring has created the MesssageProvider implementation and injected it into the MessageRenderer implementation. Notice that we didn't have to make any changes to the classes that are being wired together using Spring. In fact, these classes have no reference to Spring whatsoever and are completely oblivious to its existence. However, this isn't always the case. Your classes can implement Spring-specified interfaces to interact in a variety of ways with the DI container. For instance, it is possible to remove the check in MessageRenderer.render() to see if the MessageProvider implementation has been set and make this check just once. However, this relies on a Spring-specific interface, and we will leave that discussion for Chapter 5.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net