package com.kendelong.util.spring;
import java.util.Map;
import javax.management.ObjectName;
import org.aopalliance.aop.Advice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.jmx.export.MBeanExportException;
import org.springframework.jmx.export.MBeanExporter;
import com.kendelong.util.monitoring.webservice.ExternalNameElementComputer;
/**
* This bean can export AspectJ-style aspects to JMX when registered. For example:
*
* <pre>
* {@code
<bean class="com.kendelong.util.circuitbreaker.CircuitBreakerAspect" scope="prototype"/>
<bean class="com.kendelong.util.spring.JmxExportingAspectPostProcessor" lazy-init="false">
<property name="mbeanExporter" ref="mbeanExporter"/>
<property name="annotationToServiceNames">
<map>
<entry key="com.kendelong.util.circuitbreaker.CircuitBreakerAspect" value="circuitbreaker"/>
</map>
</property>
<property name="jmxDomain" value="app.mystuff"/>
</bean>
}
</pre>
*
* Important properties to set are the jmxDomain to use for the ONames of the MBeans,
* and the value of the map which will be also used for the OName. OName structure is
* jmxDomain:service=[map value],bean=[original advised bean name]
*
* @author Ken DeLong
*
*/
public class JmxExportingAspectPostProcessor implements BeanPostProcessor
{
private MBeanExporter mbeanExporter;
private Map<Class<?>, String> annotationToServiceNames;
private String jmxDomain;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ExternalNameElementComputer nameElementComputer = new ExternalNameElementComputer();
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
boolean advised = bean instanceof Advised;
if(advised)
{
Advised advisedBean = (Advised) bean;
Advisor[] advisors = advisedBean.getAdvisors();
for(Advisor advisor : advisors)
{
Advice advice = advisor.getAdvice();
if(advice instanceof AbstractAspectJAdvice)
{
AbstractAspectJAdvice aaja = (AbstractAspectJAdvice) advice;
Class<?> aspectPojoClass = aaja.getAspectJAdviceMethod().getDeclaringClass();
String serviceType = annotationToServiceNames.get(aspectPojoClass);
if(serviceType != null)
{
ObjectName oname;
String onameString = getDomain(bean) + "." + serviceType + ":bean=" + beanName;
try
{
/* as long as the aspect is marked prototype, each advised bean
* gets a new instance, as seen by the return value of this next
* method call. I've also checked that successive calls to the
* getAspectInstance() method return the same object.
*/
Object target = aaja.getAspectInstanceFactory().getAspectInstance();
oname = new ObjectName(onameString);
registerMBean(oname, target, beanName);
}
catch(Exception e)
{
logger.error("Cannot register mbean with oname [" + onameString +"]", e);
}
}
}
}
}
return bean;
}
private String getDomain(Object bean)
{
String webserviceNameElement = nameElementComputer.computeExternalNameElement(bean.getClass());
if(webserviceNameElement == null)
return jmxDomain;
else
return jmxDomain + ".webservice." + webserviceNameElement;
}
protected void registerMBean(ObjectName oname, Object target, String beanName)
{
try
{
// For some reason, we some objects (notably controllers) get registered 2-3 times. Subsequent registrations throw exceptions.
// unregisterManagedResource() is very tolerant and won't blow up if the object isn't registered.
// With this "last one wins" strategy, your dev environment stays clean when JRebel reloads the web context.
mbeanExporter.unregisterManagedResource(oname);
mbeanExporter.registerManagedResource(target, oname);
logger.info("Exported " + beanName + " to JMX as " + oname.toString());
}
catch(MBeanExportException e)
{
logger.warn("Error registering MBean [" + oname + "]; cause: [" + e.getCause() + "]");
}
}
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException
{
return bean;
}
public MBeanExporter getMbeanExporter()
{
return mbeanExporter;
}
public void setMbeanExporter(MBeanExporter mbeanExporter)
{
this.mbeanExporter = mbeanExporter;
}
public Map<Class<?>, String> getAnnotationToServiceNames()
{
return annotationToServiceNames;
}
public void setAnnotationToServiceNames(Map<Class<?>, String> annotationToServiceNames)
{
this.annotationToServiceNames = annotationToServiceNames;
}
public String getJmxDomain()
{
return jmxDomain;
}
public void setJmxDomain(String jmxDomain)
{
this.jmxDomain = jmxDomain;
}
}