mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Unwrap AOP proxies in configprops endpoint serialization
Update `ConfigurationPropertiesReportEndpoint` to unwrap AOP proxies. Prior to this commit, the configprops endpoint serialized AOP proxies directly, exposing the Advised interface getters (targetSource, exposeProxy, preFiltered) into the response. See gh-50273 Signed-off-by: zxuhan7 <zxuhan7@gmail.com>
This commit is contained in:
+48
-11
@@ -25,8 +25,8 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
@@ -53,6 +53,8 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
@@ -212,22 +214,57 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
private ContextConfigurationPropertiesDescriptor describeBeans(ObjectMapper mapper, ApplicationContext context,
|
||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
|
||||
Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = beans.values()
|
||||
.stream()
|
||||
.filter(beanFilterPredicate)
|
||||
.collect(Collectors.toMap(ConfigurationPropertiesBean::getName,
|
||||
(bean) -> describeBean(mapper, bean, showUnsanitized)));
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = new LinkedHashMap<>();
|
||||
beans.values().stream().filter(beanFilterPredicate).forEach((bean) -> {
|
||||
ConfigurationPropertiesBeanDescriptor descriptor = describeBean(mapper, bean, showUnsanitized);
|
||||
if (descriptor != null) {
|
||||
descriptors.put(bean.getName(), descriptor);
|
||||
}
|
||||
});
|
||||
return new ContextConfigurationPropertiesDescriptor(descriptors,
|
||||
(context.getParent() != null) ? context.getParent().getId() : null);
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesBeanDescriptor describeBean(ObjectMapper mapper, ConfigurationPropertiesBean bean,
|
||||
boolean showUnsanitized) {
|
||||
String prefix = bean.getAnnotation().prefix();
|
||||
Map<String, Object> serialized = safeSerialize(mapper, bean.getInstance(), prefix);
|
||||
Map<String, Object> properties = sanitize(prefix, serialized, showUnsanitized);
|
||||
Map<String, Object> inputs = getInputs(prefix, serialized, showUnsanitized);
|
||||
return new ConfigurationPropertiesBeanDescriptor(prefix, properties, inputs);
|
||||
return describeTargetBean(bean.getInstance(), (instance) -> {
|
||||
String prefix = bean.getAnnotation().prefix();
|
||||
Map<String, Object> serialized = safeSerialize(mapper, instance, prefix);
|
||||
Map<String, Object> properties = sanitize(prefix, serialized, showUnsanitized);
|
||||
Map<String, Object> inputs = getInputs(prefix, serialized, showUnsanitized);
|
||||
return new ConfigurationPropertiesBeanDescriptor(prefix, properties, inputs);
|
||||
});
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesBeanDescriptor describeTargetBean(Object bean,
|
||||
Function<Object, ConfigurationPropertiesBeanDescriptor> actionWithTarget) {
|
||||
TargetSource targetSource = null;
|
||||
Object target = bean;
|
||||
while (target instanceof Advised advised) {
|
||||
try {
|
||||
targetSource = advised.getTargetSource();
|
||||
target = targetSource.getTarget();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (target != null) {
|
||||
try {
|
||||
return actionWithTarget.apply(target);
|
||||
}
|
||||
finally {
|
||||
if (targetSource != null) {
|
||||
try {
|
||||
targetSource.releaseTarget(target);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+121
@@ -29,12 +29,16 @@ import java.util.Map;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationPropertiesDescriptor;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -253,6 +257,53 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void aopProxyDoesNotLeakAdvisedInternals() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(CglibProxiedFooConfig.class)
|
||||
.withPropertyValues("foo.name:test");
|
||||
contextRunner.run((context) -> {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
ConfigurationPropertiesDescriptor applicationProperties = endpoint.configurationProperties();
|
||||
ConfigurationPropertiesBeanDescriptor foo = getContextDescriptor(context, applicationProperties).getBeans()
|
||||
.get("foo");
|
||||
assertThat(foo).isNotNull();
|
||||
Map<String, Object> map = foo.getProperties();
|
||||
assertThat(map).containsOnlyKeys("name", "bar");
|
||||
assertThat(map).containsEntry("name", "test");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void aopProxyTargetingAnotherProxyIsUnwrapped() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(NestedCglibProxiedFooConfig.class)
|
||||
.withPropertyValues("foo.name:nested");
|
||||
contextRunner.run((context) -> {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
ConfigurationPropertiesDescriptor applicationProperties = endpoint.configurationProperties();
|
||||
ConfigurationPropertiesBeanDescriptor foo = getContextDescriptor(context, applicationProperties).getBeans()
|
||||
.get("foo");
|
||||
assertThat(foo).isNotNull();
|
||||
assertThat(foo.getProperties()).containsOnlyKeys("name", "bar");
|
||||
assertThat(foo.getProperties()).containsEntry("name", "nested");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void aopProxyWithUnresolvableTargetIsExcluded() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(UnresolvableTargetFooConfig.class);
|
||||
contextRunner.run((context) -> {
|
||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||
.getBean(ConfigurationPropertiesReportEndpoint.class);
|
||||
ConfigurationPropertiesDescriptor applicationProperties = endpoint.configurationProperties();
|
||||
assertThat(getContextDescriptor(context, applicationProperties).getBeans()).doesNotContainKey("foo");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hikariDataSourceConfigurationPropertiesBeanCanBeSerialized() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
@@ -313,6 +364,14 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||
});
|
||||
}
|
||||
|
||||
private ContextConfigurationPropertiesDescriptor getContextDescriptor(AssertableApplicationContext context,
|
||||
ConfigurationPropertiesDescriptor applicationProperties) {
|
||||
ContextConfigurationPropertiesDescriptor contextDescriptor = applicationProperties.getContexts()
|
||||
.get(context.getId());
|
||||
assertThat(contextDescriptor).isNotNull();
|
||||
return contextDescriptor;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties
|
||||
static class Base {
|
||||
@@ -576,6 +635,68 @@ class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(Base.class)
|
||||
static class CglibProxiedFooConfig {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("foo")
|
||||
Foo foo() {
|
||||
ProxyFactory proxyFactory = new ProxyFactory(new Foo());
|
||||
proxyFactory.setProxyTargetClass(true);
|
||||
return (Foo) proxyFactory.getProxy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(Base.class)
|
||||
static class NestedCglibProxiedFooConfig {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("foo")
|
||||
Foo foo() {
|
||||
ProxyFactory inner = new ProxyFactory(new Foo());
|
||||
inner.setProxyTargetClass(true);
|
||||
ProxyFactory outer = new ProxyFactory(inner.getProxy());
|
||||
outer.setProxyTargetClass(true);
|
||||
return (Foo) outer.getProxy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(Base.class)
|
||||
static class UnresolvableTargetFooConfig {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("foo")
|
||||
Foo foo() {
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
proxyFactory.setProxyTargetClass(true);
|
||||
proxyFactory.setTargetSource(new TargetSource() {
|
||||
|
||||
@Override
|
||||
public Class<?> getTargetClass() {
|
||||
return Foo.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTarget() throws Exception {
|
||||
throw new IllegalStateException("no target");
|
||||
}
|
||||
|
||||
});
|
||||
return (Foo) proxyFactory.getProxy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties
|
||||
static class HikariDataSourceConfig {
|
||||
|
||||
Reference in New Issue
Block a user