Spring Framework: Refactoring Helloworld Application€¦ · 6.HelloWorld using Spring framework as...

Post on 17-Oct-2020

7 views 0 download

Transcript of Spring Framework: Refactoring Helloworld Application€¦ · 6.HelloWorld using Spring framework as...

1

Spring Framework:Spring Framework:RefactoringRefactoring

Helloworld ApplicationHelloworld Application

Mudassar HakimMudassar Hakimmudassar.trainer@gmail.commudassar.trainer@gmail.com

1

2mudassar.trainer@gmail.com

Theme of this Presentation● How a simple HelloWorld application can be

refactored in order to achieve the agility (and testability)?– How can I change a certain part of an application

without affecting other parts of the code?– How can I wire different parts of the application

without writing a lot of glue code myself?– How can I test the business logic without being tied

up with a particular framework?

3mudassar.trainer@gmail.com

Refactoring HelloWorld Application1. HelloWorld 2. HelloWorld with command line arguments3. HelloWorld with decoupling without using Interface4. HelloWorld with decoupling using Interface5. HelloWorld with decoupling through Factory6. HelloWorld using Spring framework as a factory class but not

using DI (Dependency Injection)7. HelloWorld using Spring framework's DI 8. HelloWorld using Spring framework's DI and XML configuration

file9. HelloWorld using Spring framework's DI and XML configuration

file with constructor argument10. HelloWorld using @Autowired annotation11.HelloWorld using auto-scanning

4mudassar.trainer@gmail.com

1. HelloWorld Application1. HelloWorld Application

5mudassar.trainer@gmail.com

HelloWorld

// This is a good old HelloWorld application we all have written// the first time we learn Java programming.

public class HelloWorld {

public static void main(String[] args) { System.out.println("Hello World!"); }}

6mudassar.trainer@gmail.com

HelloWorld: Outstanding Problems● Message content is hard-coded

– You have to change code (and recompile) to display a different message

public class HelloWorld {

public static void main(String[] args) { System.out.println("Hello World!"); }}

7mudassar.trainer@gmail.com

HelloWorld: Areas for Refactoring● Support a flexible mechanism for changing the

message

8mudassar.trainer@gmail.com

2. HelloWorld Application2. HelloWorld Applicationwith Command Linewith Command Line

ArgumentsArguments

9mudassar.trainer@gmail.com

