Skip to content

Commit 87598f4

Browse files
committed
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
1 parent 2d37c96 commit 87598f4

File tree

1,315 files changed

+4831
-963
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,315 files changed

+4831
-963
lines changed

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ configure(allprojects) { project ->
177177
}
178178

179179
dependencies {
180+
provided("com.google.code.findbugs:jsr305:3.0.2")
180181
testCompile("junit:junit:${junitVersion}") {
181182
exclude group:'org.hamcrest', module:'hamcrest-core'
182183
}

spring-aop/src/main/java/org/aopalliance/aop/AspectException.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.aopalliance.aop;
1818

19+
import org.springframework.lang.Nullable;
20+
1921
/**
2022
* Superclass for all AOP infrastructure exceptions.
2123
* Unchecked, as such exceptions are fatal and end user
@@ -41,7 +43,7 @@ public AspectException(String message) {
4143
* @param message the exception message
4244
* @param cause the root cause, if any
4345
*/
44-
public AspectException(String message, Throwable cause) {
46+
public AspectException(String message, @Nullable Throwable cause) {
4547
super(message, cause);
4648
}
4749

spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.aopalliance.intercept;
1818

19+
import org.springframework.lang.Nullable;
20+
1921
/**
2022
* Intercepts calls on an interface on its way to the target. These
2123
* are nested "on top" of the target.
@@ -52,6 +54,7 @@ public interface MethodInterceptor extends Interceptor {
5254
* @throws Throwable if the interceptors or the target object
5355
* throws an exception
5456
*/
57+
@Nullable
5558
Object invoke(MethodInvocation invocation) throws Throwable;
5659

5760
}

spring-aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.lang.reflect.Method;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* After returning advice is invoked only on normal method return, not if an
2325
* exception is thrown. Such advice can see the return value, but cannot change it.
@@ -39,6 +41,6 @@ public interface AfterReturningAdvice extends AfterAdvice {
3941
* allowed by the method signature. Otherwise the exception
4042
* will be wrapped as a runtime exception.
4143
*/
42-
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
44+
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
4345

4446
}

spring-aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.lang.reflect.Method;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* A specialized type of {@link MethodMatcher} that takes into account introductions
2325
* when matching methods. If there are no introductions on the target class,
@@ -39,6 +41,6 @@ public interface IntroductionAwareMethodMatcher extends MethodMatcher {
3941
* asking is the subject on one or more introductions; {@code false} otherwise
4042
* @return whether or not this method matches statically
4143
*/
42-
boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions);
44+
boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions);
4345

4446
}

spring-aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.lang.reflect.Method;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* Advice invoked before a method is invoked. Such advices cannot
2325
* prevent the method call proceeding, unless they throw a Throwable.
@@ -39,6 +41,6 @@ public interface MethodBeforeAdvice extends BeforeAdvice {
3941
* allowed by the method signature. Otherwise the exception
4042
* will be wrapped as a runtime exception.
4143
*/
42-
void before(Method method, Object[] args, Object target) throws Throwable;
44+
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
4345

4446
}

spring-aop/src/main/java/org/springframework/aop/MethodMatcher.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.lang.reflect.Method;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* Part of a {@link Pointcut}: Checks whether the target method is eligible for advice.
2325
*
@@ -57,7 +59,7 @@ public interface MethodMatcher {
5759
* the candidate class must be taken to be the method's declaring class)
5860
* @return whether or not this method matches statically
5961
*/
60-
boolean matches(Method method, Class<?> targetClass);
62+
boolean matches(Method method, @Nullable Class<?> targetClass);
6163

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

9193

9294
/**

spring-aop/src/main/java/org/springframework/aop/ProxyMethodInvocation.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import org.aopalliance.intercept.MethodInvocation;
2020

21+
import org.springframework.lang.Nullable;
22+
2123
/**
2224
* Extension of the AOP Alliance {@link org.aopalliance.intercept.MethodInvocation}
2325
* interface, allowing access to the proxy that the method invocation was made through.
@@ -73,14 +75,15 @@ public interface ProxyMethodInvocation extends MethodInvocation {
7375
* @param key the name of the attribute
7476
* @param value the value of the attribute, or {@code null} to reset it
7577
*/
76-
void setUserAttribute(String key, Object value);
78+
void setUserAttribute(String key, @Nullable Object value);
7779

7880
/**
7981
* Return the value of the specified user attribute.
8082
* @param key the name of the attribute
8183
* @return the value of the attribute, or {@code null} if not set
8284
* @see #setUserAttribute
8385
*/
86+
@Nullable
8487
Object getUserAttribute(String key);
8588

8689
}

