diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index c66fd04b2171..c25a12b91195 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -282,13 +282,15 @@ private CachedIntrospectionResults(Class beanClass) throws BeansException { // This call is slow so we do it once. PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { - if (Class.class == beanClass && (!"name".equals(pd.getName()) && !pd.getName().endsWith("Name"))) { + if (Class.class == beanClass && !("name".equals(pd.getName()) || + (pd.getName().endsWith("Name") && String.class == pd.getPropertyType()))) { // Only allow all name variants of Class properties continue; } - if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType()) - || ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) { - // Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those + if (pd.getWriteMethod() == null && pd.getPropertyType() != null && + (ClassLoader.class.isAssignableFrom(pd.getPropertyType()) || + ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) { + // Ignore ClassLoader and ProtectionDomain read-only properties - no need to bind to those continue; } if (logger.isTraceEnabled()) { @@ -326,9 +328,10 @@ private void introspectInterfaces(Class beanClass, Class currClass) throws // GenericTypeAwarePropertyDescriptor leniently resolves a set* write method // against a declared read method, so we prefer read method descriptors here. pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); - if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType()) - || ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) { - // Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those + if (pd.getWriteMethod() == null && pd.getPropertyType() != null && + (ClassLoader.class.isAssignableFrom(pd.getPropertyType()) || + ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) { + // Ignore ClassLoader and ProtectionDomain read-only properties - no need to bind to those continue; } this.propertyDescriptors.put(pd.getName(), pd); diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java index 0711189b5f59..691e954603f5 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -23,6 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.core.OverridingClassLoader; +import org.springframework.core.io.DefaultResourceLoader; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -104,7 +106,7 @@ public void checkNotWritablePropertyHoldPossibleMatches() { .satisfies(ex -> assertThat(ex.getPossibleMatches()).containsExactly("age")); } - @Test // Can't be shared; there is no such thing as a read-only field + @Test // Can't be shared; there is no such thing as a read-only field public void setReadOnlyMapProperty() { TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean())); TypedReadOnlyMapClient target = new TypedReadOnlyMapClient(); @@ -156,12 +158,34 @@ public void propertyDescriptors() { BeanWrapper accessor = createAccessor(target); accessor.setPropertyValue("name", "a"); accessor.setPropertyValue("spouse.name", "b"); + assertThat(target.getName()).isEqualTo("a"); assertThat(target.getSpouse().getName()).isEqualTo("b"); assertThat(accessor.getPropertyValue("name")).isEqualTo("a"); assertThat(accessor.getPropertyValue("spouse.name")).isEqualTo("b"); assertThat(accessor.getPropertyDescriptor("name").getPropertyType()).isEqualTo(String.class); assertThat(accessor.getPropertyDescriptor("spouse.name").getPropertyType()).isEqualTo(String.class); + + assertThat(accessor.isReadableProperty("class.package")).isFalse(); + assertThat(accessor.isReadableProperty("class.module")).isFalse(); + assertThat(accessor.isReadableProperty("class.classLoader")).isFalse(); + assertThat(accessor.isReadableProperty("class.name")).isTrue(); + assertThat(accessor.isReadableProperty("class.simpleName")).isTrue(); + assertThat(accessor.getPropertyValue("class.name")).isEqualTo(TestBean.class.getName()); + assertThat(accessor.getPropertyValue("class.simpleName")).isEqualTo(TestBean.class.getSimpleName()); + assertThat(accessor.getPropertyDescriptor("class.name").getPropertyType()).isEqualTo(String.class); + assertThat(accessor.getPropertyDescriptor("class.simpleName").getPropertyType()).isEqualTo(String.class); + + accessor = createAccessor(new DefaultResourceLoader()); + + assertThat(accessor.isReadableProperty("class.package")).isFalse(); + assertThat(accessor.isReadableProperty("class.module")).isFalse(); + assertThat(accessor.isReadableProperty("class.classLoader")).isFalse(); + assertThat(accessor.isReadableProperty("classLoader")).isTrue(); + assertThat(accessor.isWritableProperty("classLoader")).isTrue(); + OverridingClassLoader ocl = new OverridingClassLoader(getClass().getClassLoader()); + accessor.setPropertyValue("classLoader", ocl); + assertThat(accessor.getPropertyValue("classLoader")).isSameAs(ocl); } @Test