Spring ReaderEventListener

0
In this article, we will see how we can extend spring classes to include our own hook at each important event of the flow during the loading of definition. At each event we add some information to a list which we will print at the end of execution.We will also examine the flow and see how spring has group them into appropriate concerns. This will help you if you want to extend spring framework and make it work for your own use cases.
If you want to know the basic flow of loading bean definitions, read here.

XmlBeanDefinitionReader

XmlBeanDefinitionReader drives the outer flow. It helps to locate the actual resources based on the location pattern. We will sub class the xml reader and override the methods that load bean definitions. This way we will know the actual location paths of the resources.

public class MyXmlDefinitionReader extends XmlBeanDefinitionReader {
    private List events;
    public MyXmlDefinitionReader(BeanDefinitionRegistry registry, List inEvents) {
        super(registry);
        events = inEvents;
    }

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        List locationsList = Arrays.asList(locations);
        events.add("load bean definitions for " + locationsList + "\n");
        int count = super.loadBeanDefinitions(locations);
        events.add(count + " beans loaded from " + locationsList + "\n");
        return count;
    }

    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        events.add("load bean definitions for " + location + "\n");
        int count = super.loadBeanDefinitions(location, null);
        events.add(count + " beans loaded from " + location + "\n");
        return count;
    }

    @Override
    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        events.add("load bean definitions for resources " + Arrays.asList(resources) + "\n");
        int count = super.loadBeanDefinitions(resources);
        events.add(count + " beans loaded from resources " + Arrays.asList(resources) + "\n");
        return count;
    }

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        events.add("load bean definitions for resource " + resource + "\n");
        int count = super.loadBeanDefinitions(resource);
        events.add(count + " beans loaded from resources " + resource + "\n");
        return count;
    }

    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return new DefaultBeanDefinitionDocumentReader(){
            protected void doRegisterBeanDefinitions(Element root) {
                events.add("register bean def for " + root + "\n");
                super.doRegisterBeanDefinitions(root);
            }
            protected void preProcessXml(Element root) {
                events.add("pre process xml\n");
            }

            protected void postProcessXml(Element root) {
                events.add("post process xml\n");
            }

        };
    }
}

ReaderEventListener

We will also set our own event listener. It will be notified as and when the main elements import, alias, beans, bean are read.

public class MyReaderEventListener extends EmptyReaderEventListener {
    private List events;
    public MyReaderEventListener(List inEvents) {
        events = inEvents;
    }
    public void componentRegistered(ComponentDefinition componentDefinition) {
        events.add(componentDefinition.getName() + " component Registered\n");
    }

    public void defaultsRegistered(DefaultsDefinition defaultsDefinition) {
        events.add("Default attributes at beans level are read\n");
    }
    public void aliasRegistered(AliasDefinition aliasDefinition) {
        events.add("alias registered");
    }

    public void importProcessed(ImportDefinition importDefinition) {
        events.add("import registered for " + importDefinition.getImportedResource());
    }
}

Environment

Next, we set our own environment so we know when the profile is validated.

public class MyEnvironment extends StandardEnvironment {
    private List events;
    public MyEnvironment(List inEvents) {
        events = inEvents;
    }
    public boolean acceptsProfiles(String... profiles) {
        boolean acceptsProfiles = super.acceptsProfiles(profiles);
        events.add("Check environment whether profile " + Arrays.asList(profiles) + " is accepted\n");
        return acceptsProfiles;
    }
}

Unit test

