Оптимизация spring jmx

Spring по умолчанию позволяет настроить экспорт бинов в jmx. Сделано это через удобные аннотации @ManagedResource. Однако существует сценарий при котором поведение по умолчанию не совсем подходит. Рассмотрим этот случай:

  • spring context лениво инициализируется. Очень удобно если есть некоторый db-context.xml в котором описаны все Datasource. Соответственно инициализируются только те которые используются. Также очень удобно при ограниченных ресурсах. fail-fast + старт только необходимого.
  • org.springframework.jmx.export.MBeanExporter умеет инициализировать JMX бины для ленивых spring бинов. Как это происходит: если spring бин - лениво инициализируется, то создаётся proxy через cglib который и будет jmx бином. При первом обращении к его методам/аттрибутам происходит инициализация spring бина.

Проблема:

  • возможна инициализация ненужных соединений. Список бинов содержит все возможные jmx бины.

Решение:

  • Необходимо создать BeanPostProcessor для контроля уже проинициализированных бинов.

Например:

import java.util.ArrayList;  
import java.util.List;  
  
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.BeanPostProcessor;  
  
public class StartedBeansAwarePostProcessor implements BeanPostProcessor {  
  
 private final List<String> beanNames = new ArrayList<String>();  
  
 @Override  
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
  return bean;  
 }  
  
 @Override  
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
  beanNames.add(beanName);  
  return bean;  
 }  
   
 public boolean isStarted(String beanName) {  
  return beanNames.contains(beanName);  
 }  
  
} 

После этого необходимо создать свой Assembler. Например:

import org.springframework.beans.factory.annotation.Required;  
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;  
import org.springframework.jmx.support.JmxUtils;  
  
public class LazyAssembler extends MetadataMBeanInfoAssembler {  
  
 private StartedBeansAwarePostProcessor startedBeans;  
  
 @Override  
 public boolean includeBean(Class beanClass, String beanName) {  
  if (startedBeans.isStarted(beanName)) {  
   if (isMBean(beanClass)) {  
    return true;  
   }  
   return super.includeBean(beanClass, beanName);  
  }  
  return false;  
 }  
  
 @Required  
 public void setStartedBeans(StartedBeansAwarePostProcessor startedBeans) {  
  this.startedBeans = startedBeans;  
 }  
  
 private boolean isMBean(Class beanClass) {  
  return JmxUtils.isMBean(beanClass);  
 }  
  
} 

И сконфигурировать spring контекст:

<bean id="lazyAssembler" class="LazyAssembler" p:attributeSource-ref="jmxAttributeSource">  
 <property name="startedBeans" ref="startedBeanAwarePostProcessor" />  
</bean>  
<bean id="startedBeanAwarePostProcessor" class="StartedBeansAwarePostProcessor" />  
<bean name="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean" p:locateExistingServerIfPossible="true" />  
<bean id="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource" />  
<bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter"  
 p:server-ref="mbeanServer">  
 <property name="assembler" ref="lazyAssembler" />  
 <property name="autodetectMode" value="2" />  
 <property name="namingStrategy">  
  <bean class="org.springframework.jmx.export.naming.MetadataNamingStrategy">  
   <property name="attributeSource" ref="jmxAttributeSource" />  
   <property name="defaultDomain" value="domain" />  
  </bean>  
 </property>  
</bean> 

Особое внимание на параметр: autodetectMode. Он должен обязательно быть равен 2, иначе MBeanExporter будет игнорировать Assembler при принятии решении о том включать бин или нет. Теперь можно инициализировать контекст. Например:

ctx.getBean(SomeBean.class); //инициализация корневого бина. По зависимостям должна инициализировать все бины необходимые для работы приложения. StartedBeansAwarePostProcessor запоминает все проинициализированные бины.  
ctx.getBean("mbeanExporter"); //инициализация jmx бинов. Выполнять строго после инициализации всех бинов приложения.