Commit becbc00a authored by Andy Wilkinson's avatar Andy Wilkinson

Use configured ObjectMapper, if available, in all EndpointMBeans

Prior to this commit, every EndpointMBean used its own ObjectMapper.
Each of these ObjectMappers was created using new ObjectMapper() with
no opportunity for configuration.

This commit uses the ObjectMapper from the application context and
shares it among all EndpointMBeans. This gives the user control over
the ObjectMapper’s configuration using spring.jackson.* properties,
their own Jackson2ObjectMapperBuilder bean, etc. In the absence of an
ObjectMapper in the application context a single ObjectMapper is
instantiated and is used by all EndpointMBeans instead.

To allow the ObjectMapper to be shared, a number of constructors have
been overloaded to also take the ObjectMapper as a parameter. In these
cases the old constructor has been preserved for backwards compatibility
but has been deprecated.

Closes gh-2393
parent 88502869
...@@ -31,11 +31,14 @@ import org.springframework.context.annotation.Bean; ...@@ -31,11 +31,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} to enable JMX export for * {@link EnableAutoConfiguration Auto-configuration} to enable JMX export for
* {@link Endpoint}s. * {@link Endpoint}s.
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson
*/ */
@Configuration @Configuration
@ConditionalOnExpression("${endpoints.jmx.enabled:true} && ${spring.jmx.enabled:true}") @ConditionalOnExpression("${endpoints.jmx.enabled:true} && ${spring.jmx.enabled:true}")
...@@ -44,11 +47,14 @@ import org.springframework.util.StringUtils; ...@@ -44,11 +47,14 @@ import org.springframework.util.StringUtils;
public class EndpointMBeanExportAutoConfiguration { public class EndpointMBeanExportAutoConfiguration {
@Autowired @Autowired
EndpointMBeanExportProperties properties = new EndpointMBeanExportProperties(); private EndpointMBeanExportProperties properties = new EndpointMBeanExportProperties();
@Autowired(required = false)
private ObjectMapper objectMapper;
@Bean @Bean
public EndpointMBeanExporter endpointMBeanExporter(MBeanServer server) { public EndpointMBeanExporter endpointMBeanExporter(MBeanServer server) {
EndpointMBeanExporter mbeanExporter = new EndpointMBeanExporter(); EndpointMBeanExporter mbeanExporter = new EndpointMBeanExporter(this.objectMapper);
String domain = this.properties.getDomain(); String domain = this.properties.getDomain();
if (StringUtils.hasText(domain)) { if (StringUtils.hasText(domain)) {
mbeanExporter.setDomain(domain); mbeanExporter.setDomain(domain);
......
/* /*
* Copyright 2013 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -20,19 +20,28 @@ import org.springframework.boot.actuate.endpoint.Endpoint; ...@@ -20,19 +20,28 @@ import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.jmx.export.annotation.ManagedResource;
import com.fasterxml.jackson.databind.ObjectMapper;
/** /**
* Simple wrapper around {@link Endpoint} implementations that provide actuator data of * Simple wrapper around {@link Endpoint} implementations that provide actuator data of
* some sort. * some sort.
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson
*/ */
@ManagedResource @ManagedResource
public class DataEndpointMBean extends EndpointMBean { public class DataEndpointMBean extends EndpointMBean {
@Deprecated
public DataEndpointMBean(String beanName, Endpoint<?> endpoint) { public DataEndpointMBean(String beanName, Endpoint<?> endpoint) {
super(beanName, endpoint); super(beanName, endpoint);
} }
public DataEndpointMBean(String beanName, Endpoint<?> endpoint,
ObjectMapper objectMapper) {
super(beanName, endpoint, objectMapper);
}
@ManagedAttribute(description = "Invoke the underlying endpoint") @ManagedAttribute(description = "Invoke the underlying endpoint")
public Object getData() { public Object getData() {
return convert(getEndpoint().invoke()); return convert(getEndpoint().invoke());
......
/* /*
* Copyright 2013 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -31,18 +31,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; ...@@ -31,18 +31,26 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* Simple wrapper around {@link Endpoint} implementations to enable JMX export. * Simple wrapper around {@link Endpoint} implementations to enable JMX export.
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson
*/ */
@ManagedResource @ManagedResource
public class EndpointMBean { public class EndpointMBean {
private final Endpoint<?> endpoint; private final Endpoint<?> endpoint;
private final ObjectMapper mapper = new ObjectMapper(); private final ObjectMapper mapper;
@Deprecated
public EndpointMBean(String beanName, Endpoint<?> endpoint) { public EndpointMBean(String beanName, Endpoint<?> endpoint) {
this(beanName, endpoint, new ObjectMapper());
}
public EndpointMBean(String beanName, Endpoint<?> endpoint, ObjectMapper objectMapper) {
Assert.notNull(beanName, "BeanName must not be null"); Assert.notNull(beanName, "BeanName must not be null");
Assert.notNull(endpoint, "Endpoint must not be null"); Assert.notNull(endpoint, "Endpoint must not be null");
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.endpoint = endpoint; this.endpoint = endpoint;
this.mapper = objectMapper;
} }
@ManagedAttribute(description = "Returns the class of the underlying endpoint") @ManagedAttribute(description = "Returns the class of the underlying endpoint")
......
/* /*
* Copyright 2013-2014 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -49,12 +49,15 @@ import org.springframework.jmx.export.naming.SelfNaming; ...@@ -49,12 +49,15 @@ import org.springframework.jmx.export.naming.SelfNaming;
import org.springframework.jmx.support.ObjectNameManager; import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
/** /**
* {@link ApplicationListener} that registers all known {@link Endpoint}s with an * {@link ApplicationListener} that registers all known {@link Endpoint}s with an
* {@link MBeanServer} using the {@link MBeanExporter} located from the application * {@link MBeanServer} using the {@link MBeanExporter} located from the application
* context. * context.
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson
*/ */
public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecycle, public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecycle,
BeanFactoryAware, ApplicationContextAware { BeanFactoryAware, ApplicationContextAware {
...@@ -91,8 +94,14 @@ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecyc ...@@ -91,8 +94,14 @@ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecyc
private Properties objectNameStaticProperties = new Properties(); private Properties objectNameStaticProperties = new Properties();
private final ObjectMapper objectMapper;
public EndpointMBeanExporter() { public EndpointMBeanExporter() {
super(); this(null);
}
public EndpointMBeanExporter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper;
setAutodetect(false); setAutodetect(false);
setNamingStrategy(this.defaultNamingStrategy); setNamingStrategy(this.defaultNamingStrategy);
setAssembler(this.assembler); setAssembler(this.assembler);
...@@ -168,9 +177,9 @@ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecyc ...@@ -168,9 +177,9 @@ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecyc
protected EndpointMBean getEndpointMBean(String beanName, Endpoint<?> endpoint) { protected EndpointMBean getEndpointMBean(String beanName, Endpoint<?> endpoint) {
if (endpoint instanceof ShutdownEndpoint) { if (endpoint instanceof ShutdownEndpoint) {
return new ShutdownEndpointMBean(beanName, endpoint); return new ShutdownEndpointMBean(beanName, endpoint, this.objectMapper);
} }
return new DataEndpointMBean(beanName, endpoint); return new DataEndpointMBean(beanName, endpoint, this.objectMapper);
} }
@Override @Override
......
/* /*
* Copyright 2013 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -21,18 +21,27 @@ import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; ...@@ -21,18 +21,27 @@ import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.jmx.export.annotation.ManagedResource;
import com.fasterxml.jackson.databind.ObjectMapper;
/** /**
* Special endpoint wrapper for {@link ShutdownEndpoint}. * Special endpoint wrapper for {@link ShutdownEndpoint}.
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson
*/ */
@ManagedResource @ManagedResource
public class ShutdownEndpointMBean extends EndpointMBean { public class ShutdownEndpointMBean extends EndpointMBean {
@Deprecated
public ShutdownEndpointMBean(String beanName, Endpoint<?> endpoint) { public ShutdownEndpointMBean(String beanName, Endpoint<?> endpoint) {
super(beanName, endpoint); super(beanName, endpoint);
} }
public ShutdownEndpointMBean(String beanName, Endpoint<?> endpoint,
ObjectMapper mapper) {
super(beanName, endpoint, mapper);
}
@ManagedOperation(description = "Shutdown the ApplicationContext") @ManagedOperation(description = "Shutdown the ApplicationContext")
public Object shutdown() { public Object shutdown() {
return convert(getEndpoint().invoke()); return convert(getEndpoint().invoke());
......
/* /*
* Copyright 2013 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,8 +16,11 @@ ...@@ -16,8 +16,11 @@
package org.springframework.boot.actuate.endpoint.jmx; package org.springframework.boot.actuate.endpoint.jmx;
import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
...@@ -28,6 +31,7 @@ import javax.management.ObjectName; ...@@ -28,6 +31,7 @@ import javax.management.ObjectName;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
...@@ -36,13 +40,19 @@ import org.springframework.jmx.export.MBeanExporter; ...@@ -36,13 +40,19 @@ import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.support.ObjectNameManager; import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
/** /**
* Tests for {@link EndpointMBeanExporter} * Tests for {@link EndpointMBeanExporter}
* *
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson
*/ */
public class EndpointMBeanExporterTests { public class EndpointMBeanExporterTests {
...@@ -176,6 +186,47 @@ public class EndpointMBeanExporterTests { ...@@ -176,6 +186,47 @@ public class EndpointMBeanExporterTests {
parent.close(); parent.close();
} }
@Test
public void jsonConversionWithDefaultObjectMapper() throws Exception {
this.context = new GenericApplicationContext();
this.context.registerBeanDefinition("endpointMbeanExporter",
new RootBeanDefinition(EndpointMBeanExporter.class));
this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
JsonConversionEndpoint.class));
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
Object response = mbeanExporter.getServer().invoke(
getObjectName("endpoint1", this.context), "getData", new Object[0],
new String[0]);
assertThat(response, is(instanceOf(Map.class)));
assertThat(((Map<?, ?>) response).get("date"), is(instanceOf(Long.class)));
}
@Test
public void jsonConversionWithCustomObjectMapper() throws Exception {
this.context = new GenericApplicationContext();
ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
constructorArgs.addIndexedArgumentValue(0, objectMapper);
this.context
.registerBeanDefinition("endpointMbeanExporter", new RootBeanDefinition(
EndpointMBeanExporter.class, constructorArgs, null));
this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
JsonConversionEndpoint.class));
this.context.refresh();
MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);
Object response = mbeanExporter.getServer().invoke(
getObjectName("endpoint1", this.context), "getData", new Object[0],
new String[0]);
assertThat(response, is(instanceOf(Map.class)));
assertThat(((Map<?, ?>) response).get("date"), is(instanceOf(String.class)));
}
private ObjectName getObjectName(String beanKey, GenericApplicationContext context) private ObjectName getObjectName(String beanKey, GenericApplicationContext context)
throws MalformedObjectNameException { throws MalformedObjectNameException {
return getObjectName("org.springframework.boot", beanKey, false, context); return getObjectName("org.springframework.boot", beanKey, false, context);
...@@ -209,4 +260,20 @@ public class EndpointMBeanExporterTests { ...@@ -209,4 +260,20 @@ public class EndpointMBeanExporterTests {
} }
} }
public static class JsonConversionEndpoint extends
AbstractEndpoint<Map<String, Object>> {
public JsonConversionEndpoint() {
super("json-conversion");
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
result.put("date", new Date());
return result;
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment