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

@@ -19,6 +19,7 @@ package org.springframework.expression;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
* A constructor resolver attempts locate a constructor and returns a ConstructorExecutor
@@ -40,6 +41,7 @@ public interface ConstructorResolver {
* @param argumentTypes the arguments that the constructor must be able to handle
* @return a ConstructorExecutor that can invoke the constructor, or null if non found
*/
@Nullable
ConstructorExecutor resolve(EvaluationContext context, String typeName, List<TypeDescriptor> argumentTypes)
throws AccessException;

View File

@@ -19,6 +19,7 @@ package org.springframework.expression;
import java.util.List;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
* A method resolver attempts locate a method and returns a command executor that can be
@@ -39,6 +40,7 @@ public interface MethodResolver {
* @param argumentTypes the arguments that the constructor must be able to handle
* @return a MethodExecutor that can invoke the method, or null if the method cannot be found
*/
@Nullable
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
List<TypeDescriptor> argumentTypes) throws AccessException;

View File

@@ -17,6 +17,8 @@
package org.springframework.expression;
import org.springframework.lang.Nullable;
/**
* A property accessor is able to read from (and possibly write to) an object's properties.
* This interface places no restrictions, and so implementors are free to access properties
@@ -43,6 +45,7 @@ public interface PropertyAccessor {
* @return an array of classes that this resolver is suitable for
* (or {@code null} if a general resolver)
*/
@Nullable
Class<?>[] getSpecificTargetClasses();
/**

View File

@@ -17,6 +17,7 @@
package org.springframework.expression;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
* A type converter can convert values between different types encountered during
@@ -53,6 +54,7 @@ public interface TypeConverter {
* @return the converted value
* @throws EvaluationException if conversion failed or is not possible to begin with
*/
@Nullable
Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType);
}

View File

@@ -1,4 +1,7 @@
/**
* Common utility classes behind the <em>Spring Expression Language</em>.
*/
@NonNullApi
package org.springframework.expression.common;
import org.springframework.lang.NonNullApi;

View File

@@ -1,4 +1,7 @@
/**
* Core abstractions behind the <em>Spring Expression Language</em>.
*/
@NonNullApi
package org.springframework.expression;
import org.springframework.lang.NonNullApi;

View File