HelloWorld With Command Line arguments public class HelloWorldWithCommandLine {

public static void main(String[] args) { // If an argument is provided, use it, otherwise, display // “Hello World!” if (args.length > 0) { System.out.println(args[0]); } else { System.out.println("Hello World!"); } }

}

10mudassar.trainer@gmail.com

HelloWorld With Command Line arguments: Areas Refactored● This code externalize the message content and

read it in at runtime, from the command line argument– You can change the message without changing and

recompiling the code

11mudassar.trainer@gmail.com

HelloWorld With Command Line arguments: Outstanding Problems● The scheme of rendering is hard-coded (println)

– System.out.println(args[0]);– What if I want to output the message differently,

maybe to stderr instead of stdout, or enclosed in HTML tags rather than as plain text?

● The code responsible for the rendering message (renderer – the code that does println) is also responsible for obtaining the message– Changing how the message is obtained means

changing the code in the renderer– What if I want to get the message from a Web

service?

12mudassar.trainer@gmail.com

HelloWorld With Command Line arguments: Areas for Further Refactoring● Rendering logic should be in a logically separate

code from the rest of the application– So that we can change the rendering logic without

affecting the rest of the application● Message provider logic should be in a logically

separate code from the rest of the application– So that we can change the messaging provider logic

without affecting the rest of the application

13mudassar.trainer@gmail.com

3. HelloWorld Application3. HelloWorld Applicationwith Decoupling Messagewith Decoupling Message

Provider & Renderer Provider & Renderer

14mudassar.trainer@gmail.com

Decouple Message Provider

● De-couple message provider logic implementation from the rest of the code by creating a separate class

public class HelloWorldMessageProvider {

// The actual message provider logic can be changed // without affecting the rest of the application public String getMessage() { return "Hello World!"; }

}

15mudassar.trainer@gmail.com

Decouple Message Renderer● De-couple message rendering logic from the rest of the

code● Message rendering logic is given

HelloWorldMessageProvider object by someone public class StandardOutMessageRenderer {

private HelloWorldMessageProvider messageProvider = null;

public void setMessageProvider(HelloWorldMessageProvider provider) { this.messageProvider = provider; }

public void render() { String message = messageProvider.getMessage(); System.out.println(message); }}

16mudassar.trainer@gmail.com

HelloWorld With Decoupling● Launcher (this is the business logic that uses the

message renderer)

public class HelloWorldDecoupled {

public static void main(String[] args) { // Create message renderer StandardOutMessageRenderer mr = new StandardOutMessageRenderer(); // Create message provider HelloWorldMessageProvider mp = new HelloWorldMessageProvider();

// Set the message provider to the message render mr.setMessageProvider(mp); // Call message renderer mr.render(); }}

17mudassar.trainer@gmail.com

HelloWorld With Decoupling: Areas Refactored● Message provider logic and message renderer

logic are separated from the rest of the code– So message provider (HelloWorldMessageProvider) can

change without affecting the rest of the application (StandardOutMessageRenderer, Launcher)

– So message renderer (StandardOutMessageRenderer) can change without affecting the rest of the application (HelloWorldMessageProvider, Launcher)

18mudassar.trainer@gmail.com

HelloWorld With Decoupling: Outstanding Problems● A particular message provider

(HelloWorldMessageProvider) is hard-coded in the message renderer– Usage of different type of message provider in the

message renderer requires a change in the message renderer

– What if I want to use GoodbyeMessageProvider instead of HelloWorldMessageProvider

public class StandardOutMessageRenderer {

private HelloWorldMessageProvider messageProvider = null; ...

19mudassar.trainer@gmail.com

HelloWorld With Decoupling: Areas for Further Refactoring● Let these components implement interfaces ● User of a component uses interface as a

reference type

20mudassar.trainer@gmail.com

4. HelloWorld Application4. HelloWorld Applicationwith Decoupling Using with Decoupling Using

InterfaceInterface

21mudassar.trainer@gmail.com

HelloWorld With Decoupling (Using Interface)● Message provider logic now uses Java

interfacepublic interface MessageProvider { public String getMessage();}

public class HelloWorldMessageProvider implements MessageProvider {

public String getMessage() { return "Hello World!"; }

}

22mudassar.trainer@gmail.com

MessageRenderer and MessageProvider Interfaces● Message rendering logic is given MessageProvider

object instance by someone

public interface MessageRenderer {

public void render(); public void setMessageProvider(MessageProvider provider); public MessageProvider getMessageProvider();}

23mudassar.trainer@gmail.com

StandardOutMessageRenderer Class public class StandardOutMessageRenderer implements MessageRenderer {

// MessageProvider is Java Interface 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()); }

// Continued to the next page

24mudassar.trainer@gmail.com

HelloWorld With Decoupling (using Interface)

// MessageProvider is Java Interface public void setMessageProvider(MessageProvider provider) { this.messageProvider = provider; }

// MessageProvider is Java Interface public MessageProvider getMessageProvider() { return this.messageProvider; }

}

25mudassar.trainer@gmail.com

HelloWorld With Decoupling (using Interface)● Launcher

public class HelloWorldDecoupled {

public static void main(String[] args) { MessageRenderer mr = new StandardOutMessageRenderer(); MessageProvider mp = new HelloWorldMessageProvider(); mr.setMessageProvider(mp); mr.render(); }}

26mudassar.trainer@gmail.com

HelloWorld With Decoupling (using Interface): Areas Refactored● Message rendering logic does not get affected

by the change in message provider implementation because it uses the MessageProvider interface as a reference type

27mudassar.trainer@gmail.com

HelloWorld With Decoupling: Outstanding Problems● The implementations of renderer and message

providers are hard-coded in business logic code (launcher in this example)

● Using different implementation of either means a change to the business logic code

public class HelloWorldDecoupled {

public static void main(String[] args) { MessageRenderer mr = new StandardOutMessageRenderer(); MessageProvider mp = new HelloWorldMessageProvider(); mr.setMessageProvider(mp); mr.render(); }}

28mudassar.trainer@gmail.com

HelloWorld With Decoupling: Areas for Further Refactoring● Create a simple factory class that reads the

implementation class names from a properties file and instantiate them during runtime on behalf of the application

29mudassar.trainer@gmail.com

5. HelloWorld Application5. HelloWorld Applicationwith Decoupling throughwith Decoupling through

Factory classFactory class

30mudassar.trainer@gmail.com

Factory Class That Read Properties Filepublic 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("msf.properties"));

// Get the name of the implementation classes String rendererClass = props.getProperty("renderer.class"); String providerClass = props.getProperty("provider.class");

// Create object instances of MessageRenderer and MessageProvider renderer = (MessageRenderer) Class.forName(rendererClass).newInstance(); provider = (MessageProvider) Class.forName(providerClass).newInstance(); } catch (Exception ex) { ex.printStackTrace(); } }

31mudassar.trainer@gmail.com

HelloWorld With Factory Class static { instance = new MessageSupportFactory(); }

public static MessageSupportFactory getInstance() { return instance; }

public MessageRenderer getMessageRenderer() { return renderer; }

public MessageProvider getMessageProvider() { return provider; }

}

32mudassar.trainer@gmail.com

Launcher Code (Business Logic)public class HelloWorldDecoupledWithFactory {

public static void main(String[] args) { MessageRenderer mr = MessageSupportFactory.getInstance().getMessageRenderer(); MessageProvider mp = MessageSupportFactory.getInstance().getMessageProvider(); mr.setMessageProvider(mp); mr.render(); }}

33mudassar.trainer@gmail.com

HelloWorld With Factory Class: Properties file# msf.propertiesrenderer.class=StandardOutMessageRendererprovider.class=HelloWorldMessageProvider

34mudassar.trainer@gmail.com

HelloWorld With Decoupling: Areas Refactored● Message provider implementation and Message

renderer implementation can be replaced simply by changing the properties file– No change is required in the business logic code

(launcher)

35mudassar.trainer@gmail.com

HelloWorld With Factory: Outstanding Problems● You still have to write a lot of glue code yourself

to assemble the application together– You have to write MessageSupportFactory class

36mudassar.trainer@gmail.com

HelloWorld With Factory: Areas for Further Refactoring● Replace MessageSupportFactory class with

Spring framework's DefaultListableBeanFactory class– You can think of DefaultListableBeanFactory class

as a more generic version of MessageSupportFactory class

37mudassar.trainer@gmail.com

6. HelloWorld Application6. HelloWorld Applicationwith Spring Frameworkwith Spring Framework(but not using DI yet)(but not using DI yet)

38mudassar.trainer@gmail.com

HelloWorld With Spring Frameworkpublic class HelloWorldSpring {

public static void main(String[] args) throws Exception {

// Get the bean factory - the code of getBeanFactory() is in the next slide BeanFactory factory = getSpringBeanFactory();

MessageRenderer mr = (MessageRenderer) factory.getBean("renderer"); MessageProvider mp = (MessageProvider) factory.getBean("provider");

mr.setMessageProvider(mp); mr.render(); }

// Continued in the next page

39mudassar.trainer@gmail.com

HelloWorld With Spring Framework(No need to understand this code) // You write your own getSpringBeanFactory() method using Spring framework's // DefaultListableBeanFactory class.

private static BeanFactory getSpringBeanFactory() 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("beans.properties"));

rdr.registerBeanDefinitions(props);

return factory; }}

