Spring‎ > ‎

Spring Configuration

Load Spring Context dynamically:
Sometimes in Runtime we may need to add/update spring context. This is not most frequently used but its not quite unusual. The user requirement may demand such a thing. You know, the main goal of  developer is to fulfill the requirements of the user at the end of the day. Lets have a look at some of the way out for this kind of requirement.

  1. Update the spring context by adding Beans from another configuration file at Runtime:
    Its quite easy, say you have two configuration files applicationContextParent.xml, and     applicationContextChild.xml. For the simplicity, say you need to load the “parent xml” file first and at runtime you have to add the beans from “child xml” file.
Say, in the parent xml file there are some bean like this

<bean id="loginService" class="org.jinos.spring.services.LoginService">
   <property name="loginManager" ref="loginManager"/>
</bean>

<bean id="loginManager" class="org.jinos.spring.manager.LoginManager">
   <property name="superUserList">
       <list>
           <value>admin1</value>
           <value>admin2</value>
           <value>admin3</value>
       </list>
   </property>
</bean>
In the Child Beans the beans are like following :

<bean id="userProfileService" class="org.jinos.spring.services.UserProfileService">
   <property name="webServiceURL" value="http://localhost:7010/GetMemberProfileService"/>
   <!-- <property name="modifiableManager" ref="reloadedManger"/> -->
</bean>

We have a util class to load the application context like the following :
/**
* @author asraf
*
*/
public class ApplicationContextUtil
{
   static String[] configFiles = {"applicationContextParent.xml"};
   
   private static ApplicationContext context = null;
   
   static
   {
       context = new ClassPathXmlApplicationContext ( configFiles );
   }
   
   public static void addContext( String[] newConfigFiles )
   {
       // add the new context to the previous context
       ApplicationContext newContext =  new ClassPathXmlApplicationContext ( newConfigFiles, context );
       context = newContext;
   }
   
   public static ApplicationContext getApplicationContext ()
   {
       // return the context
       return context;
   }

To test these, we can write the test class like the following:
public class SpringBeanLoadTest
{        
   /**
    * @param args
    */
   public static void main ( String[] args )
   {
       try
       {
           loadParentBeans ( );
           loadChildBeans ();
       }
       catch ( Exception ex )
       {
           ex.printStackTrace ( );
       }
   }
   
   static void loadParentBeans ()
   {
       ApplicationContext context = ApplicationContextUtil.getApplicationContext ( );
       LoginService loginService = (LoginService) context.getBean ( "loginService" );
       System.out.println ( "First Super User "+ loginService.getLoginManager ( ).getSuperUserList ( ).get ( 0 ) );
   }
   
   static void loadChildBeans ()
   {            
        String[] newContextFiles = {"applicationContextChild.xml"};
        ApplicationContextUtil.addContext ( newContextFiles );
        ApplicationContext newContext = ApplicationContextUtil.getApplicationContext ( );;
       
        // check the previous beans
        LoginService loginService = (LoginService) newContext.getBean ( "loginService" );
        System.out.println ( "First Super User "+ loginService.getLoginManager ( ).getSuperUserList ( ).get ( 0 ) );
       
        // now check the new beans
       // userProfileService
        UserProfileService userProfileService = (UserProfileService) newContext.getBean ( "userProfileService" );
        System.out.println ( "First Super User "+ userProfileService.getWebServiceURL ( ) );
       
   }

}

Bingo, That’s all you have added the beans at Run time !!! Spring is quite COOL !! isn’t it ?

The Full source code including the service and manager is attached below. In the project source find the test class SpringAddBeansTest.java.

  1. Refresh the Application Context :

    It’s the pretty simple one. use “AbstractRefreshableApplicationContext” to load the context. While changing some property from the context file refresh the context context will be updated. Let’s take a look at an example :


AbstractRefreshableApplicationContext refreshableContext = new

ClassPathXmlApplicationContext (  "applicationContextRefresh.xml" );


This will load the context.
for ( ;; )
       {
           try
           {
               refreshableContext.refresh ( );
               ConfigurableManager manager = (ConfigurableManager) refreshableContext
                       .getBean ( "configurableManager" );
               System.out.println ( "Dao Name : " + manager.getDao ( ).getUrl ( ) );
           }
           catch( Exception ex)
           {
               ex.printStackTrace ( );
               
           }            

           Thread.sleep ( 5 * 1000 );
       }

Its an infinite loop for the test. If you change the url property, In the console you will see the properties have been changed. pretty simple, isn’t it ?

The Full source code including the service and manager is attached below. In the project source find the test class RefreshableContextTest.java.

