Java Coder们都知道,Java提供了JMX(Java Management Extensions) attach的机制(如JConsole),能够动态获取JVM运行时的一些信息。咱们能够自定义MBean,来暴露指定的一些参数值,如DB链接数等。为方便故障排查,咱们添加了一些DB相关的metrics,因而在Spring配置文件里面添加了以下代码html
<bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" depends-on="dataSource"> <property name="beans"> <map> <entry key="Catalina:type=DataSource" value="#{dataSource.createPool().getJmxPool()}" /> </map> </property> </bean>
MBeanExporter是Spring提供的一个工具类,能够用来注册自定义的MBean,只须要将目标类以map键值对的形式添加到beans这个属性里面。经过Jmx咱们能够访问到MBean上的Public参数,从而拿到运行时的metrics。 java
上述是JConsole的一个截图,最后一个Tab就是由JDK默认暴露出来的一些MBean的信息。web
经过Spring的MBeanExporter注册自定义的MBean到JVM,结果工程启动报错,堆栈以下:spring
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmxExporter' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [org.apache.tomcat.jdbc.pool.jmx.ConnectionPool@265c255a] with key 'Catalina:type=DataSource'; nested exception is javax.management.InstanceAlreadyExistsException: Catalina:type=DataSource at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1553) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:703) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482) at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403) at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306) at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4792) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5256) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1420) at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1410) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
报的异常是InstanceAlreadyExistsException。找到MBeanExporter的源码:apache
public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExportOperations, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { // 自定义的MBean,放在一个Map里面保存 private Map<string, object> beans; public void setBeans(Map<string, object> beans) { this.beans = beans; } }
它实现了InitializingBean接口,该接口只有一个方法afterPropertiesSet。做为Spring生命周期的重要一环,当Spring Bean实例化好而且设置好属性以后,会调用这个方法:tomcat
[@Override](https://my.oschina.net/u/1162528) public void afterPropertiesSet() { // 确保MBeanServer存在,全部的MBean都是依附于MBeanServer的 if (this.server == null) { this.server = JmxUtils.locateMBeanServer(); } try { logger.info("Registering beans for JMX exposure on startup"); // 调用registerBeans方法,注册配置文件中的Beans registerBeans(); registerNotificationListeners(); } catch (RuntimeException ex) { // 若是出错,将bean注销 unregisterNotificationListeners(); unregisterBeans(); throw ex; } }
能够看到,最终会走到registerBeans方法,去注册Spring配置文件中的Bean。中间省略注册的一部分过程,只看最终部分代码,最终会走到父类MBeanRegistrationSupport的doRegister方法:oracle
public class MBeanRegistrationSupport { // registrationPolicy默认是FAIL_ON_EXISTING,也就是当重复注册的时候,会失败 private RegistrationPolicy registrationPolicy = RegistrationPolicy.FAIL_ON_EXISTING; protected void doRegister(Object mbean, ObjectName objectName) throws JMException { ObjectName actualObjectName; synchronized (this.registeredBeans) { ObjectInstance registeredBean = null; try { // 真正注册MBean的地方,将此MBean注册给MBeanServer registeredBean = this.server.registerMBean(mbean, objectName); } // 当重复MBean重复注册的时候,会抛出InstanceAlreadyExistsException异常 catch (InstanceAlreadyExistsException ex) { // 当抛出重复注册异常的时候会ignore,单单打印一个日志 if (this.registrationPolicy == RegistrationPolicy.IGNORE_EXISTING) { logger.debug("Ignoring existing MBean at [" + objectName + "]"); } // 当重复注册的时候,会替换掉原有的 else if (this.registrationPolicy == RegistrationPolicy.REPLACE_EXISTING) { try { logger.debug("Replacing existing MBean at [" + objectName + "]"); // 将原有的MBean注销掉 this.server.unregisterMBean(objectName); // 注册新的MBean registeredBean = this.server.registerMBean(mbean, objectName); } catch (InstanceNotFoundException ex2) { logger.error("Unable to replace existing MBean at [" + objectName + "]", ex2); throw ex; } } else { throw ex; } } } } }
真正注册MBean的地方是MBeanServer的registerMBean方法,这里不展开细说,最终MBean会放在一个Map里面,当要注册的MBean的key已经存在的时候,会抛出InstanceAlreadyExistsException异常。app
MBeanRegistrationSupport中有一个重要参数registrationPolicy,有三个值分别是FAIL_ON_EXISTING(出异常时注册失败),IGNORE_EXISTING(忽略异常)和REPLACE_EXISTING(出异常时替换原有的),而默认值是FAIL_ON_EXISTING,也就是说,当出现MBean重复注册的时候,会将异常InstanceAlreadyExistsException直接抛出去。dom
确实,因为项目须要,咱们的Tomcat里面配置了两个工程实例,致使了MBean注册冲突。ide
找到重复注册的MBean,确认是否是真的有必要存在。若是不是,能够经过修改配置或者删除多余的MBean实例。
对于经过MBeanExporter注册的case,修改了上述registrationPolicy为就能解决问题,如修改成IGNORE_EXISTING:
<bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" depends-on="dataSource"> <property name="registrationPolicy" value="IGNORE_EXISTING"></property> <property name="beans"> <map> <entry key="Catalina:type=DataSource" value="#{dataSource.createPool().getJmxPool()}" /> </map> </property> </bean>
若是是经过注解的形式注入的,也能够手动调用MBeanExporter的setRegistrationPolicy方法。
在Java6以后,Jmx是默认打开的。若是你确实不须要这个功能,name能够将它关闭。如Spring boot工程能够在application.properties中添加如下配置来关闭: > spring.jmx.enabled = false
或者参考这篇文档。
MBeanServer注册MBean的时候能够指定一个domain name,对应一个命名空间,
public interface MBeanServer extends MBeanServerConnection { // name变量即为domain name public ObjectInstance registerMBean(Object object, ObjectName name) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException; }
如MBeanExporter中只需将MBean的Key值设置成惟一的即可以。 如spring boot能够在application.properties中添加如下配置设置domain name: > spring.jmx.default_domain = custom.domain
其余状况能够参考这里
其实InstanceAlreadyExistsException是一个比较广泛的问题,一般是因为在同一个JVM Instance中注册了多个相同Key的MBean致使的,由于同一个Tomcat实例里面只容许存在一个相同的MBean。
若是是配置错误致使Instance启动了屡次,则要找到相关的错误配置。若是是须要起多个Instance,则能够经过关闭Jmx,修改registrationPolicy或将MBean注册到不一样的domain name来解决错误。</string,></string,>