INT-3330 EnableIntegrationMBeanExport Annotation

JIRA: https://jira.spring.io/browse/INT-3330

INT-3330: Enable SpEL evaluation

INT-3330: Polishing for `MBeanExporterHelper`

INT-3330: Fix `errorChannel` early access

INT-3330: Polishing according PR comments

Polishing - copyrights, author, docs
This commit is contained in:
Artem Bilan
2014-03-18 20:57:36 +02:00
committed by Gary Russell
parent 1d6e80ecd0
commit 97c270c0e2
23 changed files with 643 additions and 196 deletions

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jmx.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;
/**
* Enables default exporting for Spring Integration components in an existing application, as
* well as well all {@code @ManagedResource} annotated beans.
*
* <p>The resulting {@link org.springframework.integration.monitor.IntegrationMBeanExporter}
* bean is defined under the name {@code integrationMbeanExporter}. Alternatively, consider defining a
* custom {@link org.springframework.integration.monitor.IntegrationMBeanExporter} bean explicitly.
*
* <p>This annotation is modeled after and functionally equivalent to Spring XML's
* {@code <int-jmx:mbean-export/>} element.
*
* @author Artem Bilan
* @since 4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(IntegrationMBeanExportConfiguration.class)
public @interface EnableIntegrationMBeanExport {
/**
* The default domain to use when generating JMX ObjectNames.
* Supports property placeholders (e.g. {@code ${project.domain}).
*/
String defaultDomain() default "";
/**
* The bean name of the MBeanServer to which MBeans should be exported. Default is to
* use the platform's default MBeanServer.
* Supports property placeholders (e.g. {@code ${project.mbeanServer})
* and SpEL expression (e.g. {@code #{mbeanServer}).
*/
String server() default "";
/**
* The policy to use when attempting to register an MBean under an
* {@link javax.management.ObjectName} that already exists. Defaults to
* {@link org.springframework.jmx.support.RegistrationPolicy#FAIL_ON_EXISTING}.
*/
RegistrationPolicy registration() default RegistrationPolicy.FAIL_ON_EXISTING;
/**
* List of simple patterns for component names to register (defaults to '*').
* The pattern is applied to all components before they are registered, looking for a match on
* the 'name' property of the ObjectName. A MessageChannel and a MessageHandler (for instance)
* can share a name because they have a different type, so in that case they would either both
* be included or both excluded.
* Supports property placeholders (e.g. {@code ${managed.components}). Can be applied for each element.
*/
String[] managedComponents() default "*";
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jmx.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.jmx.support.RegistrationPolicy;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@code @Configuration} class that registers a {@link IntegrationMBeanExporter} bean.
* <p/>
* <p>This configuration class is automatically imported when using the
* {@link EnableIntegrationMBeanExport} annotation. See its javadoc for complete usage details.
*
* @author Artem Bilan
* @since 4.0
*/
@Configuration
public class IntegrationMBeanExportConfiguration implements ImportAware, EnvironmentAware, BeanFactoryAware {
private static final String MBEAN_EXPORTER_NAME = "integrationMbeanExporter";
private AnnotationAttributes attributes;
private BeanFactory beanFactory;
private BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
private BeanExpressionContext expressionContext;
private Environment environment;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.resolver = ((ConfigurableListableBeanFactory) beanFactory).getBeanExpressionResolver();
this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null);
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> map = importMetadata.getAnnotationAttributes(EnableIntegrationMBeanExport.class.getName());
this.attributes = AnnotationAttributes.fromMap(map);
Assert.notNull(this.attributes,
"@EnableIntegrationMBeanExport is not present on importing class " + importMetadata.getClassName());
}
@Bean(name = MBEAN_EXPORTER_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public IntegrationMBeanExporter mbeanExporter() {
IntegrationMBeanExporter exporter = new IntegrationMBeanExporter();
exporter.setRegistrationPolicy(this.attributes.<RegistrationPolicy>getEnum("registration"));
setupDomain(exporter);
setupServer(exporter);
setupComponentNamePatterns(exporter);
return exporter;
}
private void setupDomain(IntegrationMBeanExporter exporter) {
String defaultDomain = this.attributes.getString("defaultDomain");
if (defaultDomain != null && this.environment != null) {
defaultDomain = this.environment.resolvePlaceholders(defaultDomain);
}
if (StringUtils.hasText(defaultDomain)) {
exporter.setDefaultDomain(defaultDomain);
}
}
private void setupServer(IntegrationMBeanExporter exporter) {
String server = this.attributes.getString("server");
if (server != null && this.environment != null) {
server = this.environment.resolvePlaceholders(server);
}
if (StringUtils.hasText(server)) {
MBeanServer bean = null;
if (server.startsWith("#{") && server.endsWith("}")) {
bean = (MBeanServer) this.resolver.evaluate(server, this.expressionContext);
}
else {
bean = this.beanFactory.getBean(server, MBeanServer.class);
}
exporter.setServer(bean);
}
else {
exporter.setServer(MBeanServerFactory.createMBeanServer());
}
}
private void setupComponentNamePatterns(IntegrationMBeanExporter exporter) {
List<String> patterns = new ArrayList<String>();
String[] managedComponents = this.attributes.getStringArray("managedComponents");
for (String managedComponent : managedComponents) {
String pattern = this.environment.resolvePlaceholders(managedComponent);
patterns.addAll(StringUtils.commaDelimitedListToSet(pattern));
}
exporter.setComponentNamePatterns(patterns.toArray(new String[patterns.size()]));
}
}