spring-aop/src/main/java/org/springframework/aop/TargetClassAware.java

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.aop;
1818

19+
import org.springframework.lang.Nullable;
20+
1921
/**
2022
* Minimal interface for exposing the target class behind a proxy.
2123
*
@@ -34,6 +36,7 @@ public interface TargetClassAware {
3436
* (typically a proxy configuration or an actual proxy).
3537
* @return the target Class, or {@code null} if not known
3638
*/
39+
@Nullable
3740
Class<?> getTargetClass();
3841

3942
}

spring-aop/src/main/java/org/springframework/aop/TargetSource.java

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.aop;
1818

19+
import org.springframework.lang.Nullable;
20+
1921
/**
2022
* A {@code TargetSource} is used to obtain the current "target" of
2123
* an AOP invocation, which will be invoked via reflection if no around
@@ -58,6 +60,7 @@ public interface TargetSource extends TargetClassAware {
5860
* @return the target object, which contains the joinpoint
5961
* @throws Exception if the target object can't be resolved
6062
*/
63+
@Nullable
6164
Object getTarget() throws Exception;
6265

6366
/**

spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.aop.support.StaticMethodMatcher;
4343
import org.springframework.core.DefaultParameterNameDiscoverer;
4444
import org.springframework.core.ParameterNameDiscoverer;
45+
import org.springframework.lang.Nullable;
4546
import org.springframework.util.Assert;
4647
import org.springframework.util.ClassUtils;
4748
import org.springframework.util.CollectionUtils;
@@ -548,7 +549,7 @@ private void configurePointcutParameters(int argumentIndexOffset) {
548549
* @param ex the exception thrown by the method execution (may be null)
549550
* @return the empty array if there are no arguments
550551
*/
551-
protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable ex) {
552+
protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex) {
552553
calculateArgumentBindings();
553554

554555
// AMC start
@@ -607,7 +608,7 @@ else if (this.joinPointStaticPartArgumentIndex != -1) {
607608
* @return the invocation result
608609
* @throws Throwable in case of invocation failure
609610
*/
610-
protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
611+
protected Object invokeAdviceMethod(JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex) throws Throwable {
611612
return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
612613
}
613614

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.aspectj.weaver.tools.PointcutPrimitive;
3131

3232
import org.springframework.core.ParameterNameDiscoverer;
33+
import org.springframework.lang.Nullable;
3334
import org.springframework.util.StringUtils;
3435

3536
/**
@@ -473,6 +474,7 @@ else if (numAnnotationSlots == 1) {
473474
/*
474475
* If the token starts meets Java identifier conventions, it's in.
475476
*/
477+
@Nullable
476478
private String maybeExtractVariableName(String candidateToken) {
477479
if (candidateToken == null || candidateToken.equals("")) {
478480
return null;

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.springframework.aop.AfterAdvice;
2424
import org.springframework.aop.AfterReturningAdvice;
25+
import org.springframework.lang.Nullable;
2526
import org.springframework.util.ClassUtils;
2627
import org.springframework.util.TypeUtils;
2728

@@ -75,7 +76,7 @@ public void afterReturning(Object returnValue, Method method, Object[] args, Obj
7576
* @param returnValue the return value of the target method
7677
* @return whether to invoke the advice method for the given return value
7778
*/
78-
private boolean shouldInvokeOnReturnValueOf(Method method, Object returnValue) {
79+
private boolean shouldInvokeOnReturnValueOf(Method method, @Nullable Object returnValue) {
7980
Class<?> type = getDiscoveredReturningType();
8081
Type genericType = getDiscoveredReturningGenericType();
8182
// If we aren't dealing with a raw type, check if generic parameters are assignable.
@@ -94,7 +95,7 @@ private boolean shouldInvokeOnReturnValueOf(Method method, Object returnValue) {
9495
* @param returnValue the return value of the target method
9596
* @return whether to invoke the advice method for the given return value and type
9697
*/
97-
private boolean matchesReturnValue(Class<?> type, Method method, Object returnValue) {
98+
private boolean matchesReturnValue(Class<?> type, Method method, @Nullable Object returnValue) {
9899
if (returnValue != null) {
99100
return ClassUtils.isAssignableValue(type, returnValue);
100101
}

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.aop.Advisor;
2222
import org.springframework.aop.AfterAdvice;
2323
import org.springframework.aop.BeforeAdvice;
24+
import org.springframework.lang.Nullable;
2425

2526
/**
2627
* Utility methods for dealing with AspectJ advisors.
@@ -58,6 +59,7 @@ public static boolean isAfterAdvice(Advisor anAdvisor) {
5859
* If neither the advisor nor the advice have precedence information, this method
5960
* will return {@code null}.
6061
*/
62+
@Nullable
6163
public static AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) {
6264
if (anAdvisor instanceof AspectJPrecedenceInformation) {
6365
return (AspectJPrecedenceInformation) anAdvisor;

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.springframework.beans.factory.FactoryBean;
5757
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
5858
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
59+
import org.springframework.lang.Nullable;
5960
import org.springframework.util.ClassUtils;
6061
import org.springframework.util.ObjectUtils;
6162
import org.springframework.util.StringUtils;
@@ -382,6 +383,7 @@ protected String getCurrentProxiedBeanName() {
382383
/**
383384
* Get a new pointcut expression based on a target class's loader rather than the default.
384385
*/
386+
@Nullable
385387
private PointcutExpression getFallbackPointcutExpression(Class<?> targetClass) {
386388
try {
387389
ClassLoader classLoader = targetClass.getClassLoader();

spring-aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.aop.ProxyMethodInvocation;
3030
import org.springframework.core.DefaultParameterNameDiscoverer;
3131
import org.springframework.core.ParameterNameDiscoverer;
32+
import org.springframework.lang.Nullable;
3233
import org.springframework.util.Assert;
3334

3435
/**
@@ -109,6 +110,7 @@ public Object getThis() {
109110
* Returns the Spring AOP target. May be {@code null} if there is no target.
110111
*/
111112
@Override
113+
@Nullable
112114
public Object getTarget() {
113115
return this.methodInvocation.getThis();
114116
}

spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.aop.framework.AopConfigException;
4242
import org.springframework.core.ParameterNameDiscoverer;
4343
import org.springframework.core.annotation.AnnotationUtils;
44+
import org.springframework.lang.Nullable;
4445
import org.springframework.util.StringUtils;
4546

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

142+
@Nullable
140143
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
141144
A result = AnnotationUtils.findAnnotation(method, toLookFor);
142145
if (result != null) {

spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorFactory.java

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.aop.Advisor;
2525
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
2626
import org.springframework.aop.framework.AopConfigException;
27+
import org.springframework.lang.Nullable;
2728

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

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

spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.aop.aspectj.annotation;
1818

1919
import org.springframework.aop.aspectj.AspectInstanceFactory;
20+
import org.springframework.lang.Nullable;
2021

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

4951
}

spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.core.annotation.AnnotationUtils;
5151
import org.springframework.core.convert.converter.Converter;
5252
import org.springframework.core.convert.converter.ConvertingComparator;
53+
import org.springframework.lang.Nullable;
5354
import org.springframework.util.ReflectionUtils;
5455
import org.springframework.util.StringUtils;
5556
import org.springframework.util.comparator.InstanceComparator;
@@ -113,7 +114,7 @@ public ReflectiveAspectJAdvisorFactory() {
113114
* @see AspectJExpressionPointcut#setBeanFactory
114115
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader()
115116
*/
116-
public ReflectiveAspectJAdvisorFactory(BeanFactory beanFactory) {
117+
public ReflectiveAspectJAdvisorFactory(@Nullable BeanFactory beanFactory) {
117118
this.beanFactory = beanFactory;
118119
}
119120

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

213+
@Nullable
211214
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
212215
AspectJAnnotation<?> aspectJAnnotation =
213216
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);

0 commit comments

Comments
 (0)