40mudassar.trainer@gmail.com

HelloWorld With Spring Framework: Areas Refactored● Removed the need of your own glue code

(MessageSupportFactory)● Gained a much more robust factory

implementation with better error handling and fully de-coupled configuration mechanism

41mudassar.trainer@gmail.com

HelloWorld With Spring Framework: Outstanding Problems● Spring acts as no more than a sophisticated

factory class creating and supplying instances of classes as needed in this case

● The startup code must have knowledge of the MessageRenderer's dependencies and must obtain dependencies and pass them to the MessageRenderer (This is called “wiring”)– You still have to inject an instance of

MessageProvider into the implementation of MessageRenderer yourself

– You are providing your own getBeanFactory() method using low-level API's of Spring framework

42mudassar.trainer@gmail.com

HelloWorld With Spring Framework: Areas for Further Refactoring● Use Dependency Injection (DI) of the Spring

Framework– Let Spring framework to handle the wiring – Let

Spring framework inject MessageProvider object into MessageRenderer object

– Glue the application together externally using the BeanFactory configuration

43mudassar.trainer@gmail.com

7. HelloWorld Application7. HelloWorld Applicationusing Spring Framework &using Spring Framework &Dependency Injection (DI)Dependency Injection (DI)

44mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DI#Message rendererrenderer.class=StandardOutMessageRenderer# Ask Spring to assign provider bean to the MessageProvider property# of the Message renderer bean (instead of you doing it manually)renderer.messageProvider(ref)=provider

