mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-23 17:59:03 +00:00
Merge pull request #50273 from zxuhan
Closes gh-50273 * pr/50273: Polish "Unwrap AOP proxies in configprops endpoint serialization" Unwrap AOP proxies in configprops endpoint serialization
This commit is contained in:
+21
-8
@@ -26,7 +26,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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 +52,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;
|
||||
@@ -211,14 +212,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
|
||||
private ContextConfigurationPropertiesDescriptor describeBeans(ObjectMapper mapper, ApplicationContext context,
|
||||
Predicate<ConfigurationPropertiesBean> beanFilterPredicate, boolean showUnsanitized) {
|
||||
ApplicationContext parent = context.getParent();
|
||||
Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = beans.values()
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = new LinkedHashMap<>();
|
||||
beans.values()
|
||||
.stream()
|
||||
.filter(beanFilterPredicate)
|
||||
.collect(Collectors.toMap(ConfigurationPropertiesBean::getName,
|
||||
(bean) -> describeBean(mapper, bean, showUnsanitized)));
|
||||
return new ContextConfigurationPropertiesDescriptor(descriptors,
|
||||
(context.getParent() != null) ? context.getParent().getId() : null);
|
||||
.forEach((bean) -> descriptors.put(bean.getName(), describeBean(mapper, bean, showUnsanitized)));
|
||||
return new ContextConfigurationPropertiesDescriptor(descriptors, (parent != null) ? parent.getId() : null);
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesBeanDescriptor describeBean(ObjectMapper mapper, ConfigurationPropertiesBean bean,
|
||||
@@ -231,8 +232,8 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
}
|
||||
|
||||
/**
|
||||
* Cautiously serialize the bean to a map (returning a map with an error message
|
||||
* instead of throwing an exception if there is a problem).
|
||||
* Cautiously serialize the ultimate bean target to a map (returning a map with an
|
||||
* error message instead of throwing an exception if there is a problem).
|
||||
* @param mapper the object mapper
|
||||
* @param bean the source bean
|
||||
* @param prefix the prefix
|
||||
@@ -241,6 +242,18 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
private Map<String, Object> safeSerialize(ObjectMapper mapper, Object bean, String prefix) {
|
||||
try {
|
||||
if (bean instanceof Advised advised) {
|
||||
TargetSource targetSource = advised.getTargetSource();
|
||||
Object target = targetSource.getTarget();
|
||||
try {
|
||||
return safeSerialize(mapper, target, prefix);
|
||||
}
|
||||
finally {
|
||||
if (target != null) {
|
||||
targetSource.releaseTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new HashMap<>(mapper.convertValue(bean, Map.class));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
||||
+123
@@ -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,55 @@ 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 aopProxyWithUnresolvableTarget() {
|
||||
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()).containsKey("foo")
|
||||
.satisfies((beans) -> assertThat(beans.get("foo").getProperties()).containsEntry("error",
|
||||
"Cannot serialize 'foo'"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void hikariDataSourceConfigurationPropertiesBeanCanBeSerialized() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
@@ -313,6 +366,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 +637,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