From c04400890f54bb2d342348085666066072b7bab9 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2020 11:23:12 +0200 Subject: [PATCH 1/6] Avoid repeated Charset resolution in MimeType Closes gh-25808 --- .../java/org/springframework/util/MimeType.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java index 1fea14ca60..1aa4162470 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeType.java +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -103,6 +103,9 @@ public class MimeType implements Comparable, Serializable { private final Map parameters; + @Nullable + private Charset resolvedCharset; + @Nullable private volatile String toStringValue; @@ -138,6 +141,7 @@ public class MimeType implements Comparable, Serializable { */ public MimeType(String type, String subtype, Charset charset) { this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charset.name())); + this.resolvedCharset = charset; } /** @@ -150,6 +154,7 @@ public class MimeType implements Comparable, Serializable { */ public MimeType(MimeType other, Charset charset) { this(other.getType(), other.getSubtype(), addCharsetParameter(charset, other.getParameters())); + this.resolvedCharset = charset; } /** @@ -197,7 +202,7 @@ public class MimeType implements Comparable, Serializable { * @see HTTP 1.1, section 2.2 */ private void checkToken(String token) { - for (int i = 0; i < token.length(); i++ ) { + for (int i = 0; i < token.length(); i++) { char ch = token.charAt(i); if (!TOKEN.get(ch)) { throw new IllegalArgumentException("Invalid token character '" + ch + "' in token \"" + token + "\""); @@ -210,8 +215,9 @@ public class MimeType implements Comparable, Serializable { Assert.hasLength(value, "'value' must not be empty"); checkToken(attribute); if (PARAM_CHARSET.equals(attribute)) { - value = unquote(value); - Charset.forName(value); + if (this.resolvedCharset == null) { + this.resolvedCharset = Charset.forName(unquote(value)); + } } else if (!isQuotedString(value)) { checkToken(value); @@ -279,8 +285,7 @@ public class MimeType implements Comparable, Serializable { */ @Nullable public Charset getCharset() { - String charset = getParameter(PARAM_CHARSET); - return (charset != null ? Charset.forName(unquote(charset)) : null); + return this.resolvedCharset; } /** From d9da663f6de9affcfa1ac78e5a69cd87e10c3444 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2020 11:23:38 +0200 Subject: [PATCH 2/6] Optimize String argument resolution in MessageTag Closes gh-25809 --- .../web/servlet/tags/MessageTag.java | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/MessageTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/MessageTag.java index 5ea3668ae1..ed240284af 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/MessageTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/MessageTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -17,9 +17,9 @@ package org.springframework.web.servlet.tags; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import javax.servlet.jsp.JspException; @@ -255,7 +255,7 @@ public class MessageTag extends HtmlEscapingAwareTag implements ArgumentAware { @Override protected final int doStartTagInternal() throws JspException, IOException { - this.nestedArguments = new LinkedList<>(); + this.nestedArguments = new ArrayList<>(); return EVAL_BODY_INCLUDE; } @@ -358,20 +358,7 @@ public class MessageTag extends HtmlEscapingAwareTag implements ArgumentAware { @Nullable protected Object[] resolveArguments(@Nullable Object arguments) throws JspException { if (arguments instanceof String) { - String[] stringArray = - StringUtils.delimitedListToStringArray((String) arguments, this.argumentSeparator); - if (stringArray.length == 1) { - Object argument = stringArray[0]; - if (argument != null && argument.getClass().isArray()) { - return ObjectUtils.toObjectArray(argument); - } - else { - return new Object[] {argument}; - } - } - else { - return stringArray; - } + return StringUtils.delimitedListToStringArray((String) arguments, this.argumentSeparator); } else if (arguments instanceof Object[]) { return (Object[]) arguments; @@ -395,7 +382,7 @@ public class MessageTag extends HtmlEscapingAwareTag implements ArgumentAware { * @throws IOException if writing failed */ protected void writeMessage(String msg) throws IOException { - this.pageContext.getOut().write(String.valueOf(msg)); + this.pageContext.getOut().write(msg); } /** From c83f6adc2432f54220a2915acaeb7f6b0731e42f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2020 11:24:26 +0200 Subject: [PATCH 3/6] Revise event multicaster locking for non-synchronized retriever caching Closes gh-25799 --- .../AbstractApplicationEventMulticaster.java | 152 +++++++++++------- .../event/ApplicationContextEventTests.java | 84 +++++++++- 2 files changed, 180 insertions(+), 56 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java index 9ffbf69804..9f0fd1d25b 100644 --- a/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -63,9 +63,9 @@ import org.springframework.util.ObjectUtils; public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { - private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); + private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever(); - final Map retrieverCache = new ConcurrentHashMap<>(64); + final Map retrieverCache = new ConcurrentHashMap<>(64); @Nullable private ClassLoader beanClassLoader; @@ -73,8 +73,6 @@ public abstract class AbstractApplicationEventMulticaster @Nullable private ConfigurableBeanFactory beanFactory; - private Object retrievalMutex = this.defaultRetriever; - @Override public void setBeanClassLoader(ClassLoader classLoader) { @@ -90,7 +88,6 @@ public abstract class AbstractApplicationEventMulticaster if (this.beanClassLoader == null) { this.beanClassLoader = this.beanFactory.getBeanClassLoader(); } - this.retrievalMutex = this.beanFactory.getSingletonMutex(); } private ConfigurableBeanFactory getBeanFactory() { @@ -104,7 +101,7 @@ public abstract class AbstractApplicationEventMulticaster @Override public void addApplicationListener(ApplicationListener listener) { - synchronized (this.retrievalMutex) { + synchronized (this.defaultRetriever) { // Explicitly remove target for a proxy, if registered already, // in order to avoid double invocations of the same listener. Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); @@ -118,7 +115,7 @@ public abstract class AbstractApplicationEventMulticaster @Override public void addApplicationListenerBean(String listenerBeanName) { - synchronized (this.retrievalMutex) { + synchronized (this.defaultRetriever) { this.defaultRetriever.applicationListenerBeans.add(listenerBeanName); this.retrieverCache.clear(); } @@ -126,7 +123,7 @@ public abstract class AbstractApplicationEventMulticaster @Override public void removeApplicationListener(ApplicationListener listener) { - synchronized (this.retrievalMutex) { + synchronized (this.defaultRetriever) { this.defaultRetriever.applicationListeners.remove(listener); this.retrieverCache.clear(); } @@ -134,7 +131,7 @@ public abstract class AbstractApplicationEventMulticaster @Override public void removeApplicationListenerBean(String listenerBeanName) { - synchronized (this.retrievalMutex) { + synchronized (this.defaultRetriever) { this.defaultRetriever.applicationListenerBeans.remove(listenerBeanName); this.retrieverCache.clear(); } @@ -142,7 +139,7 @@ public abstract class AbstractApplicationEventMulticaster @Override public void removeAllListeners() { - synchronized (this.retrievalMutex) { + synchronized (this.defaultRetriever) { this.defaultRetriever.applicationListeners.clear(); this.defaultRetriever.applicationListenerBeans.clear(); this.retrieverCache.clear(); @@ -156,7 +153,7 @@ public abstract class AbstractApplicationEventMulticaster * @see org.springframework.context.ApplicationListener */ protected Collection> getApplicationListeners() { - synchronized (this.retrievalMutex) { + synchronized (this.defaultRetriever) { return this.defaultRetriever.getApplicationListeners(); } } @@ -177,32 +174,34 @@ public abstract class AbstractApplicationEventMulticaster Class sourceType = (source != null ? source.getClass() : null); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); - // Quick check for existing entry on ConcurrentHashMap... - ListenerRetriever retriever = this.retrieverCache.get(cacheKey); - if (retriever != null) { - return retriever.getApplicationListeners(); - } - - if (this.beanClassLoader == null || - (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && - (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { - // Fully synchronized building and caching of a ListenerRetriever - synchronized (this.retrievalMutex) { - retriever = this.retrieverCache.get(cacheKey); - if (retriever != null) { - return retriever.getApplicationListeners(); + // Potential new retriever to populate + CachedListenerRetriever newRetriever = null; + + // Quick check for existing entry on ConcurrentHashMap + CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey); + if (existingRetriever == null) { + // Caching a new ListenerRetriever if possible + if (this.beanClassLoader == null || + (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && + (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { + newRetriever = new CachedListenerRetriever(); + existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever); + if (existingRetriever != null) { + newRetriever = null; // no need to populate it in retrieveApplicationListeners } - retriever = new ListenerRetriever(true); - Collection> listeners = - retrieveApplicationListeners(eventType, sourceType, retriever); - this.retrieverCache.put(cacheKey, retriever); - return listeners; } } - else { - // No ListenerRetriever caching -> no synchronization necessary - return retrieveApplicationListeners(eventType, sourceType, null); + + if (existingRetriever != null) { + Collection> result = existingRetriever.getApplicationListeners(); + if (result != null) { + return result; + } + // If result is null, the existing retriever is not fully populated yet by another thread. + // Proceed like caching wasn't possible for this current local attempt. } + + return retrieveApplicationListeners(eventType, sourceType, newRetriever); } /** @@ -213,12 +212,15 @@ public abstract class AbstractApplicationEventMulticaster * @return the pre-filtered list of application listeners for the given event and source type */ private Collection> retrieveApplicationListeners( - ResolvableType eventType, @Nullable Class sourceType, @Nullable ListenerRetriever retriever) { + ResolvableType eventType, @Nullable Class sourceType, @Nullable CachedListenerRetriever retriever) { List> allListeners = new ArrayList<>(); + Set> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null); + Set filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null); + Set> listeners; Set listenerBeans; - synchronized (this.retrievalMutex) { + synchronized (this.defaultRetriever) { listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans); } @@ -228,7 +230,7 @@ public abstract class AbstractApplicationEventMulticaster for (ApplicationListener listener : listeners) { if (supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { - retriever.applicationListeners.add(listener); + filteredListeners.add(listener); } allListeners.add(listener); } @@ -246,10 +248,10 @@ public abstract class AbstractApplicationEventMulticaster if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { if (beanFactory.isSingleton(listenerBeanName)) { - retriever.applicationListeners.add(listener); + filteredListeners.add(listener); } else { - retriever.applicationListenerBeans.add(listenerBeanName); + filteredListenerBeans.add(listenerBeanName); } } allListeners.add(listener); @@ -261,7 +263,7 @@ public abstract class AbstractApplicationEventMulticaster // BeanDefinition metadata (e.g. factory method generics) above. Object listener = beanFactory.getSingleton(listenerBeanName); if (retriever != null) { - retriever.applicationListeners.remove(listener); + filteredListeners.remove(listener); } allListeners.remove(listener); } @@ -274,9 +276,15 @@ public abstract class AbstractApplicationEventMulticaster } AnnotationAwareOrderComparator.sort(allListeners); - if (retriever != null && retriever.applicationListenerBeans.isEmpty()) { - retriever.applicationListeners.clear(); - retriever.applicationListeners.addAll(allListeners); + if (retriever != null) { + if (filteredListenerBeans.isEmpty()) { + retriever.applicationListeners = new LinkedHashSet<>(allListeners); + retriever.applicationListenerBeans = filteredListenerBeans; + } + else { + retriever.applicationListeners = filteredListeners; + retriever.applicationListenerBeans = filteredListenerBeans; + } } return allListeners; } @@ -415,17 +423,54 @@ public abstract class AbstractApplicationEventMulticaster * allowing for efficient retrieval of pre-filtered listeners. *

An instance of this helper gets cached per event type and source type. */ - private class ListenerRetriever { + private class CachedListenerRetriever { - public final Set> applicationListeners = new LinkedHashSet<>(); + @Nullable + public volatile Set> applicationListeners; - public final Set applicationListenerBeans = new LinkedHashSet<>(); + @Nullable + public volatile Set applicationListenerBeans; - private final boolean preFiltered; + @Nullable + public Collection> getApplicationListeners() { + Set> applicationListeners = this.applicationListeners; + Set applicationListenerBeans = this.applicationListenerBeans; + if (applicationListeners == null || applicationListenerBeans == null) { + // Not fully populated yet + return null; + } - public ListenerRetriever(boolean preFiltered) { - this.preFiltered = preFiltered; + List> allListeners = new ArrayList<>( + applicationListeners.size() + applicationListenerBeans.size()); + allListeners.addAll(applicationListeners); + if (!applicationListenerBeans.isEmpty()) { + BeanFactory beanFactory = getBeanFactory(); + for (String listenerBeanName : applicationListenerBeans) { + try { + allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class)); + } + catch (NoSuchBeanDefinitionException ex) { + // Singleton listener instance (without backing bean definition) disappeared - + // probably in the middle of the destruction phase + } + } + } + if (!applicationListenerBeans.isEmpty()) { + AnnotationAwareOrderComparator.sort(allListeners); + } + return allListeners; } + } + + + /** + * Helper class that encapsulates a general set of target listeners. + */ + private class DefaultListenerRetriever { + + public final Set> applicationListeners = new LinkedHashSet<>(); + + public final Set applicationListenerBeans = new LinkedHashSet<>(); public Collection> getApplicationListeners() { List> allListeners = new ArrayList<>( @@ -435,8 +480,9 @@ public abstract class AbstractApplicationEventMulticaster BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : this.applicationListenerBeans) { try { - ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); - if (this.preFiltered || !allListeners.contains(listener)) { + ApplicationListener listener = + beanFactory.getBean(listenerBeanName, ApplicationListener.class); + if (!allListeners.contains(listener)) { allListeners.add(listener); } } @@ -446,9 +492,7 @@ public abstract class AbstractApplicationEventMulticaster } } } - if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) { - AnnotationAwareOrderComparator.sort(allListeners); - } + AnnotationAwareOrderComparator.sort(allListeners); return allListeners; } } diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index 3b40c090d7..331336a848 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -16,8 +16,8 @@ package org.springframework.context.event; +import java.util.ArrayList; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -35,6 +36,8 @@ import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.PayloadApplicationEvent; import org.springframework.context.support.AbstractApplicationContext; @@ -374,6 +377,19 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen assertThat(MyNonSingletonListener.seenEvents.contains(event4)).isTrue(); MyNonSingletonListener.seenEvents.clear(); + context.publishEvent(event1); + context.publishEvent(event2); + context.publishEvent(event3); + context.publishEvent(event4); + assertThat(MyNonSingletonListener.seenEvents.contains(event1)).isTrue(); + assertThat(MyNonSingletonListener.seenEvents.contains(event2)).isTrue(); + assertThat(MyNonSingletonListener.seenEvents.contains(event3)).isTrue(); + assertThat(MyNonSingletonListener.seenEvents.contains(event4)).isTrue(); + MyNonSingletonListener.seenEvents.clear(); + + AbstractApplicationEventMulticaster multicaster = context.getBean(AbstractApplicationEventMulticaster.class); + assertThat(multicaster.retrieverCache.size()).isEqualTo(3); + context.close(); } @@ -516,6 +532,36 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen context.close(); } + @Test + public void initMethodPublishesEvent() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("listener", new RootBeanDefinition(BeanThatListens.class)); + context.registerBeanDefinition("messageSource", new RootBeanDefinition(StaticMessageSource.class)); + context.registerBeanDefinition("initMethod", new RootBeanDefinition(EventPublishingInitMethod.class)); + context.refresh(); + + context.publishEvent(new MyEvent(this)); + BeanThatListens listener = context.getBean(BeanThatListens.class); + assertThat(listener.getEventCount()).isEqualTo(3); + + context.close(); + } + + @Test + public void initMethodPublishesAsyncEvent() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("listener", new RootBeanDefinition(BeanThatListens.class)); + context.registerBeanDefinition("messageSource", new RootBeanDefinition(StaticMessageSource.class)); + context.registerBeanDefinition("initMethod", new RootBeanDefinition(AsyncEventPublishingInitMethod.class)); + context.refresh(); + + context.publishEvent(new MyEvent(this)); + BeanThatListens listener = context.getBean(BeanThatListens.class); + assertThat(listener.getEventCount()).isEqualTo(3); + + context.close(); + } + @SuppressWarnings("serial") public static class MyEvent extends ApplicationEvent { @@ -537,7 +583,7 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen public static class MyOrderedListener1 implements ApplicationListener, Ordered { - public final List seenEvents = new LinkedList<>(); + public final List seenEvents = new ArrayList<>(); @Override public void onApplicationEvent(ApplicationEvent event) { @@ -652,4 +698,38 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen } } + + public static class EventPublishingInitMethod implements ApplicationEventPublisherAware, InitializingBean { + + private ApplicationEventPublisher publisher; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.publisher.publishEvent(new MyEvent(this)); + } + } + + + public static class AsyncEventPublishingInitMethod implements ApplicationEventPublisherAware, InitializingBean { + + private ApplicationEventPublisher publisher; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } + + @Override + public void afterPropertiesSet() throws Exception { + Thread thread = new Thread(() -> this.publisher.publishEvent(new MyEvent(this))); + thread.start(); + thread.join(); + } + } + } From 21cb9e8bffd11442469195dc7e0d5cbe5b6e148f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2020 11:24:50 +0200 Subject: [PATCH 4/6] Translate NullBean result to null for lookup method with bean name Closes gh-25806 --- .../CglibSubclassingInstantiationStrategy.java | 6 ++++-- .../annotation/LookupAnnotationTests.java | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 88e9942e80..698a04d249 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -238,8 +238,10 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt Assert.state(lo != null, "LookupOverride not found"); Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all if (StringUtils.hasText(lo.getBeanName())) { - return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : + Object bean = (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : this.owner.getBean(lo.getBeanName())); + // Detect package-protected NullBean instance through equals(null) check + return (bean.equals(null) ? null : bean); } else { return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java index d9b198e3e3..bd30b5b44c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -108,10 +108,23 @@ public class LookupAnnotationTests { assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); } + @Test // gh-25806 + public void testWithNullBean() { + RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class, () -> null); + tbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + beanFactory.registerBeanDefinition("testBean", tbd); + + AbstractBean bean = beanFactory.getBean("beanConsumer", BeanConsumer.class).abstractBean; + assertThat(bean).isNotNull(); + Object expected = bean.get(); + assertThat(expected).isNull(); + assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); + } + public static abstract class AbstractBean { - @Lookup + @Lookup("testBean") public abstract TestBean get(); @Lookup From 392ad0999076ed1158ba9139a0a71e5a298474de Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2020 11:25:05 +0200 Subject: [PATCH 5/6] Add MariaDB to the list of supported database products for procedures Closes gh-25811 --- .../jdbc/core/metadata/CallMetaDataProviderFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java index 2e8480b914..4e52119f74 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/CallMetaDataProviderFactory.java @@ -43,6 +43,7 @@ public final class CallMetaDataProviderFactory { "Apache Derby", "DB2", "Informix Dynamic Server", + "MariaDB", "Microsoft SQL Server", "MySQL", "Oracle", From 6e4fcb69f01856957abad75dd48be016d2d7dfa0 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Sep 2020 11:26:01 +0200 Subject: [PATCH 6/6] Polishing --- .../org/springframework/util/ObjectUtils.java | 13 +++--- .../MappingJackson2MessageConverter.java | 4 +- .../converter/AbstractMessageConverter.java | 44 ++++++++++--------- .../MappingJackson2MessageConverter.java | 8 ++-- .../MarshallingMessageConverter.java | 7 ++- .../converter/ProtobufMessageConverter.java | 4 +- .../MappingJackson2MessageConverterTests.java | 41 ++++++++--------- .../codec/json/AbstractJackson2Decoder.java | 2 +- .../http/codec/json/Jackson2CodecSupport.java | 4 +- .../http/converter/HttpMessageConverter.java | 4 +- .../AbstractJackson2HttpMessageConverter.java | 4 +- .../web/client/RestTemplate.java | 12 +++-- .../web/util/UriComponentsBuilder.java | 22 ++++------ .../web/servlet/view/RedirectView.java | 8 ++-- 14 files changed, 84 insertions(+), 93 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java index 397ff3a7cc..83428aa32a 100644 --- a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -132,14 +132,13 @@ public abstract class ObjectUtils { * @see CollectionUtils#isEmpty(java.util.Collection) * @see CollectionUtils#isEmpty(java.util.Map) */ - @SuppressWarnings("rawtypes") public static boolean isEmpty(@Nullable Object obj) { if (obj == null) { return true; } if (obj instanceof Optional) { - return !((Optional) obj).isPresent(); + return !((Optional) obj).isPresent(); } if (obj instanceof CharSequence) { return ((CharSequence) obj).length() == 0; @@ -148,10 +147,10 @@ public abstract class ObjectUtils { return Array.getLength(obj) == 0; } if (obj instanceof Collection) { - return ((Collection) obj).isEmpty(); + return ((Collection) obj).isEmpty(); } if (obj instanceof Map) { - return ((Map) obj).isEmpty(); + return ((Map) obj).isEmpty(); } // else @@ -611,9 +610,7 @@ public abstract class ObjectUtils { if (obj == null) { return EMPTY_STRING; } - String className = obj.getClass().getName(); - String identityHexString = getIdentityHexString(obj); - return className + '@' + identityHexString; + return obj.getClass().getName() + "@" + getIdentityHexString(obj); } /** diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java index d8823c1800..f4a8cf31f1 100644 --- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java +++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java @@ -457,11 +457,11 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B } Class mappedClass = this.idClassMappings.get(typeId); if (mappedClass != null) { - return this.objectMapper.getTypeFactory().constructType(mappedClass); + return this.objectMapper.constructType(mappedClass); } try { Class typeClass = ClassUtils.forName(typeId, this.beanClassLoader); - return this.objectMapper.getTypeFactory().constructType(typeClass); + return this.objectMapper.constructType(typeClass); } catch (Throwable ex) { throw new MessageConversionException("Failed to resolve type id [" + typeId + "]", ex); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java index 0dd92e1206..3617635950 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -92,7 +92,7 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter } /** - * Allows sub-classes to add more supported mime types. + * Allows subclasses to add more supported mime types. * @since 5.2.2 */ protected void addSupportedMimeTypes(MimeType... supportedMimeTypes) { @@ -167,21 +167,6 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter } - /** - * Returns the default content type for the payload. Called when - * {@link #toMessage(Object, MessageHeaders)} is invoked without message headers or - * without a content type header. - *

By default, this returns the first element of the {@link #getSupportedMimeTypes() - * supportedMimeTypes}, if any. Can be overridden in sub-classes. - * @param payload the payload being converted to message - * @return the content type, or {@code null} if not known - */ - @Nullable - protected MimeType getDefaultContentType(Object payload) { - List mimeTypes = getSupportedMimeTypes(); - return (!mimeTypes.isEmpty() ? mimeTypes.get(0) : null); - } - @Override @Nullable public final Object fromMessage(Message message, Class targetClass) { @@ -197,10 +182,6 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter return convertFromInternal(message, targetClass, conversionHint); } - protected boolean canConvertFrom(Message message, Class targetClass) { - return (supports(targetClass) && supportsMimeType(message.getHeaders())); - } - @Override @Nullable public final Message toMessage(Object payload, @Nullable MessageHeaders headers) { @@ -240,6 +221,11 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter return builder.build(); } + + protected boolean canConvertFrom(Message message, Class targetClass) { + return (supports(targetClass) && supportsMimeType(message.getHeaders())); + } + protected boolean canConvertTo(Object payload, @Nullable MessageHeaders headers) { return (supports(payload.getClass()) && supportsMimeType(headers)); } @@ -265,6 +251,22 @@ public abstract class AbstractMessageConverter implements SmartMessageConverter return (headers != null && this.contentTypeResolver != null ? this.contentTypeResolver.resolve(headers) : null); } + /** + * Return the default content type for the payload. Called when + * {@link #toMessage(Object, MessageHeaders)} is invoked without + * message headers or without a content type header. + *

By default, this returns the first element of the + * {@link #getSupportedMimeTypes() supportedMimeTypes}, if any. + * Can be overridden in subclasses. + * @param payload the payload being converted to a message + * @return the content type, or {@code null} if not known + */ + @Nullable + protected MimeType getDefaultContentType(Object payload) { + List mimeTypes = getSupportedMimeTypes(); + return (!mimeTypes.isEmpty() ? mimeTypes.get(0) : null); + } + /** * Whether the given class is supported by this converter. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java index fe4a194f0a..e0c0129cbd 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java @@ -41,6 +41,7 @@ import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.MimeType; /** @@ -139,6 +140,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { } } + @Override protected boolean canConvertFrom(Message message, @Nullable Class targetClass) { if (targetClass == null || !supportsMimeType(message.getHeaders())) { @@ -210,7 +212,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { Object payload = message.getPayload(); Class view = getSerializationView(conversionHint); try { - if (targetClass.isInstance(payload)) { + if (ClassUtils.isAssignableValue(targetClass, payload)) { return payload; } else if (payload instanceof byte[]) { @@ -246,7 +248,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { Type genericParameterType = param.getNestedGenericParameterType(); Class contextClass = param.getContainingClass(); Type type = GenericTypeResolver.resolveType(genericParameterType, contextClass); - return this.objectMapper.getTypeFactory().constructType(type); + return this.objectMapper.constructType(type); } return this.objectMapper.constructType(targetClass); } @@ -331,7 +333,7 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter { * @return the JSON encoding to use (never {@code null}) */ protected JsonEncoding getJsonEncoding(@Nullable MimeType contentType) { - if (contentType != null && (contentType.getCharset() != null)) { + if (contentType != null && contentType.getCharset() != null) { Charset charset = contentType.getCharset(); for (JsonEncoding encoding : JsonEncoding.values()) { if (charset.name().equals(encoding.getJavaName())) { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java index ccc21edf09..31e9b8223a 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java @@ -46,6 +46,8 @@ import org.springframework.util.MimeType; * * @author Arjen Poutsma * @since 4.2 + * @see Marshaller + * @see Unmarshaller */ public class MarshallingMessageConverter extends AbstractMessageConverter { @@ -61,7 +63,8 @@ public class MarshallingMessageConverter extends AbstractMessageConverter { * {@link #setUnmarshaller(Unmarshaller)} to be invoked separately. */ public MarshallingMessageConverter() { - this(new MimeType("application", "xml"), new MimeType("text", "xml"), new MimeType("application", "*+xml")); + this(new MimeType("application", "xml"), new MimeType("text", "xml"), + new MimeType("application", "*+xml")); } /** @@ -160,7 +163,7 @@ public class MarshallingMessageConverter extends AbstractMessageConverter { return new StreamSource(new ByteArrayInputStream((byte[]) payload)); } else { - return new StreamSource(new StringReader((String) payload)); + return new StreamSource(new StringReader(payload.toString())); } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/ProtobufMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/ProtobufMessageConverter.java index a597207739..085957e4d4 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/ProtobufMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/ProtobufMessageConverter.java @@ -120,9 +120,9 @@ public class ProtobufMessageConverter extends AbstractMessageConverter { @Override protected boolean canConvertTo(Object payload, @Nullable MessageHeaders headers) { - MimeType mimeType = getMimeType(headers); + MimeType contentType = getMimeType(headers); return (super.canConvertTo(payload, headers) || - this.protobufFormatSupport != null && this.protobufFormatSupport.supportsWriteOnly(mimeType)); + this.protobufFormatSupport != null && this.protobufFormatSupport.supportsWriteOnly(contentType)); } @Override diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java index efca6b26fe..1e69ff4137 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/MappingJackson2MessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -48,10 +48,9 @@ public class MappingJackson2MessageConverterTests { @Test public void defaultConstructor() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); - assertThat(converter.getSupportedMimeTypes()) - .contains(new MimeType("application", "json")); + assertThat(converter.getSupportedMimeTypes()).contains(new MimeType("application", "json")); assertThat(converter.getObjectMapper().getDeserializationConfig() - .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); } @Test // SPR-12724 @@ -60,7 +59,7 @@ public class MappingJackson2MessageConverterTests { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(mimetype); assertThat(converter.getSupportedMimeTypes()).contains(mimetype); assertThat(converter.getObjectMapper().getDeserializationConfig() - .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); } @Test // SPR-12724 @@ -70,19 +69,14 @@ public class MappingJackson2MessageConverterTests { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(jsonMimetype, xmlMimetype); assertThat(converter.getSupportedMimeTypes()).contains(jsonMimetype, xmlMimetype); assertThat(converter.getObjectMapper().getDeserializationConfig() - .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); } @Test public void fromMessage() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); - String payload = "{" + - "\"bytes\":\"AQI=\"," + - "\"array\":[\"Foo\",\"Bar\"]," + - "\"number\":42," + - "\"string\":\"Foo\"," + - "\"bool\":true," + - "\"fraction\":42.0}"; + String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"]," + + "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; Message message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build(); MyBean actual = (MyBean) converter.fromMessage(message, MyBean.class); @@ -97,8 +91,8 @@ public class MappingJackson2MessageConverterTests { @Test public void fromMessageUntyped() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); - String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"]," - + "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; + String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"]," + + "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; Message message = MessageBuilder.withPayload(payload.getBytes(StandardCharsets.UTF_8)).build(); @SuppressWarnings("unchecked") HashMap actual = (HashMap) converter.fromMessage(message, HashMap.class); @@ -111,7 +105,7 @@ public class MappingJackson2MessageConverterTests { assertThat(actual.get("bytes")).isEqualTo("AQI="); } - @Test // gh-22386 + @Test // gh-22386 public void fromMessageMatchingInstance() { MyBean myBean = new MyBean(); MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); @@ -137,7 +131,7 @@ public class MappingJackson2MessageConverterTests { assertThat(myBean.getString()).isEqualTo("string"); } - @Test // SPR-16252 + @Test // SPR-16252 public void fromMessageToList() throws Exception { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); String payload = "[1, 2, 3, 4, 5, 6, 7, 8, 9]"; @@ -151,7 +145,7 @@ public class MappingJackson2MessageConverterTests { assertThat(actual).isEqualTo(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L)); } - @Test // SPR-16486 + @Test // SPR-16486 public void fromMessageToMessageWithPojo() throws Exception { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); String payload = "{\"string\":\"foo\"}"; @@ -198,7 +192,7 @@ public class MappingJackson2MessageConverterTests { String payload = "H\u00e9llo W\u00f6rld"; Message message = converter.toMessage(payload, headers); - assertThat(new String((byte[]) message.getPayload(), StandardCharsets.UTF_16BE)).isEqualTo(("\"" + payload + "\"")); + assertThat(new String((byte[]) message.getPayload(), StandardCharsets.UTF_16BE)).isEqualTo("\"" + payload + "\""); assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(contentType); } @@ -214,7 +208,7 @@ public class MappingJackson2MessageConverterTests { String payload = "H\u00e9llo W\u00f6rld"; Message message = converter.toMessage(payload, headers); - assertThat(message.getPayload()).isEqualTo(("\"" + payload + "\"")); + assertThat(message.getPayload()).isEqualTo("\"" + payload + "\""); assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(contentType); } @@ -254,9 +248,12 @@ public class MappingJackson2MessageConverterTests { public void jsonViewPayload(@JsonView(MyJacksonView2.class) JacksonViewBean payload) { } - void handleList(List payload) {} + void handleList(List payload) { + } + + void handleMessage(Message message) { + } - void handleMessage(Message message) {} public static class MyBean { diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java index daf06e3d55..a0b400ef7d 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java @@ -97,7 +97,7 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple @Override public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { - JavaType javaType = getObjectMapper().getTypeFactory().constructType(elementType.getType()); + JavaType javaType = getObjectMapper().constructType(elementType.getType()); // Skip String: CharSequenceDecoder + "*/*" comes after return (!CharSequence.class.isAssignableFrom(elementType.toClass()) && getObjectMapper().canDeserialize(javaType) && supportsMimeType(mimeType)); diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java index 6162be280c..a6720f5082 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java @@ -27,7 +27,6 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.TypeFactory; import org.apache.commons.logging.Log; import org.springframework.core.GenericTypeResolver; @@ -111,8 +110,7 @@ public abstract class Jackson2CodecSupport { } protected JavaType getJavaType(Type type, @Nullable Class contextClass) { - TypeFactory typeFactory = this.objectMapper.getTypeFactory(); - return typeFactory.constructType(GenericTypeResolver.resolveType(type, contextClass)); + return this.objectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass)); } protected Map getHints(ResolvableType resolvableType) { diff --git a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java index 55260af656..a0583b9c6d 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -25,7 +25,7 @@ import org.springframework.http.MediaType; import org.springframework.lang.Nullable; /** - * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. + * Strategy interface for converting from and to HTTP requests and responses. * * @author Arjen Poutsma * @author Juergen Hoeller diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index bf6f1ca48c..7f3b59fee4 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -43,7 +43,6 @@ import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.core.GenericTypeResolver; import org.springframework.http.HttpInputMessage; @@ -377,8 +376,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener * @return the Jackson JavaType */ protected JavaType getJavaType(Type type, @Nullable Class contextClass) { - TypeFactory typeFactory = this.objectMapper.getTypeFactory(); - return typeFactory.constructType(GenericTypeResolver.resolveType(type, contextClass)); + return this.objectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass)); } /** diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index d8bb4f730f..c1b875b545 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -21,7 +21,6 @@ import java.lang.reflect.Type; import java.net.URI; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -110,9 +109,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat ClassLoader classLoader = RestTemplate.class.getClassLoader(); romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader); jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader); - jackson2Present = - ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && - ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); + jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && + ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader); jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader); @@ -918,7 +916,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat HttpHeaders httpHeaders = httpRequest.getHeaders(); HttpHeaders requestHeaders = this.requestEntity.getHeaders(); if (!requestHeaders.isEmpty()) { - requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values))); + requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); } if (httpHeaders.getContentLength() < 0) { httpHeaders.setContentLength(0L); @@ -937,7 +935,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat (GenericHttpMessageConverter) messageConverter; if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { - requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values))); + requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); } logBody(requestBody, requestContentType, genericConverter); genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest); @@ -946,7 +944,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } else if (messageConverter.canWrite(requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { - requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values))); + requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); } logBody(requestBody, requestContentType, messageConverter); ((HttpMessageConverter) messageConverter).write( diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 6cfdf0f4f6..42e3ac1807 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -32,6 +32,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; @@ -237,7 +238,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { } builder.scheme(scheme); if (opaque) { - String ssp = uri.substring(scheme.length()).substring(1); + String ssp = uri.substring(scheme.length() + 1); if (StringUtils.hasLength(fragment)) { ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1)); } @@ -402,9 +403,8 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { * characters that should have been encoded. */ public UriComponents build(boolean encoded) { - return buildInternal(encoded ? - EncodingHint.FULLY_ENCODED : - this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE); + return buildInternal(encoded ? EncodingHint.FULLY_ENCODED : + (this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE)); } private UriComponents buildInternal(EncodingHint hint) { @@ -416,8 +416,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, hint == EncodingHint.FULLY_ENCODED); - - result = hint == EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric; + result = (hint == EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric); } if (!this.uriVariables.isEmpty()) { result = result.expand(name -> this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE)); @@ -474,9 +473,8 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { * @see UriComponents#toUriString() */ public String toUriString() { - return this.uriVariables.isEmpty() ? - build().encode().toUriString() : - buildInternal(EncodingHint.ENCODE_TEMPLATE).toUriString(); + return (this.uriVariables.isEmpty() ? build().encode().toUriString() : + buildInternal(EncodingHint.ENCODE_TEMPLATE).toUriString()); } @@ -660,7 +658,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { @Override public UriComponentsBuilder queryParam(String name, @Nullable Collection values) { - return queryParam(name, values != null ? values.toArray() : EMPTY_VALUES); + return queryParam(name, (CollectionUtils.isEmpty(values) ? EMPTY_VALUES : values.toArray())); } /** @@ -689,7 +687,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { @Override public UriComponentsBuilder replaceQueryParam(String name, @Nullable Collection values) { - return replaceQueryParam(name, values != null ? values.toArray() : EMPTY_VALUES); + return replaceQueryParam(name, (CollectionUtils.isEmpty(values) ? EMPTY_VALUES : values.toArray())); } /** @@ -778,12 +776,10 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { scheme("https"); port(null); } - String hostHeader = headers.getFirst("X-Forwarded-Host"); if (StringUtils.hasText(hostHeader)) { adaptForwardedHost(StringUtils.tokenizeToStringArray(hostHeader, ",")[0]); } - String portHeader = headers.getFirst("X-Forwarded-Port"); if (StringUtils.hasText(portHeader)) { port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0])); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java index 64315aac85..4c64b129d8 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/RedirectView.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.net.URLEncoder; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; @@ -35,6 +34,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; @@ -456,12 +456,12 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView { boolean first = (targetUrl.toString().indexOf('?') < 0); for (Map.Entry entry : queryProperties(model).entrySet()) { Object rawValue = entry.getValue(); - Collection values; + Collection values; if (rawValue != null && rawValue.getClass().isArray()) { - values = Arrays.asList(ObjectUtils.toObjectArray(rawValue)); + values = CollectionUtils.arrayToList(rawValue); } else if (rawValue instanceof Collection) { - values = ((Collection) rawValue); + values = ((Collection) rawValue); } else { values = Collections.singleton(rawValue);