In the unit test, we use our own xml reader to load the bean definitions. We also set the sub-classed event listener and environment to the reader. Finally, we print all the list of events that we populated at each occurrence of the event starting from loading to registration of beans.

 

    public void testBasicFlow() {
        System.setProperty("spring.profiles.active", "dev");
        final List events = new ArrayList<>();
        DefaultListableBeanFactory testNameFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new MyXmlDefinitionReader(testNameFactory, events);
        xmlBeanDefinitionReader.setDocumentReaderClass(MyBeanDefDocReader.class);
        xmlBeanDefinitionReader.setEventListener(new MyReaderEventListener(events));
        xmlBeanDefinitionReader.setEnvironment(new MyEnvironment(events));
                xmlBeanDefinitionReader.loadBeanDefinitions("classpath*:org/springframework/beans/factory/xml/context*.xml",
                        "classpath*:org/springframework/beans/factory/xml/aliasContext.xml");
        assertTrue(testNameFactory.containsBeanDefinition("bean1"));
        assertTrue(testNameFactory.containsBeanDefinition("importedBean"));
        System.out.println(events);
    }

When we run the above test, we get lot of output!. You can tweak the above classes to log information as per your own interest.

[load bean definitions for [classpath*:org/springframework/beans/factory/xml/context*.xml, classpath*:org/springframework/beans/factory/xml/aliasContext.xml], load bean definitions for classpath*:org/springframework/beans/factory/xml/context*.xml
, load bean definitions for resources [file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context1.xml], file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context2.xml], file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context3.xml]]
, load bean definitions for resource file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context1.xml]
, register bean def for [beans: null]
, Check environment whether profile [dev]is accepted
, Default attributes at beans level are read
, pre process xml
, bean1 component Registered
, bean2 component Registered
, register bean def for [beans: null]
, Check environment whether profile [dev]is accepted
, Default attributes at beans level are read
, pre process xml
, devBean1 component Registered
, post process xml
, register bean def for [beans: null]
, Check environment whether profile [test]is accepted
, post process xml
, 3 beans loaded from resources file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context1.xml]
, load bean definitions for resource file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context2.xml]
, register bean def for [beans: null]
, Default attributes at beans level are read
, pre process xml
, bean3 component Registered
, post process xml
, 1 beans loaded from resources file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context2.xml]
, load bean definitions for resource file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context3.xml]
, register bean def for [beans: null]
, Default attributes at beans level are read
, pre process xml
, load bean definitions for resource file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\testImport.xml]
, register bean def for [beans: null]
, Default attributes at beans level are read
, pre process xml
, importedBean component Registered
, post process xml
, 1 beans loaded from resources file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\testImport.xml]
, import registered for testImport.xml, bean3 component Registered
, post process xml
, 1 beans loaded from resources file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context3.xml]
, 5 beans loaded from resources [file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context1.xml], file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context2.xml], file [C:\spring-framework\branches\3.2.x\out\test\spring-beans\org\springframework\beans\factory\xml\context3.xml]]
, 5 beans loaded from classpath*:org/springframework/beans/factory/xml/context*.xml
, load bean definitions for classpath*:org/springframework/beans/factory/xml/aliasContext.xml
, load bean definitions for resources [URL [file:/C:/spring-framework/branches/3.2.x/out/test/spring-beans/org/springframework/beans/factory/xml/aliasContext.xml]]
, load bean definitions for resource URL [file:/C:/spring-framework/branches/3.2.x/out/test/spring-beans/org/springframework/beans/factory/xml/aliasContext.xml]
, register bean def for [beans: null]
, Default attributes at beans level are read
, pre process xml
, alias registered, post process xml
, 0 beans loaded from resources URL [file:/C:/spring-framework/branches/3.2.x/out/test/spring-beans/org/springframework/beans/factory/xml/aliasContext.xml]
, 0 beans loaded from resources [URL [file:/C:/spring-framework/branches/3.2.x/out/test/spring-beans/org/springframework/beans/factory/xml/aliasContext.xml]]
, 0 beans loaded from classpath*:org/springframework/beans/factory/xml/aliasContext.xml
, 5 beans loaded from [classpath*:org/springframework/beans/factory/xml/context*.xml, classpath*:org/springframework/beans/factory/xml/aliasContext.xml]
]

Flow diagram depicting different concerns

In the below diagram, we group the actions into different concerns. Each concern is handled by a dedicated class.
basic flow of loading bean definitions-2
basic flow of loading bean definitions-2
Share.

Leave A Reply