Resolving Spring Circular Reference Problem with proxied class

Have you ever run into a problem like this ?

"Error creating bean with name 'beanA': Bean with name 'beanA' has been injected into other beans (beanB) in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."

Let me first explain why this happens:

You have a circular dependency between your BeanA & BeanB. In such cases, if one of your bean does not have any AOP applied to it (transaction / audit-trail etc) , spring behaves fine. However, if both of your beans are proxied or in other word, has AOP advice attached to it (mostly transaction advice) , you have a problem. This is because spring initially injects the raw version of your bean beanA in the other bean beanB but later adds Aspect into it. So If your beanA has any aspect (transaction / audit-trail ) , they will not be in action when beanB uses beanA. Spring does not allow this by default and gives you the exception.

In a perfect world and in a perfect project, you wouldn’t want circular dependency in your beans. But as we know, the world is not perfect and sometimes you just have to do it (obviously within same module).

How do I do it you ask? Fortunately you have a few options :

1. Set “allowEagerInit” to false in your application context.
2. Break the circular dependency for spring by not injecting one of the bean with application context and get the bean @ runtime by calling “applicationContext.getBean”
3. Write your own BeanFactory to allow circular dependency ( If you are ok with the side effects )

I am going to describe the 3rd method in details. With spring, its always very simple to add hook in necessary points. First write a class like this:

public class AllowRawInjectionDespiteWrappingXMLWebApplicationContext extends
		XmlWebApplicationContext {

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
			throws IOException {
		beanFactory.setAllowRawInjectionDespiteWrapping(true);
		super.loadBeanDefinitions(beanFactory);
	}

	 
}

All I am doing here is changing the default value “allowRawInjectionDespiteWrapping” to true to let spring know I am OK with raw injection of my AOP beans.

Next, Change your default contextClass “XmlWebApplicationContext” class name which spring uses by adding the following in your web.xml file.

	<context-param>
	        <param-name>contextClass</param-name>
	        <param-value>com.myapp.AllowRawInjectionDespiteWrappingXMLWebApplicationContext</param-value>
	</context-param>

All that trouble to break a design rule (avoid circular dependency)..Fair enough !!

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!