Skip to content

Commit

Permalink
Introduce null-safety of Spring Framework API
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sdeleuze committed May 27, 2017
1 parent 2d37c96 commit 87598f4
Show file tree
Hide file tree
Showing 1,315 changed files with 4,831 additions and 963 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ configure(allprojects) { project ->
}

dependencies {
provided("com.google.code.findbugs:jsr305:3.0.2")
testCompile("junit:junit:${junitVersion}") {
exclude group:'org.hamcrest', module:'hamcrest-core'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.aopalliance.aop;

import org.springframework.lang.Nullable;

/**
* Superclass for all AOP infrastructure exceptions.
* Unchecked, as such exceptions are fatal and end user
Expand All @@ -41,7 +43,7 @@ public AspectException(String message) {
* @param message the exception message
* @param cause the root cause, if any
*/
public AspectException(String message, Throwable cause) {
public AspectException(String message, @Nullable Throwable cause) {
super(message, cause);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.aopalliance.intercept;

import org.springframework.lang.Nullable;

/**
* Intercepts calls on an interface on its way to the target. These
* are nested "on top" of the target.
Expand Down Expand Up @@ -52,6 +54,7 @@ public interface MethodInterceptor extends Interceptor {
* @throws Throwable if the interceptors or the target object
* throws an exception
*/
@Nullable
Object invoke(MethodInvocation invocation) throws Throwable;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;

/**
* After returning advice is invoked only on normal method return, not if an
* exception is thrown. Such advice can see the return value, but cannot change it.
Expand All @@ -39,6 +41,6 @@ public interface AfterReturningAdvice extends AfterAdvice {
* allowed by the method signature. Otherwise the exception
* will be wrapped as a runtime exception.
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;

/**
* A specialized type of {@link MethodMatcher} that takes into account introductions
* when matching methods. If there are no introductions on the target class,
Expand All @@ -39,6 +41,6 @@ public interface IntroductionAwareMethodMatcher extends MethodMatcher {
* asking is the subject on one or more introductions; {@code false} otherwise
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions);
boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions);

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;

/**
* Advice invoked before a method is invoked. Such advices cannot
* prevent the method call proceeding, unless they throw a Throwable.
Expand All @@ -39,6 +41,6 @@ public interface MethodBeforeAdvice extends BeforeAdvice {
* allowed by the method signature. Otherwise the exception
* will be wrapped as a runtime exception.
*/
void before(Method method, Object[] args, Object target) throws Throwable;
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;

/**
* Part of a {@link Pointcut}: Checks whether the target method is eligible for advice.
*
Expand Down Expand Up @@ -57,7 +59,7 @@ public interface MethodMatcher {
* the candidate class must be taken to be the method's declaring class)
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class<?> targetClass);
boolean matches(Method method, @Nullable Class<?> targetClass);

/**
* Is this MethodMatcher dynamic, that is, must a final call be made on the
Expand Down Expand Up @@ -86,7 +88,7 @@ public interface MethodMatcher {
* @return whether there's a runtime match
* @see MethodMatcher#matches(Method, Class)
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import org.aopalliance.intercept.MethodInvocation;

import org.springframework.lang.Nullable;

/**
* Extension of the AOP Alliance {@link org.aopalliance.intercept.MethodInvocation}
* interface, allowing access to the proxy that the method invocation was made through.
Expand Down Expand Up @@ -73,14 +75,15 @@ public interface ProxyMethodInvocation extends MethodInvocation {
* @param key the name of the attribute
* @param value the value of the attribute, or {@code null} to reset it
*/
void setUserAttribute(String key, Object value);
void setUserAttribute(String key, @Nullable Object value);

/**
* Return the value of the specified user attribute.
* @param key the name of the attribute
* @return the value of the attribute, or {@code null} if not set
* @see #setUserAttribute
*/
@Nullable
Object getUserAttribute(String key);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.aop;

import org.springframework.lang.Nullable;

/**
* Minimal interface for exposing the target class behind a proxy.
*
Expand All @@ -34,6 +36,7 @@ public interface TargetClassAware {
* (typically a proxy configuration or an actual proxy).
* @return the target Class, or {@code null} if not known
*/
@Nullable
Class<?> getTargetClass();

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.aop;

import org.springframework.lang.Nullable;

/**
* A {@code TargetSource} is used to obtain the current "target" of
* an AOP invocation, which will be invoked via reflection if no around
Expand Down Expand Up @@ -58,6 +60,7 @@ public interface TargetSource extends TargetClassAware {
* @return the target object, which contains the joinpoint
* @throws Exception if the target object can't be resolved
*/
@Nullable
Object getTarget() throws Exception;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
Expand Down Expand Up @@ -548,7 +549,7 @@ private void configurePointcutParameters(int argumentIndexOffset) {
* @param ex the exception thrown by the method execution (may be null)
* @return the empty array if there are no arguments
*/
protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable ex) {
protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex) {
calculateArgumentBindings();

// AMC start
Expand Down Expand Up @@ -607,7 +608,7 @@ else if (this.joinPointStaticPartArgumentIndex != -1) {
* @return the invocation result
* @throws Throwable in case of invocation failure
*/
protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
protected Object invokeAdviceMethod(JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex) throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.aspectj.weaver.tools.PointcutPrimitive;

import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

/**
Expand Down Expand Up @@ -473,6 +474,7 @@ else if (numAnnotationSlots == 1) {
/*
* If the token starts meets Java identifier conventions, it's in.
*/
@Nullable
private String maybeExtractVariableName(String candidateToken) {
if (candidateToken == null || candidateToken.equals("")) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.TypeUtils;

Expand Down Expand Up @@ -75,7 +76,7 @@ public void afterReturning(Object returnValue, Method method, Object[] args, Obj
* @param returnValue the return value of the target method
* @return whether to invoke the advice method for the given return value
*/
private boolean shouldInvokeOnReturnValueOf(Method method, Object returnValue) {
private boolean shouldInvokeOnReturnValueOf(Method method, @Nullable Object returnValue) {
Class<?> type = getDiscoveredReturningType();
Type genericType = getDiscoveredReturningGenericType();
// If we aren't dealing with a raw type, check if generic parameters are assignable.
Expand All @@ -94,7 +95,7 @@ private boolean shouldInvokeOnReturnValueOf(Method method, Object returnValue) {
* @param returnValue the return value of the target method
* @return whether to invoke the advice method for the given return value and type
*/
private boolean matchesReturnValue(Class<?> type, Method method, Object returnValue) {
private boolean matchesReturnValue(Class<?> type, Method method, @Nullable Object returnValue) {
if (returnValue != null) {
return ClassUtils.isAssignableValue(type, returnValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.springframework.aop.Advisor;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.BeforeAdvice;
import org.springframework.lang.Nullable;

/**
* Utility methods for dealing with AspectJ advisors.
Expand Down Expand Up @@ -58,6 +59,7 @@ public static boolean isAfterAdvice(Advisor anAdvisor) {
* If neither the advisor nor the advice have precedence information, this method
* will return {@code null}.
*/
@Nullable
public static AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) {
if (anAdvisor instanceof AspectJPrecedenceInformation) {
return (AspectJPrecedenceInformation) anAdvisor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -382,6 +383,7 @@ protected String getCurrentProxiedBeanName() {
/**
* Get a new pointcut expression based on a target class's loader rather than the default.
*/
@Nullable
private PointcutExpression getFallbackPointcutExpression(Class<?> targetClass) {
try {
ClassLoader classLoader = targetClass.getClassLoader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
Expand Down Expand Up @@ -109,6 +110,7 @@ public Object getThis() {
* Returns the Spring AOP target. May be {@code null} if there is no target.
*/
@Override
@Nullable
public Object getTarget() {
return this.methodInvocation.getThis();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.springframework.aop.framework.AopConfigException;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

/**
Expand Down Expand Up @@ -125,6 +126,7 @@ public void validate(Class<?> aspectClass) throws AopConfigException {
* (there <i>should</i> only be one anyway...)
*/
@SuppressWarnings("unchecked")
@Nullable
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
Class<?>[] classesToLookFor = new Class<?>[] {
Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
Expand All @@ -137,6 +139,7 @@ protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method metho
return null;
}

@Nullable
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
A result = AnnotationUtils.findAnnotation(method, toLookFor);
if (result != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.lang.Nullable;

/**
* Interface for factories that can create Spring AOP Advisors from classes
Expand Down Expand Up @@ -79,6 +80,7 @@ public interface AspectJAdvisorFactory {
* or if it is a pointcut that will be used by other advice but will not
* create a Spring advice in its own right
*/
@Nullable
Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrder, String aspectName);

Expand All @@ -98,6 +100,7 @@ Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFact
* @see org.springframework.aop.aspectj.AspectJAfterReturningAdvice
* @see org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
*/
@Nullable
Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.aop.aspectj.annotation;

import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.lang.Nullable;

/**
* Subinterface of {@link org.springframework.aop.aspectj.AspectInstanceFactory}
Expand Down Expand Up @@ -44,6 +45,7 @@ public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactor
* @return the mutex object (may be {@code null} for no mutex to use)
* @since 4.3
*/
@Nullable
Object getAspectCreationMutex();

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConvertingComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.comparator.InstanceComparator;
Expand Down Expand Up @@ -113,7 +114,7 @@ public ReflectiveAspectJAdvisorFactory() {
* @see AspectJExpressionPointcut#setBeanFactory
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader()
*/
public ReflectiveAspectJAdvisorFactory(BeanFactory beanFactory) {
public ReflectiveAspectJAdvisorFactory(@Nullable BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

Expand Down Expand Up @@ -176,6 +177,7 @@ public void doWith(Method method) throws IllegalArgumentException {
* @param introductionField the field to introspect
* @return {@code null} if not an Advisor
*/
@Nullable
private Advisor getDeclareParentsAdvisor(Field introductionField) {
DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class);
if (declareParents == null) {
Expand Down Expand Up @@ -208,6 +210,7 @@ public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInsta
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

@Nullable
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
Expand Down
Loading

0 comments on commit 87598f4

Please sign in to comment.