diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 6cec5d5014f..185a5807631 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.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"); * you may not use this file except in compliance with the License. @@ -571,8 +571,8 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements bean.destroy(); } catch (Throwable ex) { - if (logger.isInfoEnabled()) { - logger.info("Destroy method on bean with name '" + beanName + "' threw an exception", ex); + if (logger.isWarnEnabled()) { + logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", ex); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index 9d1ce3466cb..36768418cdd 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -261,10 +261,10 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { catch (Throwable ex) { String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'"; if (logger.isDebugEnabled()) { - logger.info(msg, ex); + logger.warn(msg, ex); } else { - logger.info(msg + ": " + ex); + logger.warn(msg + ": " + ex); } } } @@ -343,14 +343,14 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { String msg = "Destroy method '" + this.destroyMethodName + "' on bean with name '" + this.beanName + "' threw an exception"; if (logger.isDebugEnabled()) { - logger.info(msg, ex.getTargetException()); + logger.warn(msg, ex.getTargetException()); } else { - logger.info(msg + ": " + ex.getTargetException()); + logger.warn(msg + ": " + ex.getTargetException()); } } catch (Throwable ex) { - logger.info("Failed to invoke destroy method '" + this.destroyMethodName + + logger.warn("Failed to invoke destroy method '" + this.destroyMethodName + "' on bean with name '" + this.beanName + "'", ex); } } diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java index 5529b5b4484..706baabd93c 100644 --- a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java +++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java @@ -17,9 +17,11 @@ package org.springframework.validation.beanvalidation2; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.util.ArrayList; @@ -50,13 +52,11 @@ import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.context.support.StaticMessageSource; import org.springframework.util.ObjectUtils; +import org.springframework.util.SerializationTestUtils; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.beanvalidation.SpringValidatorAdapter; -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.assertj.core.api.Assertions.assertThat; /** @@ -88,7 +88,7 @@ public class SpringValidatorAdapterTests { } @Test // SPR-13406 - public void testNoStringArgumentValue() { + public void testNoStringArgumentValue() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("pass"); testBean.setConfirmPassword("pass"); @@ -103,10 +103,11 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH)).isEqualTo("Size of Password must be between 8 and 128"); assertThat(error.contains(ConstraintViolation.class)).isTrue(); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString()).isEqualTo("password"); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString())).isEqualTo(error.toString()); } @Test // SPR-13406 - public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() { + public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("password"); testBean.setConfirmPassword("PASSWORD"); @@ -121,6 +122,7 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH)).isEqualTo("Password must be same value as Password(Confirm)"); assertThat(error.contains(ConstraintViolation.class)).isTrue(); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString()).isEqualTo("password"); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString())).isEqualTo(error.toString()); } @Test // SPR-13406 @@ -323,8 +325,8 @@ public class SpringValidatorAdapterTests { @Documented @Constraint(validatedBy = {SameValidator.class}) - @Target({TYPE, ANNOTATION_TYPE}) - @Retention(RUNTIME) + @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) @Repeatable(SameGroup.class) @interface Same { @@ -338,8 +340,8 @@ public class SpringValidatorAdapterTests { String comparingField(); - @Target({TYPE, ANNOTATION_TYPE}) - @Retention(RUNTIME) + @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { Same[] value(); @@ -349,8 +351,8 @@ public class SpringValidatorAdapterTests { @Documented @Inherited - @Retention(RUNTIME) - @Target({TYPE, ANNOTATION_TYPE}) + @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) @interface SameGroup { Same[] value(); @@ -486,7 +488,7 @@ public class SpringValidatorAdapterTests { @Constraint(validatedBy = AnythingValidator.class) - @Retention(RUNTIME) + @Retention(RetentionPolicy.RUNTIME) public @interface AnythingValid { String message() default "{AnythingValid.message}"; @@ -507,23 +509,22 @@ public class SpringValidatorAdapterTests { @Override public boolean isValid(Object value, ConstraintValidatorContext context) { - List fieldsErros = new ArrayList<>(); - Arrays.asList(value.getClass().getDeclaredFields()).forEach(f -> { - f.setAccessible(true); + List fieldsErrors = new ArrayList<>(); + Arrays.asList(value.getClass().getDeclaredFields()).forEach(field -> { + field.setAccessible(true); try { - if (!f.getName().equals(ID) && f.get(value) == null) { - fieldsErros.add(f); + if (!field.getName().equals(ID) && field.get(value) == null) { + fieldsErrors.add(field); context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()) - .addPropertyNode(f.getName()) + .addPropertyNode(field.getName()) .addConstraintViolation(); } } catch (IllegalAccessException ex) { throw new IllegalStateException(ex); } - }); - return fieldsErros.isEmpty(); + return fieldsErrors.isEmpty(); } } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index a55fff3d690..a6b6b5e46f1 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -165,27 +165,15 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. String nestedField = bindingResult.getNestedPath() + field; if (nestedField.isEmpty()) { String[] errorCodes = bindingResult.resolveMessageCodes(errorCode); - ObjectError error = new ObjectError( - errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()) { - @Override - public boolean shouldRenderDefaultMessage() { - return requiresMessageFormat(violation); - } - }; - error.wrap(violation); + ObjectError error = new ViolationObjectError( + errors.getObjectName(), errorCodes, errorArgs, violation, this); bindingResult.addError(error); } else { Object rejectedValue = getRejectedValue(field, violation, bindingResult); String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field); - FieldError error = new FieldError(errors.getObjectName(), nestedField, - rejectedValue, false, errorCodes, errorArgs, violation.getMessage()) { - @Override - public boolean shouldRenderDefaultMessage() { - return requiresMessageFormat(violation); - } - }; - error.wrap(violation); + FieldError error = new ViolationFieldError(errors.getObjectName(), nestedField, + rejectedValue, errorCodes, errorArgs, violation, this); bindingResult.addError(error); } } @@ -307,29 +295,6 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. return new DefaultMessageSourceResolvable(codes, field); } - /** - * Indicate whether this violation's interpolated message has remaining - * placeholders and therefore requires {@link java.text.MessageFormat} - * to be applied to it. Called for a Bean Validation defined message - * (coming out {@code ValidationMessages.properties}) when rendered - * as the default message in Spring's MessageSource. - *

The default implementation considers a Spring-style "{0}" placeholder - * for the field name as an indication for {@link java.text.MessageFormat}. - * Any other placeholder or escape syntax occurrences are typically a - * mismatch, coming out of regex pattern values or the like. Note that - * standard Bean Validation does not support "{0}" style placeholders at all; - * this is a feature typically used in Spring MessageSource resource bundles. - * @param violation the Bean Validation constraint violation, including - * BV-defined interpolation of named attribute references in its message - * @return {@code true} if {@code java.text.MessageFormat} is to be applied, - * or {@code false} if the violation's message should be used as-is - * @since 5.1.8 - * @see #getArgumentsForConstraint - */ - protected boolean requiresMessageFormat(ConstraintViolation violation) { - return violation.getMessage().contains("{0}"); - } - /** * Extract the rejected value behind the given constraint violation, * for exposure through the Spring errors representation. @@ -354,6 +319,33 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. return invalidValue; } + /** + * Indicate whether this violation's interpolated message has remaining + * placeholders and therefore requires {@link java.text.MessageFormat} + * to be applied to it. Called for a Bean Validation defined message + * (coming out {@code ValidationMessages.properties}) when rendered + * as the default message in Spring's MessageSource. + *

The default implementation considers a Spring-style "{0}" placeholder + * for the field name as an indication for {@link java.text.MessageFormat}. + * Any other placeholder or escape syntax occurrences are typically a + * mismatch, coming out of regex pattern values or the like. Note that + * standard Bean Validation does not support "{0}" style placeholders at all; + * this is a feature typically used in Spring MessageSource resource bundles. + * @param violation the Bean Validation constraint violation, including + * BV-defined interpolation of named attribute references in its message + * @return {@code true} if {@code java.text.MessageFormat} is to be applied, + * or {@code false} if the violation's message should be used as-is + * @since 5.1.8 + * @see #getArgumentsForConstraint + */ + protected boolean requiresMessageFormat(ConstraintViolation violation) { + return containsSpringStylePlaceholder(violation.getMessage()); + } + + private static boolean containsSpringStylePlaceholder(@Nullable String message) { + return (message != null && message.contains("{0}")); + } + //--------------------------------------------------------------------- // Implementation of JSR-303 Validator interface @@ -436,6 +428,71 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. public String getDefaultMessage() { return this.resolvableString; } + + @Override + public String toString() { + return this.resolvableString; + } + } + + + /** + * Subclass of {@code ObjectError} with Spring-style default message rendering. + */ + @SuppressWarnings("serial") + private static class ViolationObjectError extends ObjectError implements Serializable { + + @Nullable + private transient SpringValidatorAdapter adapter; + + @Nullable + private transient ConstraintViolation violation; + + public ViolationObjectError(String objectName, String[] codes, Object[] arguments, + ConstraintViolation violation, SpringValidatorAdapter adapter) { + + super(objectName, codes, arguments, violation.getMessage()); + this.adapter = adapter; + this.violation = violation; + wrap(violation); + } + + @Override + public boolean shouldRenderDefaultMessage() { + return (this.adapter != null && this.violation != null ? + this.adapter.requiresMessageFormat(this.violation) : + containsSpringStylePlaceholder(getDefaultMessage())); + } + } + + + /** + * Subclass of {@code FieldError} with Spring-style default message rendering. + */ + @SuppressWarnings("serial") + private static class ViolationFieldError extends FieldError implements Serializable { + + @Nullable + private transient SpringValidatorAdapter adapter; + + @Nullable + private transient ConstraintViolation violation; + + public ViolationFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes, + Object[] arguments, ConstraintViolation violation, SpringValidatorAdapter adapter) { + + super(objectName, field, rejectedValue, false, codes, arguments, violation.getMessage()); + this.adapter = adapter; + this.violation = violation; + wrap(violation); + } + + @Override + public boolean shouldRenderDefaultMessage() { + return (this.adapter != null && this.violation != null ? + this.adapter.requiresMessageFormat(this.violation) : + containsSpringStylePlaceholder(getDefaultMessage())); + } } } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java index 018510ac561..d9e6ae307d4 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java @@ -17,9 +17,11 @@ package org.springframework.validation.beanvalidation; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.util.ArrayList; @@ -48,12 +50,10 @@ import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.context.support.StaticMessageSource; import org.springframework.util.ObjectUtils; +import org.springframework.util.SerializationTestUtils; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.FieldError; -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.assertj.core.api.Assertions.assertThat; /** @@ -85,7 +85,7 @@ public class SpringValidatorAdapterTests { } @Test // SPR-13406 - public void testNoStringArgumentValue() { + public void testNoStringArgumentValue() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("pass"); testBean.setConfirmPassword("pass"); @@ -100,10 +100,11 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH)).isEqualTo("Size of Password must be between 8 and 128"); assertThat(error.contains(ConstraintViolation.class)).isTrue(); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString()).isEqualTo("password"); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString())).isEqualTo(error.toString()); } @Test // SPR-13406 - public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() { + public void testApplyMessageSourceResolvableToStringArgumentValueWithResolvedLogicalFieldName() throws Exception { TestBean testBean = new TestBean(); testBean.setPassword("password"); testBean.setConfirmPassword("PASSWORD"); @@ -118,6 +119,7 @@ public class SpringValidatorAdapterTests { assertThat(messageSource.getMessage(error, Locale.ENGLISH)).isEqualTo("Password must be same value as Password(Confirm)"); assertThat(error.contains(ConstraintViolation.class)).isTrue(); assertThat(error.unwrap(ConstraintViolation.class).getPropertyPath().toString()).isEqualTo("password"); + assertThat(SerializationTestUtils.serializeAndDeserialize(error.toString())).isEqualTo(error.toString()); } @Test // SPR-13406 @@ -278,8 +280,8 @@ public class SpringValidatorAdapterTests { @Documented @Constraint(validatedBy = {SameValidator.class}) - @Target({TYPE, ANNOTATION_TYPE}) - @Retention(RUNTIME) + @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) @Repeatable(SameGroup.class) @interface Same { @@ -293,8 +295,8 @@ public class SpringValidatorAdapterTests { String comparingField(); - @Target({TYPE, ANNOTATION_TYPE}) - @Retention(RUNTIME) + @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { Same[] value(); @@ -304,8 +306,8 @@ public class SpringValidatorAdapterTests { @Documented @Inherited - @Retention(RUNTIME) - @Target({TYPE, ANNOTATION_TYPE}) + @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) @interface SameGroup { Same[] value(); @@ -441,7 +443,7 @@ public class SpringValidatorAdapterTests { @Constraint(validatedBy = AnythingValidator.class) - @Retention(RUNTIME) + @Retention(RetentionPolicy.RUNTIME) public @interface AnythingValid { String message() default "{AnythingValid.message}"; @@ -462,14 +464,14 @@ public class SpringValidatorAdapterTests { @Override public boolean isValid(Object value, ConstraintValidatorContext context) { - List fieldsErros = new ArrayList<>(); - Arrays.asList(value.getClass().getDeclaredFields()).forEach(f -> { - f.setAccessible(true); + List fieldsErrors = new ArrayList<>(); + Arrays.asList(value.getClass().getDeclaredFields()).forEach(field -> { + field.setAccessible(true); try { - if (!f.getName().equals(ID) && f.get(value) == null) { - fieldsErros.add(f); + if (!field.getName().equals(ID) && field.get(value) == null) { + fieldsErrors.add(field); context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()) - .addPropertyNode(f.getName()) + .addPropertyNode(field.getName()) .addConstraintViolation(); } } @@ -477,7 +479,7 @@ public class SpringValidatorAdapterTests { throw new IllegalStateException(ex); } }); - return fieldsErros.isEmpty(); + return fieldsErrors.isEmpty(); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 7823d59c460..aa3819a3c91 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -138,6 +138,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); + method = ClassUtils.getInterfaceMethodIfPossible(method); this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -180,6 +181,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); + method = ClassUtils.getInterfaceMethodIfPossible(method); invoker = new InvokerPair(method, typeDescriptor); this.lastReadInvokerPair = invoker; this.readerCache.put(cacheKey, invoker); @@ -239,6 +241,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // Treat it like a property Property property = new Property(type, null, method); TypeDescriptor typeDescriptor = new TypeDescriptor(property); + method = ClassUtils.getInterfaceMethodIfPossible(method); this.writerCache.put(cacheKey, method); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -287,6 +290,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (method == null) { method = findSetterForProperty(name, type, target); if (method != null) { + method = ClassUtils.getInterfaceMethodIfPossible(method); cachedMember = method; this.writerCache.put(cacheKey, cachedMember); } @@ -414,13 +418,24 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { method.getParameterCount() == numberOfParams && (!mustBeStatic || Modifier.isStatic(method.getModifiers())) && (requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) { - return ClassUtils.getInterfaceMethodIfPossible(method); + return method; } } } return null; } + /** + * Return class methods ordered with non-bridge methods appearing higher. + */ + private Method[] getSortedMethods(Class clazz) { + return this.sortedMethodsCache.computeIfAbsent(clazz, key -> { + Method[] methods = key.getMethods(); + Arrays.sort(methods, (o1, o2) -> (o1.isBridge() == o2.isBridge() ? 0 : (o1.isBridge() ? 1 : -1))); + return methods; + }); + } + /** * Determine whether the given {@code Method} is a candidate for property access * on an instance of the given target class. @@ -434,17 +449,6 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { return true; } - /** - * Return class methods ordered with non-bridge methods appearing higher. - */ - private Method[] getSortedMethods(Class clazz) { - return this.sortedMethodsCache.computeIfAbsent(clazz, key -> { - Method[] methods = key.getMethods(); - Arrays.sort(methods, (o1, o2) -> (o1.isBridge() == o2.isBridge() ? 0 : (o1.isBridge() ? 1 : -1))); - return methods; - }); - } - /** * Return the method suffixes for a given property name. The default implementation * uses JavaBean conventions with additional support for properties of the form 'xY' @@ -536,7 +540,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (method == null) { method = findGetterForProperty(name, clazz, target); if (method != null) { - invocationTarget = new InvokerPair(method, new TypeDescriptor(new MethodParameter(method, -1))); + TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1)); + method = ClassUtils.getInterfaceMethodIfPossible(method); + invocationTarget = new InvokerPair(method, typeDescriptor); ReflectionUtils.makeAccessible(method); this.readerCache.put(cacheKey, invocationTarget); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index 7c81d11885e..5f6665cbbc0 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -58,6 +58,7 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver; +import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -1177,9 +1178,13 @@ public class SpelReproTests extends AbstractExpressionTests { public void SPR9994_bridgeMethods() throws Exception { ReflectivePropertyAccessor accessor = new ReflectivePropertyAccessor(); StandardEvaluationContext context = new StandardEvaluationContext(); - Object target = new GenericImplementation(); + GenericImplementation target = new GenericImplementation(); + accessor.write(context, target, "property", "1"); + assertThat(target.value).isEqualTo(1); TypedValue value = accessor.read(context, target, "property"); + assertThat(value.getValue()).isEqualTo(1); assertThat(value.getTypeDescriptor().getType()).isEqualTo(Integer.class); + assertThat(value.getTypeDescriptor().getAnnotations()).isNotEmpty(); } @Test @@ -1188,6 +1193,7 @@ public class SpelReproTests extends AbstractExpressionTests { StandardEvaluationContext context = new StandardEvaluationContext(); Object target = new OnlyBridgeMethod(); TypedValue value = accessor.read(context, target, "property"); + assertThat(value.getValue()).isNull(); assertThat(value.getTypeDescriptor().getType()).isEqualTo(Integer.class); } @@ -1196,7 +1202,7 @@ public class SpelReproTests extends AbstractExpressionTests { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Class valueType = parser.parseExpression("simpleProperty").getValueType(evaluationContext); - assertThat(valueType).isNotNull(); + assertThat(valueType).isEqualTo(Boolean.class); } @Test @@ -1204,7 +1210,7 @@ public class SpelReproTests extends AbstractExpressionTests { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Object value = parser.parseExpression("simpleProperty").getValue(evaluationContext); - assertThat(value).isNotNull(); + assertThat(value).isInstanceOf(Boolean.class); } @Test @@ -1212,7 +1218,7 @@ public class SpelReproTests extends AbstractExpressionTests { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Class valueType = parser.parseExpression("primitiveProperty").getValueType(evaluationContext); - assertThat(valueType).isNotNull(); + assertThat(valueType).isEqualTo(Boolean.class); } @Test @@ -1220,7 +1226,7 @@ public class SpelReproTests extends AbstractExpressionTests { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Object value = parser.parseExpression("primitiveProperty").getValue(evaluationContext); - assertThat(value).isNotNull(); + assertThat(value).isInstanceOf(Boolean.class); } @Test @@ -2172,15 +2178,25 @@ public class SpelReproTests extends AbstractExpressionTests { private interface GenericInterface { + void setProperty(T value); + T getProperty(); } private static class GenericImplementation implements GenericInterface { + int value; + + @Override + public void setProperty(Integer value) { + this.value = value; + } + @Override + @Nullable public Integer getProperty() { - return null; + return this.value; } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.java index 25665a816e0..645538e8273 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/HandlerMethodReturnValueHandlerComposite.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"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.messaging.Message; -import org.springframework.util.Assert; import org.springframework.util.concurrent.ListenableFuture; /** @@ -139,9 +138,10 @@ public class HandlerMethodReturnValueHandlerComposite implements AsyncHandlerMet @Nullable public ListenableFuture toListenableFuture(Object returnValue, MethodParameter returnType) { HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); - Assert.state(handler instanceof AsyncHandlerMethodReturnValueHandler, - "AsyncHandlerMethodReturnValueHandler required"); - return ((AsyncHandlerMethodReturnValueHandler) handler).toListenableFuture(returnValue, returnType); + if (handler instanceof AsyncHandlerMethodReturnValueHandler) { + return ((AsyncHandlerMethodReturnValueHandler) handler).toListenableFuture(returnValue, returnType); + } + return null; } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ReactiveReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ReactiveReturnValueHandler.java index 7fb0f669649..c71e8293875 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ReactiveReturnValueHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ReactiveReturnValueHandler.java @@ -21,7 +21,6 @@ import reactor.core.publisher.Mono; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.util.Assert; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.MonoToListenableFutureAdapter; @@ -60,8 +59,10 @@ public class ReactiveReturnValueHandler extends AbstractAsyncReturnValueHandler @Override public ListenableFuture toListenableFuture(Object returnValue, MethodParameter returnType) { ReactiveAdapter adapter = this.adapterRegistry.getAdapter(returnType.getParameterType(), returnValue); - Assert.state(adapter != null, () -> "No ReactiveAdapter found for " + returnType.getParameterType()); - return new MonoToListenableFutureAdapter<>(Mono.from(adapter.toPublisher(returnValue))); + if (adapter != null) { + return new MonoToListenableFutureAdapter<>(Mono.from(adapter.toPublisher(returnValue))); + } + return null; } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java index 2b349c555f9..b43b9a265a8 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java @@ -74,6 +74,7 @@ import org.springframework.messaging.support.MessageHeaderInitializer; import org.springframework.stereotype.Controller; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.PathMatcher; import org.springframework.util.StringValueResolver; @@ -93,6 +94,10 @@ import org.springframework.validation.Validator; public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHandler implements EmbeddedValueResolverAware, SmartLifecycle { + private static final boolean reactorPresent = ClassUtils.isPresent( + "reactor.core.publisher.Flux", SimpAnnotationMethodMessageHandler.class.getClassLoader()); + + private final SubscribableChannel clientInboundChannel; private final SimpMessageSendingOperations clientMessagingTemplate; @@ -328,7 +333,9 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan handlers.add(new ListenableFutureReturnValueHandler()); handlers.add(new CompletableFutureReturnValueHandler()); - handlers.add(new ReactiveReturnValueHandler()); + if (reactorPresent) { + handlers.add(new ReactiveReturnValueHandler()); + } // Annotation-based return value types diff --git a/src/checkstyle/checkstyle.xml b/src/checkstyle/checkstyle.xml index 5c935eba060..be9d94bf6d2 100644 --- a/src/checkstyle/checkstyle.xml +++ b/src/checkstyle/checkstyle.xml @@ -45,7 +45,9 @@ - + + +