Browse Source

DefaultListableBeanFactory checks for pre-converted Optional wrappers

Issue: SPR-17607
pull/22320/head
Juergen Hoeller 7 years ago
parent
commit
e714fc533a
  1. 7
      spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
  2. 40
      spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java
  3. 21
      spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

7
spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 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.
@ -1594,7 +1594,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/** /**
* A dependency descriptor marker for multiple elements. * A dependency descriptor for a multi-element declaration with nested elements.
*/ */
private static class MultiElementDescriptor extends NestedDependencyDescriptor { private static class MultiElementDescriptor extends NestedDependencyDescriptor {
@ -1622,7 +1622,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
super.resolveCandidate(beanName, requiredType, beanFactory)); super.resolveCandidate(beanName, requiredType, beanFactory));
} }
}; };
return Optional.ofNullable(doResolveDependency(descriptorToUse, beanName, null, null)); Object result = doResolveDependency(descriptorToUse, beanName, null, null);
return (result instanceof Optional ? (Optional<?>) result : Optional.ofNullable(result));
} }
} }

40
spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2019 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.
@ -25,6 +25,7 @@ import java.net.URI;
import java.net.URL; import java.net.URL;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.security.Permission; import java.security.Permission;
import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -45,7 +46,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -61,6 +62,7 @@ import static org.junit.Assert.*;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0 * @since 3.0
*/ */
public class ApplicationContextExpressionTests { public class ApplicationContextExpressionTests {
@ -100,6 +102,8 @@ public class ApplicationContextExpressionTests {
} }
}); });
ac.getBeanFactory().setConversionService(new DefaultConversionService());
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Properties placeholders = new Properties(); Properties placeholders = new Properties();
placeholders.setProperty("code", "123"); placeholders.setProperty("code", "123");
@ -174,6 +178,9 @@ public class ApplicationContextExpressionTests {
System.getProperties().put("country", "UK"); System.getProperties().put("country", "UK");
assertEquals("123 UK", tb3.country); assertEquals("123 UK", tb3.country);
assertEquals("123 UK", tb3.countryFactory.getObject()); assertEquals("123 UK", tb3.countryFactory.getObject());
assertEquals("123", tb3.optionalValue1.get());
assertEquals("123", tb3.optionalValue2.get());
assertFalse(tb3.optionalValue3.isPresent());
assertSame(tb0, tb3.tb); assertSame(tb0, tb3.tb);
tb3 = (ValueTestBean) SerializationTestUtils.serializeAndDeserialize(tb3); tb3 = (ValueTestBean) SerializationTestUtils.serializeAndDeserialize(tb3);
@ -207,12 +214,7 @@ public class ApplicationContextExpressionTests {
GenericApplicationContext ac = new GenericApplicationContext(); GenericApplicationContext ac = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac); AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
GenericConversionService cs = new GenericConversionService(); GenericConversionService cs = new GenericConversionService();
cs.addConverter(String.class, String.class, new Converter<String, String>() { cs.addConverter(String.class, String.class, String::trim);
@Override
public String convert(String source) {
return source.trim();
}
});
ac.getBeanFactory().registerSingleton(GenericApplicationContext.CONVERSION_SERVICE_BEAN_NAME, cs); ac.getBeanFactory().registerSingleton(GenericApplicationContext.CONVERSION_SERVICE_BEAN_NAME, cs);
RootBeanDefinition rbd = new RootBeanDefinition(PrototypeTestBean.class); RootBeanDefinition rbd = new RootBeanDefinition(PrototypeTestBean.class);
rbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); rbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
@ -274,8 +276,7 @@ public class ApplicationContextExpressionTests {
@Test @Test
public void systemPropertiesSecurityManager() { public void systemPropertiesSecurityManager() {
GenericApplicationContext ac = new GenericApplicationContext(); AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
GenericBeanDefinition bd = new GenericBeanDefinition(); GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(TestBean.class); bd.setBeanClass(TestBean.class);
@ -311,8 +312,7 @@ public class ApplicationContextExpressionTests {
@Test @Test
public void stringConcatenationWithDebugLogging() { public void stringConcatenationWithDebugLogging() {
GenericApplicationContext ac = new GenericApplicationContext(); AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(ac);
GenericBeanDefinition bd = new GenericBeanDefinition(); GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(String.class); bd.setBeanClass(String.class);
@ -326,11 +326,10 @@ public class ApplicationContextExpressionTests {
@Test @Test
public void resourceInjection() throws IOException { public void resourceInjection() throws IOException {
System.setProperty("logfile", "log4j.properties"); System.setProperty("logfile", "do_not_delete_me.txt");
try { try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ResourceInjectionBean.class)) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ResourceInjectionBean.class);
ResourceInjectionBean resourceInjectionBean = ac.getBean(ResourceInjectionBean.class); ResourceInjectionBean resourceInjectionBean = ac.getBean(ResourceInjectionBean.class);
Resource resource = new ClassPathResource("log4j.properties"); Resource resource = new ClassPathResource("do_not_delete_me.txt");
assertEquals(resource, resourceInjectionBean.resource); assertEquals(resource, resourceInjectionBean.resource);
assertEquals(resource.getURL(), resourceInjectionBean.url); assertEquals(resource.getURL(), resourceInjectionBean.url);
assertEquals(resource.getURI(), resourceInjectionBean.uri); assertEquals(resource.getURI(), resourceInjectionBean.uri);
@ -364,6 +363,15 @@ public class ApplicationContextExpressionTests {
@Value("${code} #{systemProperties.country}") @Value("${code} #{systemProperties.country}")
public ObjectFactory<String> countryFactory; public ObjectFactory<String> countryFactory;
@Value("${code}")
private transient Optional<String> optionalValue1;
@Value("${code:#{null}}")
private transient Optional<String> optionalValue2;
@Value("${codeX:#{null}}")
private transient Optional<String> optionalValue3;
@Autowired @Qualifier("original") @Autowired @Qualifier("original")
public transient TestBean tb; public transient TestBean tb;
} }

21
spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 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.
@ -83,7 +83,7 @@ public class TypeDescriptor implements Serializable {
*/ */
public TypeDescriptor(MethodParameter methodParameter) { public TypeDescriptor(MethodParameter methodParameter) {
this.resolvableType = ResolvableType.forMethodParameter(methodParameter); this.resolvableType = ResolvableType.forMethodParameter(methodParameter);
this.type = this.resolvableType.resolve(methodParameter.getParameterType()); this.type = this.resolvableType.resolve(methodParameter.getNestedParameterType());
this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ? this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ?
methodParameter.getMethodAnnotations() : methodParameter.getParameterAnnotations()); methodParameter.getMethodAnnotations() : methodParameter.getParameterAnnotations());
} }
@ -607,7 +607,7 @@ public class TypeDescriptor implements Serializable {
} }
/** /**
* Creates a type descriptor for a nested type declared within the method parameter. * Create a type descriptor for a nested type declared within the method parameter.
* <p>For example, if the methodParameter is a {@code List<String>} and the * <p>For example, if the methodParameter is a {@code List<String>} and the
* nesting level is 1, the nested type descriptor will be String.class. * nesting level is 1, the nested type descriptor will be String.class.
* <p>If the methodParameter is a {@code List<List<String>>} and the nesting * <p>If the methodParameter is a {@code List<List<String>>} and the nesting
@ -637,7 +637,7 @@ public class TypeDescriptor implements Serializable {
} }
/** /**
* Creates a type descriptor for a nested type declared within the field. * Create a type descriptor for a nested type declared within the field.
* <p>For example, if the field is a {@code List<String>} and the nesting * <p>For example, if the field is a {@code List<String>} and the nesting
* level is 1, the nested type descriptor will be {@code String.class}. * level is 1, the nested type descriptor will be {@code String.class}.
* <p>If the field is a {@code List<List<String>>} and the nesting level is * <p>If the field is a {@code List<List<String>>} and the nesting level is
@ -646,8 +646,9 @@ public class TypeDescriptor implements Serializable {
* is 1, the nested type descriptor will be String, derived from the map value. * is 1, the nested type descriptor will be String, derived from the map value.
* <p>If the field is a {@code List<Map<Integer, String>>} and the nesting * <p>If the field is a {@code List<Map<Integer, String>>} and the nesting
* level is 2, the nested type descriptor will be String, derived from the map value. * level is 2, the nested type descriptor will be String, derived from the map value.
* <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. * <p>Returns {@code null} if a nested type cannot be obtained because it was not
* For example, if the field is a {@code List<?>}, the nested type descriptor returned will be {@code null}. * declared. For example, if the field is a {@code List<?>}, the nested type
* descriptor returned will be {@code null}.
* @param field the field * @param field the field
* @param nestingLevel the nesting level of the collection/array element or * @param nestingLevel the nesting level of the collection/array element or
* map key/value declaration within the field * map key/value declaration within the field
@ -661,7 +662,7 @@ public class TypeDescriptor implements Serializable {
} }
/** /**
* Creates a type descriptor for a nested type declared within the property. * Create a type descriptor for a nested type declared within the property.
* <p>For example, if the property is a {@code List<String>} and the nesting * <p>For example, if the property is a {@code List<String>} and the nesting
* level is 1, the nested type descriptor will be {@code String.class}. * level is 1, the nested type descriptor will be {@code String.class}.
* <p>If the property is a {@code List<List<String>>} and the nesting level * <p>If the property is a {@code List<List<String>>} and the nesting level
@ -670,9 +671,9 @@ public class TypeDescriptor implements Serializable {
* is 1, the nested type descriptor will be String, derived from the map value. * is 1, the nested type descriptor will be String, derived from the map value.
* <p>If the property is a {@code List<Map<Integer, String>>} and the nesting * <p>If the property is a {@code List<Map<Integer, String>>} and the nesting
* level is 2, the nested type descriptor will be String, derived from the map value. * level is 2, the nested type descriptor will be String, derived from the map value.
* <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. * <p>Returns {@code null} if a nested type cannot be obtained because it was not
* For example, if the property is a {@code List<?>}, the nested type descriptor * declared. For example, if the property is a {@code List<?>}, the nested type
* returned will be {@code null}. * descriptor returned will be {@code null}.
* @param property the property * @param property the property
* @param nestingLevel the nesting level of the collection/array element or * @param nestingLevel the nesting level of the collection/array element or
* map key/value declaration within the property * map key/value declaration within the property

Loading…
Cancel
Save