Exporting Spring Beans as JMX Mbeans

If you always want to stay on top of your production system and always want to know whats going on in your APP, you would want to configure JMX for your application.

First you need to start your JVM with the following parameters to enable any JMX client to connect to your system.

-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

This is just done without any security but in production, you would want to add authentication.

Now, add these configuration for your spring APP to automatically expose your Spring Beans as MBeans if they have the right annotations.

	<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"
		lazy-init="false">
		<property name="autodetect" value="true"></property>
		<property name="namingStrategy" ref="namingStrategy"></property>
		<property name="assembler" ref="assembler"></property>		
		<property name="server" ref="mbeanServer"></property>		
	</bean>

	<bean id="assembler"
		class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
		<property name="attributeSource" ref="attributeSource" />
	</bean>

	<bean id="namingStrategy"
		class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
		<property name="attributeSource" ref="attributeSource" />
	</bean>

	<bean id="mbeanServer" class="java.lang.management.ManagementFactory" factory-method="getPlatformMBeanServer"/>	

The ‘exporter’ will inspect all your defined Spring Beans in your configuration and see if the have Spring JMX annotations. Now you need to define your beans with Annotations.

@ManagedResource(objectName = "yourapp:name=log4jLevelChanger", description = "LOG4j Level Changer")
public class Log4jLevelChanger  {
	private static final Log log = LogFactory.getLog(Log4jLevelChanger.class);
	
	public void init() throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
		ArrayList<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
		for(MBeanServer server : servers) {
			String name = (String) server.getAttribute(MBeanServerDelegate.DELEGATE_NAME,
            "MBeanServerId");	
			
			log.info("found  server: " + name);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.vantage.callcenter.web.utils.Log4jLevelChanger#setLogLevel(java.lang
	 * .String, java.lang.String)
	 */
	@ManagedOperation
	@ManagedOperationParameters({
	@ManagedOperationParameter(description="Name of the Logger to configure", name="loggerName"),
	@ManagedOperationParameter(description="Level ofthe Logger to set", name="level")
	})
	public void setLogLevel(
			
			String loggerName, 
			
			String level) {
		if ("debug".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.DEBUG);
		} else if ("info".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.INFO);
		} else if ("error".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.ERROR);
		} else if ("fatal".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.FATAL);
		} else if ("warn".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.WARN);
		}
	}

        //Read the log4j.properties to reconfigure the logging levels.
	@ManagedOperation
	public boolean refreshLogging() {
		try {
			Log4jConfigurer.initLogging("classpath:log4j.properties");			
			return true;
		} catch (FileNotFoundException e) {
			return false;
		}
	}
	
	
	@ManagedOperation
	@ManagedOperationParameters ({
		@ManagedOperationParameter(description="Name of the Logger to get the Level", name="loggerName")	
	})
	public String getLogLevel(
			
			String loggerName) {
		log.debug("getLogLevel -> loggerName: " + loggerName);
		Logger logger = Logger.getLogger(loggerName);
		
		if(logger != null) {
			if(logger.getLevel() != null) {
				return logger.getLevel().toString();
			}
		}
		
		return Logger.getRootLogger().getLevel().toString();
	}
}

and define it in your application-context.xml as a regular bean

<bean id="log4jLevelChanger"  class="com.test.Log4jLevelChanger"></bean>

Thats it! Now you can open any Standard JMX client (JConsole / VisualVM / JManage) to inspect the bean and changing log levels!

You can also add Mbean Annotations to any regular Bean that you wish to inspect at runtime. For example, you can add it to the bean that manages your threadpool to see its utilization at runtime, you can add it to any in memory cache to find out memory leaks. If you think closely, the possibilities can be endless!

Spring Session Scoped Bean

I have always hated direct usages of “HttpSession”. You know how tricky it becomes when you want to do advanced things like session replications / caching etc. I have previously used home grown session factory abstraction. But just recently I came across Spring “Session Scoped Bean”. So far, we have only used dependency injection of spring beans which had only 2 scopes – singleton & prototype. But now spring supports custom scopes for beans and one of them is “Session”. It means that this bean is created when a new HttpSession is created and preserved as long as the HttpSession is valid. Spring uses AOP to extract the sessionId from the httprequest and manage the lifecycle of the bean. So instead of putting your object directly into the session, you get to put them in an injected POJO based placeholder. This makes it easy to do the testing and getting rid of HttpSession dependency. So when time comes, you can use solutions like “terracotta” to distribute your session without changing your code! Isn’t that wonderful! Another niche Spring trick!

The details of how to setup the configurations can be found here. I followed the exact steps and it worked flawlessly.

Asterisk-java + Spring

Those who don’t know about Asterisk Java, its a wonderful java library to talk to a asterisk server through AGI & AMI. We are using it heavily to control our Asterisk box from our “main application”. Our main application is a full fledged spring application running on tomcat. At first we were running the asterisk java components (AGIServer , AgiScripts etc) as a normal java application but soon we wanted our AgiScripts to talk to our spring beans Or even better, we want our agi scripts to be Spring beans.

The beauty of the spring and asterisk-java library is that they were made for each other! Asterisk java library was written in wonderful object-oriented way with clear separation on dependencies. So it was very easy to define the AGIServer and all its dependencies as Spring Bean. So We can inject the MappingStrategy with our own implementation of “BeanNameAwareAGIMappingStrategy” which implements ApplicationContextAware looks like this

 @Override
 protected AgiScript createAgiScriptInstance(String beanName) {
        Object bean = applicationContext.getBean(beanName)
        if(bean == null) {
           throw new IllegalArgumentException(\"No bean with name: [\" + beanName +\"] found. Make sure that you have all beans defined which are there in your fastagi-mapping.properties\");
        }

        if(!(bean instanceof AgiScript)) {
          throw new IllegalArgumentException(\"spring bean : \" + beanName + \" must implement org.asteriskjava.fastagi.AgiScript interface\");
        }

        return (AgiScript) bean;
}

So now, instead of defining fully qualified classnames in your “fastagi-mapping.properties”, you only give the bean name and our mapping strategy looks it up from the application context. So your plain agi scripts can talk to your dao, services and do all sort of fancy things! Isn’t that wonderful!