@@ -25,6 +25,7 @@ import java.util.Stack;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -148,6 +149,7 @@ public class CodeFlow implements Opcodes {
/**
* Return the descriptor for the item currently on top of the stack (in the current scope).
*/
@Nullable
public String lastDescriptor() {
if (this.compilationScopes.peek().isEmpty()) {
return null;
@@ -508,7 +510,8 @@ public class CodeFlow implements Opcodes {
* @return the type descriptor for the object
* (descriptor is "Ljava/lang/Object" for {@code null} value)
*/
public static String toDescriptorFromObject(Object value) {
@Nullable
public static String toDescriptorFromObject(@Nullable Object value) {
if (value == null) {
return "Ljava/lang/Object";
}
@@ -768,6 +771,7 @@ public class CodeFlow implements Opcodes {
* @param type the type (may be primitive) for which to determine the descriptor
* @return the descriptor
*/
@Nullable
public static String toDescriptor(Class<?> type) {
String name = type.getName();
if (type.isPrimitive()) {

View File

@@ -31,6 +31,7 @@ import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -204,6 +205,7 @@ public class ExpressionState {
this.variableScopes.peek().setVariable(name, value);
}
@Nullable
public Object lookupLocalVariable(String name) {
ensureVariableScopesInitialized();
int scopeNumber = this.variableScopes.size() - 1;

View File

@@ -18,6 +18,7 @@ package org.springframework.expression.spel;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.lang.Nullable;
/**
* Represents a node in the Ast for a parsed expression.
@@ -85,6 +86,7 @@ public interface SpelNode {
* @return the class of the object if it is not already a class object,
* or {@code null} if the object is {@code null}
*/
@Nullable
Class<?> getObjectClass(Object obj);
/**

View File

@@ -39,6 +39,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.support.ReflectiveConstructorExecutor;
import org.springframework.lang.Nullable;
/**
* Represents the invocation of a constructor. Either a constructor on a regular type or
@@ -178,6 +179,7 @@ public class ConstructorReference extends SpelNodeImpl {
* @return a reusable ConstructorExecutor that can be invoked to run the constructor or null
* @throws SpelEvaluationException if there is a problem locating the constructor
*/
@Nullable
private ConstructorExecutor findExecutorForConstructor(String typeName,
List<TypeDescriptor> argumentTypes, ExpressionState state)
throws SpelEvaluationException {

View File

@@ -38,6 +38,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.ReflectiveMethodExecutor;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
import org.springframework.lang.Nullable;
/**
* Expression language AST node that represents a method reference.
@@ -168,6 +169,7 @@ public class MethodReference extends SpelNodeImpl {
return Collections.unmodifiableList(descriptors);
}
@Nullable
private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value,
TypeDescriptor target, List<TypeDescriptor> argumentTypes) {

View File

@@ -23,6 +23,7 @@ import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;
@@ -61,6 +62,7 @@ public abstract class Operator extends SpelNodeImpl {
return this.children[0];
}
@Nullable
public SpelNodeImpl getRightOperand() {
return this.children[1];
}

View File

@@ -1,4 +1,7 @@
/**
* SpEL's abstract syntax tree.
*/
@NonNullApi
package org.springframework.expression.spel.ast;
import org.springframework.lang.NonNullApi;

View File

@@ -1,4 +1,7 @@
/**
* SpEL's central implementation package.
*/
@NonNullApi
package org.springframework.expression.spel;
import org.springframework.lang.NonNullApi;

View File

@@ -72,6 +72,7 @@ import org.springframework.expression.spel.ast.StringLiteral;
import org.springframework.expression.spel.ast.Ternary;
import org.springframework.expression.spel.ast.TypeReference;
import org.springframework.expression.spel.ast.VariableReference;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -374,6 +375,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
// nonDottedNode: indexer;
@Nullable
private SpelNodeImpl maybeEatNonDottedNode() {
if (peekToken(TokenKind.LSQUARE)) {
if (maybeEatIndexer()) {
@@ -392,6 +394,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// | lastSelection
// ))
// ;
@Nullable
private SpelNodeImpl eatDottedNode() {
Token t = nextToken(); // it was a '.' or a '?.'
boolean nullSafeNavigation = (t.kind == TokenKind.SAFE_NAVI);
@@ -434,6 +437,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
// methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
@Nullable
private SpelNodeImpl[] maybeEatMethodArgs() {
if (!peekToken(TokenKind.LPAREN)) {
return null;
@@ -497,6 +501,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// | lastSelection
// | indexer
// | constructor
@Nullable
private SpelNodeImpl eatStartNode() {
if (maybeEatLiteral()) {
return pop();
@@ -888,6 +893,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// relationalOperator
// : EQUAL | NOT_EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN
// | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES
@Nullable
private Token maybeEatRelationalOperator() {
Token t = peekToken();
if (t == null) {
@@ -988,6 +994,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
return this.tokenStreamPointer<this.tokenStream.size();
}
@Nullable
private Token nextToken() {
if (this.tokenStreamPointer >= this.tokenStreamLength) {
return null;
@@ -995,6 +1002,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
return this.tokenStream.get(this.tokenStreamPointer++);
}
@Nullable
private Token peekToken() {
if (this.tokenStreamPointer >= this.tokenStreamLength) {
return null;

View File

@@ -35,6 +35,7 @@ import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.CompiledExpression;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
@@ -97,6 +98,7 @@ public class SpelCompiler implements Opcodes {
* @return an instance of the class implementing the compiled expression, or null
* if compilation is not possible
*/
@Nullable
public CompiledExpression compile(SpelNodeImpl expression) {
if (expression.isCompilable()) {
if (logger.isDebugEnabled()) {
@@ -130,6 +132,7 @@ public class SpelCompiler implements Opcodes {
* @return the expression call, or {@code null} if the decision was to opt out of
* compilation during code generation
*/
@Nullable
private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
String clazzName = "spel/Ex" + getNextSuffix();

View File

@@ -1,4 +1,7 @@
/**
* SpEL's standard parser implementation.
*/
@NonNullApi
package org.springframework.expression.spel.standard;
import org.springframework.lang.NonNullApi;

View File

@@ -26,6 +26,7 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
@@ -50,6 +51,7 @@ public class ReflectionHelper {
* @return a MatchInfo object indicating what kind of match it was,
* or {@code null} if it was not a match
*/
@Nullable
static ArgumentsMatchInfo compareArguments(
List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) {
@@ -139,6 +141,7 @@ public class ReflectionHelper {
* @return a MatchInfo object indicating what kind of match it was,
* or {@code null} if it was not a match
*/
@Nullable
static ArgumentsMatchInfo compareArgumentsVarargs(
List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) {
@@ -257,8 +260,9 @@ public class ReflectionHelper {
* @return {@code true} if some kind of conversion occurred on an argument
* @throws EvaluationException if a problem occurs during conversion
*/
@Nullable
static boolean convertArguments(TypeConverter converter, Object[] arguments, Executable executable,
Integer varargsPosition) throws EvaluationException {
@Nullable Integer varargsPosition) throws EvaluationException {
boolean conversionOccurred = false;
if (varargsPosition == null) {

View File

@@ -25,6 +25,7 @@ import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.TypedValue;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
/**
@@ -75,6 +76,7 @@ public class ReflectiveMethodExecutor implements MethodExecutor {
return this.publicDeclaringClass;
}
@Nullable
private Class<?> discoverPublicClass(Method method, Class<?> clazz) {
if (Modifier.isPublic(clazz.getModifiers())) {
try {

View File

@@ -40,6 +40,7 @@ import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.CompilablePropertyAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -367,6 +368,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
"set", clazz, mustBeStatic, 1, ANY_TYPES);
}
@Nullable
private Method findMethodForProperty(String[] methodSuffixes, String prefix, Class<?> clazz,
boolean mustBeStatic, int numberOfParams, Set<Class<?>> requiredReturnTypes) {
@@ -427,6 +429,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
/**
* Find a field of a certain name on a specified class.
*/
@Nullable
protected Field findField(String name, Class<?> clazz, boolean mustBeStatic) {
Field[] fields = clazz.getFields();
for (Field field : fields) {

View File

@@ -1,4 +1,7 @@
/**
* SpEL's default implementations for various core abstractions.
*/
@NonNullApi
package org.springframework.expression.spel.support;
import org.springframework.lang.NonNullApi;