Introduce null-safety of Spring Framework API

This commit introduces 2 new @Nullable and @NonNullApi
annotations that leverage JSR 305 (dormant but available via
Findbugs jsr305 dependency and already used by libraries
like OkHttp) meta-annotations to specify explicitly
null-safety of Spring Framework parameters and return values.

In order to avoid adding too much annotations, the
default is set at package level with @NonNullApi and
@Nullable annotations are added when needed at parameter or
return value level. These annotations are intended to be used
on Spring Framework itself but also by other Spring projects.

@Nullable annotations have been introduced based on Javadoc
and search of patterns like "return null;". It is expected that
nullability of Spring Framework API will be polished with
complementary commits.

In practice, this will make the whole Spring Framework API
null-safe for Kotlin projects (when KT-10942 will be fixed)
since Kotlin will be able to leverage these annotations to
know if a parameter or a return value is nullable or not. But
this is also useful for Java developers as well since IntelliJ
IDEA, for example, also understands these annotations to
generate warnings when unsafe nullable usages are detected.

Issue: SPR-15540
This commit is contained in:
Sebastien Deleuze
2017-05-27 08:14:59 +02:00
parent 2d37c966b2
commit 87598f48e4
1315 changed files with 4831 additions and 963 deletions

View File

@@ -29,6 +29,7 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -88,7 +89,7 @@ public class CaffeineCacheManager implements CacheManager {
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(Collection<String> cacheNames) {
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
for (String name : cacheNames) {
this.cacheMap.put(name, createCaffeineCache(name));

View File

@@ -3,4 +3,7 @@
* <a href="https://github.com/ben-manes/caffeine/">Caffeine</a> library,
* allowing to set up Caffeine caches within Spring's cache abstraction.
*/
@NonNullApi
package org.springframework.cache.caffeine;
import org.springframework.lang.NonNullApi;

View File

@@ -9,4 +9,7 @@
* Instead, consider using it through JCache (JSR-107), with
* Spring's support in {@code org.springframework.cache.jcache}.
*/
@NonNullApi
package org.springframework.cache.ehcache;
import org.springframework.lang.NonNullApi;

View File

@@ -25,6 +25,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
/**
* {@link FactoryBean} for a JCache {@link javax.cache.CacheManager},
@@ -54,7 +55,7 @@ public class JCacheManagerFactoryBean
* Specify the URI for the desired CacheManager.
* Default is {@code null} (i.e. JCache's default).
*/
public void setCacheManagerUri(URI cacheManagerUri) {
public void setCacheManagerUri(@Nullable URI cacheManagerUri) {
this.cacheManagerUri = cacheManagerUri;
}
@@ -63,7 +64,7 @@ public class JCacheManagerFactoryBean
* Default is {@code null} (i.e. no special properties to apply).
* @see javax.cache.spi.CachingProvider#getCacheManager(URI, ClassLoader, Properties)
*/
public void setCacheManagerProperties(Properties cacheManagerProperties) {
public void setCacheManagerProperties(@Nullable Properties cacheManagerProperties) {
this.cacheManagerProperties = cacheManagerProperties;
}

View File

@@ -28,6 +28,7 @@ import org.springframework.cache.interceptor.AbstractCacheInvoker;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
@@ -71,6 +72,7 @@ abstract class AbstractCacheInterceptor<O extends AbstractJCacheOperation<A>, A
* <p>Throw an {@link IllegalStateException} if the collection holds more than one element
* @return the single element or {@code null} if the collection is empty
*/
@Nullable
static Cache extractFrom(Collection<? extends Cache> caches) {
if (CollectionUtils.isEmpty(caches)) {
return null;

View File

@@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
@@ -77,6 +78,7 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
}
}
@Nullable
private JCacheOperation<?> computeCacheOperation(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
@@ -113,6 +115,7 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
* @return the cache operation associated with this method
* (or {@code null} if none)
*/
@Nullable
protected abstract JCacheOperation<?> findCacheOperation(Method method, Class<?> targetType);
/**

View File

@@ -31,6 +31,7 @@ import javax.cache.annotation.CacheResult;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
@@ -157,6 +158,7 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
}
}
@Nullable
protected CacheResolverFactory determineCacheResolverFactory(CacheDefaults defaults,
Class<? extends CacheResolverFactory> candidate) {

View File

@@ -23,6 +23,7 @@ import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.ExceptionTypeFilter;
import org.springframework.util.SerializationUtils;
@@ -92,6 +93,7 @@ class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheResultOper
}
}
@Nullable
private Cache resolveExceptionCache(CacheOperationInvocationContext<CacheResultOperation> context) {
CacheResolver exceptionCacheResolver = context.getOperation().getExceptionCacheResolver();
if (exceptionCacheResolver != null) {

View File

@@ -21,6 +21,7 @@ import javax.cache.annotation.CacheResult;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.lang.Nullable;
import org.springframework.util.ExceptionTypeFilter;
import org.springframework.util.StringUtils;
@@ -78,6 +79,7 @@ class CacheResultOperation extends AbstractJCacheKeyOperation<CacheResult> {
* caching exceptions should be disabled.
* @see javax.cache.annotation.CacheResult#exceptionCacheName()
*/
@Nullable
public String getExceptionCacheName() {
return this.exceptionCacheName;
}

View File

@@ -32,6 +32,7 @@ import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -69,6 +70,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
/**
* Return the specified cache manager to use, if any.
*/
@Nullable
public CacheManager getCacheManager() {
return this.cacheManager;
}
@@ -84,6 +86,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
/**
* Return the specified cache resolver to use, if any.
*/
@Nullable
public CacheResolver getCacheResolver() {
return this.cacheResolver;
}
@@ -99,6 +102,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
/**
* Return the specified exception cache resolver to use, if any.
*/
@Nullable
public CacheResolver getExceptionCacheResolver() {
return this.exceptionCacheResolver;
}
@@ -115,6 +119,7 @@ public class DefaultJCacheOperationSource extends AnnotationJCacheOperationSourc
/**
* Return the specified key generator to use, if any.
*/
@Nullable
public KeyGenerator getKeyGenerator() {
return this.keyGenerator;
}

View File

@@ -18,6 +18,8 @@ package org.springframework.cache.jcache.interceptor;
import java.lang.reflect.Method;
import org.springframework.lang.Nullable;
/**
* Interface used by {@link JCacheInterceptor}. Implementations know how to source
* cache operation attributes from standard JSR-107 annotations.
@@ -37,6 +39,7 @@ public interface JCacheOperationSource {
* the declaring class of the method must be used)
* @return the cache operation for this method, or {@code null} if none found
*/
JCacheOperation<?> getCacheOperation(Method method, Class<?> targetClass);
@Nullable
JCacheOperation<?> getCacheOperation(Method method, @Nullable Class<?> targetClass);
}

View File

@@ -20,6 +20,7 @@ import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
@@ -43,6 +44,7 @@ public abstract class JCacheOperationSourcePointcut
* Obtain the underlying {@link JCacheOperationSource} (may be {@code null}).
* To be implemented by subclasses.
*/
@Nullable
protected abstract JCacheOperationSource getCacheOperationSource();
@Override

View File

@@ -4,4 +4,7 @@
* and {@link org.springframework.cache.Cache Cache} implementation for
* use in a Spring context, using a JSR-107 compliant cache provider.
*/
@NonNullApi
package org.springframework.cache.jcache;
import org.springframework.lang.NonNullApi;

View File

@@ -2,4 +2,7 @@
* Transaction-aware decorators for the org.springframework.cache package.
* Provides synchronization of put operations with Spring-managed transactions.
*/
@NonNullApi
package org.springframework.cache.transaction;
import org.springframework.lang.NonNullApi;

View File

@@ -25,6 +25,7 @@ import javax.activation.MimetypesFileTypeMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
/**
* Spring-configurable {@code FileTypeMap} implementation that will read
@@ -139,7 +140,7 @@ public class ConfigurableMimeFileTypeMap extends FileTypeMap implements Initiali
* @see javax.activation.MimetypesFileTypeMap#MimetypesFileTypeMap(java.io.InputStream)
* @see javax.activation.MimetypesFileTypeMap#addMimeTypes(String)
*/
protected FileTypeMap createFileTypeMap(Resource mappingLocation, String[] mappings) throws IOException {
protected FileTypeMap createFileTypeMap(@Nullable Resource mappingLocation, @Nullable String[] mappings) throws IOException {
MimetypesFileTypeMap fileTypeMap = null;
if (mappingLocation != null) {
InputStream is = mappingLocation.getInputStream();

View File

@@ -31,6 +31,7 @@ import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
import org.springframework.lang.Nullable;
import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.MailException;
import org.springframework.mail.MailParseException;
@@ -218,6 +219,7 @@ public class JavaMailSenderImpl implements JavaMailSender {
/**
* Return the username for the account at the mail host.
*/
@Nullable
public String getUsername() {
return this.username;
}
@@ -240,6 +242,7 @@ public class JavaMailSenderImpl implements JavaMailSender {
/**
* Return the password for the account at the mail host.
*/
@Nullable
public String getPassword() {
return this.password;
}
@@ -257,6 +260,7 @@ public class JavaMailSenderImpl implements JavaMailSender {
* Return the default encoding for {@link MimeMessage MimeMessages},
* or {@code null} if none.
*/
@Nullable
public String getDefaultEncoding() {
return this.defaultEncoding;
}
@@ -282,6 +286,7 @@ public class JavaMailSenderImpl implements JavaMailSender {
* Return the default Java Activation {@link FileTypeMap} for
* {@link MimeMessage MimeMessages}, or {@code null} if none.
*/
@Nullable
public FileTypeMap getDefaultFileTypeMap() {
return this.defaultFileTypeMap;
}

View File

@@ -39,6 +39,7 @@ import javax.mail.internet.MimeUtility;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -361,7 +362,7 @@ public class MimeMessageHelper {
* will be added to (can be the same as the root multipart object, or an element
* nested underneath the root multipart element)
*/
protected final void setMimeMultiparts(MimeMultipart root, MimeMultipart main) {
protected final void setMimeMultiparts(@Nullable MimeMultipart root, MimeMultipart main) {
this.rootMimeMultipart = root;
this.mimeMultipart = main;
}
@@ -423,6 +424,7 @@ public class MimeMessageHelper {
* @return the default encoding associated with the MimeMessage,
* or {@code null} if none found
*/
@Nullable
protected String getDefaultEncoding(MimeMessage mimeMessage) {
if (mimeMessage instanceof SmartMimeMessage) {
return ((SmartMimeMessage) mimeMessage).getDefaultEncoding();
@@ -433,6 +435,7 @@ public class MimeMessageHelper {
/**
* Return the specific character encoding used for this message, if any.
*/
@Nullable
public String getEncoding() {
return this.encoding;
}

View File

@@ -20,6 +20,8 @@ import javax.activation.FileTypeMap;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import org.springframework.lang.Nullable;
/**
* Special subclass of the standard JavaMail {@link MimeMessage}, carrying a
* default encoding to be used when populating the message and a default Java
@@ -48,7 +50,7 @@ class SmartMimeMessage extends MimeMessage {
* @param defaultEncoding the default encoding, or {@code null} if none
* @param defaultFileTypeMap the default FileTypeMap, or {@code null} if none
*/
public SmartMimeMessage(Session session, String defaultEncoding, FileTypeMap defaultFileTypeMap) {
public SmartMimeMessage(Session session, @Nullable String defaultEncoding, @Nullable FileTypeMap defaultFileTypeMap) {
super(session);
this.defaultEncoding = defaultEncoding;
this.defaultFileTypeMap = defaultFileTypeMap;
@@ -58,6 +60,7 @@ class SmartMimeMessage extends MimeMessage {
/**
* Return the default encoding of this message, or {@code null} if none.
*/
@Nullable
public final String getDefaultEncoding() {
return this.defaultEncoding;
}
@@ -65,6 +68,7 @@ class SmartMimeMessage extends MimeMessage {
/**
* Return the default FileTypeMap of this message, or {@code null} if none.
*/
@Nullable
public final FileTypeMap getDefaultFileTypeMap() {
return this.defaultFileTypeMap;
}

View File

@@ -3,4 +3,7 @@
* Provides an extended JavaMailSender interface and a MimeMessageHelper
* class for convenient population of a JavaMail MimeMessage.
*/
@NonNullApi
package org.springframework.mail.javamail;
import org.springframework.lang.NonNullApi;

View File

@@ -2,4 +2,7 @@
* Spring's generic mail infrastructure.
* Concrete implementations are provided in the subpackages.
*/
@NonNullApi
package org.springframework.mail;
import org.springframework.lang.NonNullApi;

View File

@@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit;
import commonj.timers.Timer;
import commonj.timers.TimerListener;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
@@ -164,6 +165,7 @@ public class TimerManagerTaskScheduler extends TimerManagerAccessor implements T
this.trigger = trigger;
}
@Nullable
public ScheduledFuture<?> schedule() {
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) {

View File

@@ -2,4 +2,7 @@
* Convenience classes for scheduling based on the CommonJ WorkManager/TimerManager
* facility, as supported by IBM WebSphere 6.0+ and BEA WebLogic 9.0+.
*/
@NonNullApi
package org.springframework.scheduling.commonj;
import org.springframework.lang.NonNullApi;

View File

@@ -27,6 +27,7 @@ import org.quartz.spi.ClassLoadHelper;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;
/**
* Wrapper that adapts from the Quartz {@link ClassLoadHelper} interface
@@ -82,6 +83,7 @@ public class ResourceLoaderClassLoadHelper implements ClassLoadHelper {
}
@Override
@Nullable
public URL getResource(String name) {
Resource resource = this.resourceLoader.getResource(name);
if (resource.exists()) {
@@ -101,6 +103,7 @@ public class ResourceLoaderClassLoadHelper implements ClassLoadHelper {
}
@Override
@Nullable
public InputStream getResourceAsStream(String name) {
Resource resource = this.resourceLoader.getResource(name);
if (resource.exists()) {

View File

@@ -22,6 +22,7 @@ import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.lang.Nullable;
/**
* Subclass of {@link AdaptableJobFactory} that also supports Spring-style
@@ -55,7 +56,7 @@ public class SpringBeanJobFactory extends AdaptableJobFactory implements Schedul
* ignored if there is no corresponding property found on the particular
* job class (all other unknown properties will still trigger an exception).
*/
public void setIgnoredUnknownProperties(String... ignoredUnknownProperties) {
public void setIgnoredUnknownProperties(@Nullable String... ignoredUnknownProperties) {
this.ignoredUnknownProperties = ignoredUnknownProperties;
}

View File

@@ -5,4 +5,7 @@
* Triggers as beans in a Spring context. Also provides
* convenience classes for implementing Quartz Jobs.
*/
@NonNullApi
package org.springframework.scheduling.quartz;
import org.springframework.lang.NonNullApi;

View File

@@ -38,6 +38,7 @@ import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
@@ -380,6 +381,7 @@ public class FreeMarkerConfigurationFactory {
* @param templateLoaders the final List of TemplateLoader instances
* @return the aggregate TemplateLoader
*/
@Nullable
protected TemplateLoader getAggregateTemplateLoader(List<TemplateLoader> templateLoaders) {
int loaderCount = templateLoaders.size();
switch (loaderCount) {

View File

@@ -3,4 +3,7 @@
* <a href="http://www.freemarker.org">FreeMarker</a>
* within a Spring application context.
*/
@NonNullApi
package org.springframework.ui.freemarker;
import org.springframework.lang.NonNullApi;