  1. Using BeanFactoryPostProcessor :

    you can also update the bean using “BeanfactoryPostProcessor”. To do this you have to implement this interface. And the implemented class will be called after the bean initialization. There you can update the bean.

@Override
   public void postProcessBeanFactory ( ConfigurableListableBeanFactory beanFactory )
           throws BeansException
   {
       // TODO Auto-generated method stub
        System.out.println("The factory contains the followig beans:");
        String[] beanNames = beanFactory.getBeanDefinitionNames();
           for (int i = 0; i < beanNames.length; ++i)
             System.out.println(beanNames[i]);
           
          BeanDefinition beanDefinition = beanFactory.getBeanDefinition("modifiableManager");
          MutablePropertyValues pvalues = beanDefinition.getPropertyValues();
          
          for (  Object ob : pvalues.getPropertyValueList ( ))
          {
              System.out.println( "Properties: " + ob.toString ( ) );
          }
          
          pvalues.addPropertyValue ( "daoName", "abc" );
                 
   }

Here, we have updated the value for a manager for example. This approach is  useful when you need to load something after the bean initialized from an external source.

The Full source code including the service and manager is attached below. In the project source find the test class SpringBeanFactoryPostProcessorTest.java.

  1. Using JMX :
    The next option can be using JMX. You have to expose the JMX mBean Server which will actually update the property of your bean. To send the “update request” you need a client.

Say, we want to expose “JmxTestBean  class” as the MBean Server in spring,

public class JmxTestBean implements IJmxTestBean
{

    private String name;
    private int age;
    private boolean isSuperman;

    public int getAge() {
       return age;
    }

    public void setAge(int age) {
       this.age = age;
    }
   
    public void setName(String name) {
       this.name = name;
    }

    public String getName() {
       return name;
    }

    public int add(int x, int y) {
       return x + y;
    }

    public void dontExposeMe() {
       throw new RuntimeException();
    }
}

Its a simple class, in real life the class won’t be so simple. But it’s easy to understand the functionality with this. Now, in the context file write the following :

<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
<!--service:jmx:jmxmp://localhost:9875  -->
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
            <property name="server" ref="mbeanServer"/>
</bean>  
     
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" depends-on="serverConnector">
       <property name="beans">
         <map>
           <entry key="bean:name=testBean1" value-ref="testBean"/>
         </map>
       </property>
       
       <property name="server" ref="mbeanServer"/>
       
     </bean>

 <bean id="testBean" class="org.jinos.spring.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
 </bean>

This will simply expose the server at “service:jmx:jmxmp://localhost:9875”. For this you will need the jmxremote_optional.jar. You can get the jar from this link. “http://www.java2s.com/Code/Jar/j/Downloadjmxremoteoptionaljar.htm”. Before running this app make sure you have this jar in your buildPath.

To access this server from the Jconsole. just go to the JDK lib dir for my pc it is “C:\Program Files\Java\jdk1.6.0_27\lib”, make sure here is the jconsole.jar. Drop the jmxremote_optional jar here. and run this command :
java -classpath jconsole.jar;jmxremote_optional.jar sun.tools.jconsole.JConsole

Then your JConsole will run and from here you can pick the attribute (say age for this mbean server). In the server console you can see that the value has been changed.

Well, Jconsole is a very good to monitor and test such scenarios. But in real scenario, we have to make our own client which will change the value of the server. For the JMX client, the xml file will be :

<bean id="clientConnector"
     class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
      <property name="serviceUrl" value="service:jmx:jmxmp://localhost:9875"/>
</bean>

   <bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
     <property name="objectName" value="bean:name=testBean1"/>
     <property name="proxyInterface" value="com.epsilon.spring.jmx.IJmxTestBean"/>
     <property name="server" ref="clientConnector"/>
   </bean>

Where, you are trying to connect with the server. In java code :

   static void testSpringJMXClient () throws Exception
   {
       context = new ClassPathXmlApplicationContext ( "applicationContextJMXClient.xml" );
       IJmxTestBean jmxTestBean = (IJmxTestBean)context.getBean ( "proxy" );
       jmxTestBean.setAge ( 500 );
       System.out.println ("Setting age : "+ jmxTestBean.getAge ( ) );
       
       
   }

Yes, what you are thinking is we can also expose the JMX using the RMI.Lets look at that :
In this case the server will be like this :

<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>

<bean id="serverConnector"
     class="org.springframework.jmx.support.ConnectorServerFactoryBean" depends-on="registry">
 <property name="objectName" value="connector:name=rmi"/>
 <property name="serviceUrl"
           value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
    <property name="server" ref="mbeanServer"/>         
</bean>

