Thursday, 15 November 2012

Chain of responsibility using Spring @Autowired List

There is a way in Spring 3.1 to auto populate a typed List which is very handy when you want to push a bit the decoupling and the cleaning in your code.

To show you how it works, I will implement a simple chain of responsibility that will take care of printing some greetings for a passed User.

Let start from the (only) domain class we have, the User:
package com.marco.springchain;
public class User {

        private final String name;
        private final char gender;

        public User(String name, char gender) {
                super();
                this.name = name;
                this.gender = gender;
        }

        public String getName() {
                return name;
        }

        public char getGender() {
                return gender;
        }
}



Then we create an interface that defines the type for our command objects to be used in our chain:

package com.marco.springchain;
public interface Printer {

        void print(User user);
}



This is the generic class (the template) for a Printer implementation.
The org.springframework.core.Ordered is used to tell the AnnotationAwareOrderComparator how we want our List to be ordered.
You don't need to implement the Ordered interface and to override the getOrder method if you don't need your chain to have an execution order.
Also notice that this abstract class return Ordered.LOWEST_PRECEDENCE, this because I want some Printer commands to just run at the end of the chain and I don't care about their execution order (everything will be clearer after, I promise!).

package com.marco.springchain;
import org.springframework.core.Ordered;
public abstract class GenericPrinter implements Printer, Ordered {

        public void print(User user) {
                String prefix = "Mr";
                if (user.getGender() == 'F') {
                        prefix = "Mrs";
                }
                System.out.println(getGreeting() + " " + prefix + " " + user.getName());
        }

        protected abstract String getGreeting();

        public int getOrder() {
                return Ordered.LOWEST_PRECEDENCE;
        }
}



This is our first real Printer command. I want this to have absolute precedence in the chain, hence the order is  HIGHEST_PRECEDENCE.

package com.marco.springchain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
public class HelloPrinter extends GenericPrinter {

        private static final String GREETING = "Hello";

        @Override
        protected String getGreeting() {
                return GREETING;
        }

        @Override
        public int getOrder() {
                return Ordered.HIGHEST_PRECEDENCE;
        }
}




WelcomePrinter to be executed as first command (After High precedence ones ).

package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class WelcomePrinter extends GenericPrinter {

        private static final String GREETING = "Welcome to the autowired chain";

        @Override
        protected String getGreeting() {
                return GREETING;
        }

        @Override
        public int getOrder() {
                return 1;
        }
}



GoodbyePrinter to be executed as second command

package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class GoodbyePrinter extends GenericPrinter {

        private static final String GREETING = "Goodbye";

        @Override
        protected String getGreeting() {
                return GREETING;
        }

        @Override
        public int getOrder() {
                return 2;
        }
}




These 2 commands need to be executed after the others, but I don't care about their specific order, so I will not override the getOrder method, leaving the GenericPrinter to return Ordered.LOWEST_PRECEDENCE for both.

package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class CleaningMemoryPrinter extends GenericPrinter {

        private static final String GREETING = "Cleaning memory after";

        @Override
        protected String getGreeting() {
                return GREETING;
        }
}



package com.marco.springchain;
import org.springframework.stereotype.Component;
@Component
public class CleaningSpacePrinter extends GenericPrinter {

        private static final String GREETING = "Cleaning space after";

        @Override
        protected String getGreeting() {
                return GREETING;
        }
}




This is the chain context.
Spring will scan (see the spring-config.xml) the package specified in the config file, it will see the typed (List<Printer>) list, and it will populate the list with an instance of any @Component that implements the type Printer.
To order the List we use AnnotationAwareOrderComparator.INSTANCE that use the getOrder method to re-order the List ( the object with the lowest value has highest priority (somewhat analogous to Servlet "load-on-startup" values)).

package com.marco.springchain;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.stereotype.Component;
@Component
public class PrinterChain {

        @Autowired
        private List<Printer> printers;

        @PostConstruct
        public void init() {
                Collections.sort(printers, AnnotationAwareOrderComparator.INSTANCE);
        }

        public void introduceUser(User user) {
                for (Printer printer : printers) {
                        printer.print(user);
                }
        }
}



The spring-config.xml in the src/main/resources.

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd" default-lazy-init="true">

    <context:component-scan base-package="com.marco.springchain"/>
</beans>



Finally, a main class to test our chain.

package com.marco.springchain;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainTest {

        public static void main(String[] args) {
                ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
                PrinterChain printerChain = (PrinterChain) context.getBean("printerChain");
                printerChain.introduceUser(new User("Marco Castigliego", 'M'));
                printerChain.introduceUser(new User("Julie Marot", 'F'));
        }
}



OUTPUT:


Hello Mr Marco Castigliego
Welcome to the autowired chain Mr Marco Castigliego
Goodbye Mr Marco Castigliego
Cleaning space after Mr Marco Castigliego
Cleaning memory after Mr Marco Castigliego
Hello Mrs Julie Marot
Welcome to the autowired chain Mrs Julie Marot
Goodbye Mrs Julie Marot
Cleaning space after Mrs Julie Marot
Cleaning memory after Mrs Julie Marot


Hope you enjoyed the example. see ya.



No comments:

Post a Comment