#Message providerprovider.class=HelloWorldMessageProvider

45mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DIpublic class HelloWorldSpringWithDI {

public static void main(String[] args) throws Exception {

// get the bean factory BeanFactory factory = getBeanFactory();

MessageRenderer mr = (MessageRenderer) factory.getBean("renderer"); // Note that you don't have to manually inject message provider to // message renderer anymore. It is because the MessageRenderer // object should be wired with MessageProvider object by Spring framework. mr.render(); }

// Continued in the next page

46mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DI 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("beans.properties"));

rdr.registerBeanDefinitions(props);

return factory; }}

47mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DI: Areas Refactored● The main() method now just obtains the

MessageRenderer bean and calls render()– It does not have to obtain MessageProvider bean

and set the MessageProvider property of the MessageRenderer bean itself.

– This “wiring” is performed through Spring framework's Dependency Injection.

48mudassar.trainer@gmail.com

A Few Things to Observe

● Note that we did not have to make any changes to the classes (beans) that are being wired together

● These classes have no reference to Spring framework whatsoever and completely oblivious to Spring framework's existence– No need to implement Spring framework's interfaces– No need to extend Spring framework's classes

● These classes are genuine POJO's which can be tested without dependent on Spring framework

49mudassar.trainer@gmail.com

8. HelloWorld Application8. HelloWorld Applicationwith Spring Framework &with Spring Framework &Dependency Injection (DI)Dependency Injection (DI)

using XML Configuration Fileusing XML Configuration File

50mudassar.trainer@gmail.com

Spring DI with XML file

● Dependencies of beans are specified in an XML file– XML based bean configuration is more popular than

properties file based configuration

51mudassar.trainer@gmail.com

Spring DI with XML Configuration File

<beans> <bean id="renderer" class="StandardOutMessageRenderer"> <property name="messageProvider" ref=”provider"/> </bean> <bean id="provider" class="HelloWorldMessageProvider"/></beans>

52mudassar.trainer@gmail.com