   <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
     <property name="port" value="1099"/>
   </bean>
   
     
   <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" depends-on="serverConnector">
       <property name="beans">
         <map>
           <entry key="bean:name=testBean1" value-ref="testBean"/>
         </map>
       </property>
       
       <property name="server" ref="mbeanServer"/>
       
     </bean>

 <bean id="testBean" class="org.jinos.spring.jmx.JmxTestBean">
    <property name="name" value="TEST"/>
    <property name="age" value="100"/>
 </bean>

Again for this you can connect with the jconsole. just run the jconsole using the jconsole.exe. and use the Remote process option in jconsole. In the “reomote process” field the url will be “service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector”.

And to make client for this, create the client bean as follows :

<bean id="clientConnector"
     class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
      <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>

   <bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
     <property name="objectName" value="bean:name=testBean1"/>
     <property name="proxyInterface" value="org.jinos.spring.jmx.IJmxTestBean"/>
     <property name="server" ref="clientConnector"/>
   </bean>

Client call is the same as before :

static void testSpringJMXClientRMI () throws Exception
   {

       context = new ClassPathXmlApplicationContext (  "applicationContextJMXClientRMI.xml" );

       IJmxTestBean jmxTestBean = (IJmxTestBean)context.getBean ( "proxy" );
       jmxTestBean.setAge ( 500 );
       System.out.println ("Setting age : "+ jmxTestBean.getAge ( ) );
       
       
   }

The Full source code including the service and manager is attached below. In the project source find the test class SpringBeanJMXTest.java (for JMX Server), SpringBeanJMXClientTest.java(for JMX Client).

  1. Using BeanNameAware and FactoryBean :

For this we need to implement a class which we are calling here SpringBeanCache, this is to preinitialize the bean from the code before loading the context. The cache class should be like this :

public class SpringBeanCache implements BeanNameAware, FactoryBean
{

   public static Map<String, Object> beansByName = new HashMap<String, Object> ( );

   private String beanName;

   @Override
   public void setBeanName ( String beanName )
   {
       this.beanName = beanName;
   }

   @Override
   public Object getObject ( )
   {
       return beansByName.get ( beanName );
   }

   @Override
   public Class<?> getObjectType ( )
   {
       return beansByName.get ( beanName ).getClass ( );
   }

   @Override
   public boolean isSingleton ( )
   {
       return true;
   }

}

The context xml file should be like the following :

<bean id="loginService" class="org.jinos.spring.services.LoginService">
   <property name="loginManager" ref="reloadedManger"/>
</bean>

<bean id="reloadedManger" class="org.jinos.spring.SpringBeanCache">    
</bean>

Note, here, the  bean “reloadedManger” refers to “SpringBeanCache”. So, before loading the context we have to put the manager in this cache.

The code should be like this :

    static void testRuntimeBeanAddTest ()
   {
       String[] contextFiles = {"applicationContextRuntime.xml"};
       
       LoginManager reloadedManger = new LoginManager ( );
       List<String> userList =  Arrays.asList ( "super1","super2" );
       reloadedManger.setSuperUserList ( userList );
       // now put the bean
       SpringBeanCache.beansByName.put ( "reloadedManger", reloadedManger );
       
       // now load the context
       ApplicationContext context =  new ClassPathXmlApplicationContext( contextFiles );
       
       // get the manager while loading bean
       LoginService service = (LoginService) context.getBean ( "loginService" );
       System.out.println ( "First Super User "+ service.getLoginManager ( ).getSuperUserList ( ).get ( 0 ) );
               
       
       
   }

The Full source code including the service and manager is attached below. In the project source find the test class SpringRuntimeInitializeBeanTest.java.

  1. Configurable Property change by Web Service :

Last but not the least, we can change the property using the webservice from some WS client. We can hold the configurable properties in a class and access the class through the web Service. The application will get the properties from that property holder class. It will be useful for some special type of project. I am not giving examples here on web Service- hope we will have discussion on more on web service in another article.

Č
ċ
Asraful Haque,
Apr 6, 2012, 5:18 AM
Comments