View File

@@ -18,6 +18,8 @@ package org.springframework.integration.jmx.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.integration.config.IntegrationConfigurationInitializer;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
@@ -37,11 +39,9 @@ public class JmxIntegrationConfigurationInitializer implements IntegrationConfig
}
private void registerMBeanExporterHelperIfNecessary(ConfigurableListableBeanFactory beanFactory) {
if (!beanFactory.getBeansOfType(IntegrationMBeanExporter.class, false, false).isEmpty()) {
MBeanExporterHelper mBeanExporterHelper = new MBeanExporterHelper();
mBeanExporterHelper.postProcessBeanFactory(beanFactory);
beanFactory.registerSingleton(MBEAN_EXPORTER_HELPER_BEAN_NAME, mBeanExporterHelper);
beanFactory.initializeBean(mBeanExporterHelper, MBEAN_EXPORTER_HELPER_BEAN_NAME);
if (beanFactory.getBeanNamesForType(IntegrationMBeanExporter.class).length > 0) {
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(MBEAN_EXPORTER_HELPER_BEAN_NAME,
new RootBeanDefinition(MBeanExporterHelper.class));
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
@@ -20,42 +20,66 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.Ordered;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Most likely a temporary class mainly needed to address issue described in INT-2307.
* It helps in eliminating conflicts when more than one MBeanExporter is present. It creates a list
* of bean names that will be exported by the IntegrationMBeanExporter and merges it with the list
* It helps in eliminating conflicts when more than one MBeanExporter is present. It creates a list
* of bean names that will be exported by the IntegrationMBeanExporter and merges it with the list
* of 'excludedBeans' of MBeanExporter so it will not attempt to export them again.
*
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.1
*
*/
class MBeanExporterHelper implements BeanFactoryPostProcessor,
BeanPostProcessor, Ordered, BeanFactoryAware {
class MBeanExporterHelper implements BeanPostProcessor, Ordered, BeanFactoryAware, InitializingBean {
private final static String EXCLUDED_BEANS_PROPERTY_NAME = "excludedBeans";
private final static String SI_ROOT_PACKAGE = "org.springframework.integration.";
private final Set<String> siBeanNames = new HashSet<String>();
private volatile BeanFactory beanFactory;
private volatile DefaultListableBeanFactory beanFactory;
private volatile boolean capturedAutoChannelCandidates;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory);
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.beanFactory != null) {
String[] beanNames = this.beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition def = this.beanFactory.getBeanDefinition(beanName);
String className = def.getBeanClassName();
if (className == null && def.getSource() instanceof StandardMethodMetadata) {
className = ((StandardMethodMetadata) def.getSource()).getIntrospectedMethod().getReturnType().getName();
}
if (StringUtils.hasText(className)){
if (className.startsWith(SI_ROOT_PACKAGE) && !(className.endsWith(IntegrationMBeanExporter.class.getName()))){
siBeanNames.add(beanName);
}
}
}
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!this.capturedAutoChannelCandidates && this.beanFactory != null) {
Object autoCreateChannelCandidates = beanFactory.getBean("$autoCreateChannelCandidates");
@@ -77,30 +101,18 @@ class MBeanExporterHelper implements BeanFactoryPostProcessor,
}
mbeDfa.setPropertyValue(EXCLUDED_BEANS_PROPERTY_NAME, siBeanNames);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName);
String className = bd.getBeanClassName();
if (StringUtils.hasText(className)){
if (className.startsWith(SI_ROOT_PACKAGE) && !(className.endsWith(IntegrationMBeanExporter.class.getName()))){
siBeanNames.add(beanName);
}
}
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}