Spring DI with XML Configuration Filepublic class HelloWorldSpringWithDIXMLFile {

public static void main(String[] args) throws Exception {

// get the bean factory BeanFactory factory = getSpringBeanFactory(); MessageRenderer mr = (MessageRenderer) factory.getBean("renderer"); mr.render(); }

private static BeanFactory getSpringBeanFactory() throws Exception { // get the bean factory BeanFactory factory = new ClassPathXmlApplicationContext("/beans.xml");

return factory; }}

53mudassar.trainer@gmail.com

9. HelloWorld Application9. HelloWorld Applicationwith Spring Framework &with Spring Framework &Dependency Injection (DI)Dependency Injection (DI)

using XML Configuration File using XML Configuration File with Constructor argument with Constructor argument

54mudassar.trainer@gmail.com

Spring DI with XML Configuration File:via Constructor<beans> <bean id="renderer" class="StandardOutMessageRenderer"> <property name="messageProvider" ref=”provider"/> </bean> <bean id="provider" class="ConfigurableMessageProvider"> <constructor-arg> <value>This is a configurable message</value> </constructor-arg> </bean></beans>

55mudassar.trainer@gmail.com

Spring DI with XML Configuration File:via Constructorpublic class ConfigurableMessageProvider implements

MessageProvider { private String message; public ConfigurableMessageProvider(String message) { this.message = message; } public String getMessage() { return message; }}

56mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DI using XML: Areas Refactored

● We were able to specify wiring requirements in XML

● What if there are too many wiring requirements that need to be specified in XML?– Declaring wiring requirements among beans could

be unmanage'able if there are too many

57mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DI using XML: Areas for Further Refactoring

● We want to specify wiring requirements in the source code using Annotation

58mudassar.trainer@gmail.com

10. HelloWorld Application10. HelloWorld Applicationwith Spring Framework &with Spring Framework &Dependency Injection (DI)Dependency Injection (DI)

using @Autowired using @Autowired AnnotationAnnotation

59mudassar.trainer@gmail.com

Wiring through @AutowiredAnnotation in Code

public class StandardOutMessageRenderer implements MessageRenderer {

@Autowired private MessageProvider messageProvider = null;

public void render() { // ... }

// Not needed anymore since it will be autowired// public void setMessageProvider(MessageProvider provider) {// this.messageProvider = provider;// }

public MessageProvider getMessageProvider() { return this.messageProvider; }

}

60mudassar.trainer@gmail.com

No need to specify wiring requirements in the 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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- enable the usage of annotations --> <context:annotation-config /> <!-- Note that there is no messageProvider property in the renderer. It is because the wiring requirement is specified through @Autowired annotation --> <bean id="renderer" class="com.mudassar.examples.StandardOutMessageRenderer"> </bean>

<bean id="provider" class="com.mudassar.examples.HelloWorldMessageProvider"/></beans>

61mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DI using @Autowired: Areas Refactored

● We were able to specify wiring requirements right in the source code using @Autowired annotation

● But we still have to declare all the beans in the XML file

● What if there are too many beans we need to declare– Having to declare all the beans in the XML is a chore– Can we have Spring framework to auto-scan and

detect all the beans?

62mudassar.trainer@gmail.com

HelloWorld using Spring Framework's DI using XML: Areas for Further Refactoring

● We want Spring framework to auto-detect all the beans and create instances

63mudassar.trainer@gmail.com

11. HelloWorld Application11. HelloWorld Applicationwith Spring Framework &with Spring Framework &Dependency Injection (DI)Dependency Injection (DI)

using Auto-scanningusing Auto-scanning

64mudassar.trainer@gmail.com

Auto-scanning beans

<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- enable the usage of annotations --> <!-- <context:annotation-config /> --> <!-- scan component, it also assumes annotation-config as well--> <context:component-scan base-package="com.mudassar.examples"/></beans>

65mudassar.trainer@gmail.com

Bean with @Component Annotation gets detected

@Component("renderer") // This is the same as @Component(value="renderer")public class StandardOutMessageRenderer implements MessageRenderer {

@Autowired private MessageProvider messageProvider = null;

public void render() { // ... }

// Not needed anymore since it will be autowired// public void setMessageProvider(MessageProvider provider) {// this.messageProvider = provider;// }

public MessageProvider getMessageProvider() { return this.messageProvider; }}

66mudassar.trainer@gmail.com

Bean with @Component Annotation gets detected

@Componentpublic class HelloWorldMessageProvider implements MessageProvider {

public String getMessage() {

return "Hello World!"; }

}

67

Thank you!Thank you!

Mudassar HakimMudassar Hakimmudassar.trainer@gmail.commudassar.trainer@gmail.com

67