Skip to content

Commit

Permalink
Switch to JSpecify annotations
Browse files Browse the repository at this point in the history
This commit updates the whole Spring Framework codebase to use JSpecify
annotations instead of Spring null-safety annotations with JSR 305
semantics.

JSpecify provides signficant enhancements such as properly defined
specifications, a canonical dependency with no split-package issue,
better tooling, better Kotlin integration and the capability to specify
generic type, array and varargs element null-safety. Generic type
null-safety is not defined by this commit yet and will be specified
later.

A key difference is that Spring null-safety annotations, following
JSR 305 semantics, apply to fields, parameters and return values,
while JSpecify annotations apply to type usages. That's why this
commit moves nullability annotations closer to the type for fields
and return values.

See spring-projectsgh-28797
  • Loading branch information
sdeleuze committed Dec 19, 2024
1 parent fcb8aed commit 62daae0
Show file tree
Hide file tree
Showing 3,458 changed files with 14,116 additions and 22,058 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ class CustomCacheConfiguration {
// tag::snippet[]
@Bean
fun cacheManager(): CacheManager {
return CaffeineCacheManager().apply {
cacheNames = listOf("default", "books")
}
return CaffeineCacheManager("default", "books")
}
// end::snippet[]
}
1 change: 1 addition & 0 deletions framework-platform/framework-platform.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ dependencies {
api("org.htmlunit:htmlunit:4.6.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.9.0")
api("org.jspecify:jspecify:1.0.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
Expand Down
4 changes: 2 additions & 2 deletions gradle/spring-module.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ dependencies {
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
jmh 'org.openjdk.jmh:jmh-generator-bytecode:1.37'
jmh 'net.sf.jopt-simple:jopt-simple'
errorprone 'com.uber.nullaway:nullaway:0.10.26'
errorprone 'com.google.errorprone:error_prone_core:2.9.0'
errorprone 'com.uber.nullaway:nullaway:0.12.2'
errorprone 'com.google.errorprone:error_prone_core:2.35.1'
}

pluginManager.withPlugin("kotlin") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.List;

import jakarta.servlet.ServletException;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;

import org.springframework.aop.support.AopUtils;
Expand All @@ -31,7 +32,6 @@
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.lang.Nullable;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.transaction.testfixture.CallCountingTransactionManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;

/**
* After returning advice is invoked only on normal method return, not if an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;

/**
* Advice invoked before a method is invoked. Such advices cannot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
package org.springframework.aop;

import org.aopalliance.intercept.MethodInvocation;

import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;

/**
* Extension of the AOP Alliance {@link org.aopalliance.intercept.MethodInvocation}
Expand Down Expand Up @@ -83,7 +82,6 @@ public interface ProxyMethodInvocation extends MethodInvocation {
* @return the value of the attribute, or {@code null} if not set
* @see #setUserAttribute
*/
@Nullable
Object getUserAttribute(String key);
@Nullable Object getUserAttribute(String key);

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

package org.springframework.aop;

import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;

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

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

package org.springframework.aop;

import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;

/**
* A {@code TargetSource} is used to obtain the current "target" of
Expand All @@ -42,8 +42,7 @@ public interface TargetSource extends TargetClassAware {
* @return the type of targets returned by this {@link TargetSource}
*/
@Override
@Nullable
Class<?> getTargetClass();
@Nullable Class<?> getTargetClass();

/**
* Will all calls to {@link #getTarget()} return the same object?
Expand All @@ -64,8 +63,7 @@ default boolean isStatic() {
* or {@code null} if there is no actual target instance
* @throws Exception if the target object can't be resolved
*/
@Nullable
Object getTarget() throws Exception;
@Nullable Object getTarget() throws Exception;

/**
* Release the given target object obtained from the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.weaver.tools.JoinPointMatch;
import org.aspectj.weaver.tools.PointcutParameter;
import org.jspecify.annotations.Nullable;

import org.springframework.aop.AopInvocationException;
import org.springframework.aop.MethodMatcher;
Expand All @@ -42,7 +43,6 @@
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 @@ -118,16 +118,13 @@ public static JoinPoint currentJoinPoint() {
* This will be non-null if the creator of this advice object knows the argument names
* and sets them explicitly.
*/
@Nullable
private String[] argumentNames;
private @Nullable String @Nullable [] argumentNames;

/** Non-null if after throwing advice binds the thrown value. */
@Nullable
private String throwingName;
private @Nullable String throwingName;

/** Non-null if after returning advice binds the return value. */
@Nullable
private String returningName;
private @Nullable String returningName;

private Class<?> discoveredReturningType = Object.class;

Expand All @@ -145,13 +142,11 @@ public static JoinPoint currentJoinPoint() {
*/
private int joinPointStaticPartArgumentIndex = -1;

@Nullable
private Map<String, Integer> argumentBindings;
private @Nullable Map<String, Integer> argumentBindings;

private boolean argumentsIntrospected = false;

@Nullable
private Type discoveredReturningGenericType;
private @Nullable Type discoveredReturningGenericType;
// Note: Unlike return type, no such generic information is needed for the throwing type,
// since Java doesn't allow exception types to be parameterized.

Expand Down Expand Up @@ -212,8 +207,7 @@ public final AspectInstanceFactory getAspectInstanceFactory() {
/**
* Return the ClassLoader for aspect instances.
*/
@Nullable
public final ClassLoader getAspectClassLoader() {
public final @Nullable ClassLoader getAspectClassLoader() {
return this.aspectInstanceFactory.getAspectClassLoader();
}

Expand Down Expand Up @@ -318,8 +312,7 @@ protected Class<?> getDiscoveredReturningType() {
return this.discoveredReturningType;
}

@Nullable
protected Type getDiscoveredReturningGenericType() {
protected @Nullable Type getDiscoveredReturningGenericType() {
return this.discoveredReturningGenericType;
}

Expand Down Expand Up @@ -657,8 +650,7 @@ protected JoinPoint getJoinPoint() {
/**
* Get the current join point match at the join point we are being dispatched on.
*/
@Nullable
protected JoinPointMatch getJoinPointMatch() {
protected @Nullable JoinPointMatch getJoinPointMatch() {
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
if (!(mi instanceof ProxyMethodInvocation pmi)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
Expand All @@ -672,8 +664,7 @@ protected JoinPointMatch getJoinPointMatch() {
// 'last man wins' which is not what we want at all.
// Using the expression is guaranteed to be safe, since 2 identical expressions
// are guaranteed to bind in exactly the same way.
@Nullable
protected JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) {
protected @Nullable JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) {
String expression = this.pointcut.getExpression();
return (expression != null ? (JoinPointMatch) pmi.getUserAttribute(expression) : null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

package org.springframework.aop.aspectj;

import org.jspecify.annotations.Nullable;

import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;

/**
* Interface implemented to provide an instance of an AspectJ aspect.
Expand All @@ -44,7 +45,6 @@ public interface AspectInstanceFactory extends Ordered {
* @return the aspect class loader (or {@code null} for the bootstrap loader)
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
*/
@Nullable
ClassLoader getAspectClassLoader();
@Nullable ClassLoader getAspectClassLoader();

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.jspecify.annotations.Nullable;

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

/**
Expand Down Expand Up @@ -157,22 +157,19 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov


/** The pointcut expression associated with the advice, as a simple String. */
@Nullable
private final String pointcutExpression;
private final @Nullable String pointcutExpression;

private boolean raiseExceptions;

/** If the advice is afterReturning, and binds the return value, this is the parameter name used. */
@Nullable
private String returningName;
private @Nullable String returningName;

/** If the advice is afterThrowing, and binds the thrown value, this is the parameter name used. */
@Nullable
private String throwingName;
private @Nullable String throwingName;

private Class<?>[] argumentTypes = new Class<?>[0];

private String[] parameterNameBindings = new String[0];
private @Nullable String[] parameterNameBindings = new String[0];

private int numberOfRemainingUnboundArguments;

Expand Down Expand Up @@ -221,8 +218,7 @@ public void setThrowingName(@Nullable String throwingName) {
* @return the parameter names
*/
@Override
@Nullable
public String[] getParameterNames(Method method) {
public @Nullable String @Nullable [] getParameterNames(Method method) {
this.argumentTypes = method.getParameterTypes();
this.numberOfRemainingUnboundArguments = this.argumentTypes.length;
this.parameterNameBindings = new String[this.numberOfRemainingUnboundArguments];
Expand Down Expand Up @@ -289,8 +285,7 @@ public String[] getParameterNames(Method method) {
* {@link #setRaiseExceptions(boolean) raiseExceptions} has been set to {@code true}
*/
@Override
@Nullable
public String[] getParameterNames(Constructor<?> ctor) {
public String @Nullable [] getParameterNames(Constructor<?> ctor) {
if (this.raiseExceptions) {
throw new UnsupportedOperationException("An advice method can never be a constructor");
}
Expand Down Expand Up @@ -453,8 +448,7 @@ else if (numAnnotationSlots == 1) {
/**
* If the token starts meets Java identifier conventions, it's in.
*/
@Nullable
private String maybeExtractVariableName(@Nullable String candidateToken) {
private @Nullable String maybeExtractVariableName(@Nullable String candidateToken) {
if (AspectJProxyUtils.isVariableName(candidateToken)) {
return candidateToken;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jspecify.annotations.Nullable;

import org.springframework.aop.AfterAdvice;
import org.springframework.lang.Nullable;

/**
* Spring AOP advice wrapping an AspectJ after advice method.
Expand All @@ -43,8 +43,7 @@ public AspectJAfterAdvice(


@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
public @Nullable Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import org.jspecify.annotations.Nullable;

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
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jspecify.annotations.Nullable;

import org.springframework.aop.AfterAdvice;
import org.springframework.lang.Nullable;

/**
* Spring AOP advice wrapping an AspectJ after-throwing advice method.
Expand Down Expand Up @@ -58,8 +58,7 @@ public void setThrowingName(String name) {
}

@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
public @Nullable Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
package org.springframework.aop.aspectj;

import org.aopalliance.aop.Advice;
import org.jspecify.annotations.Nullable;

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 @@ -59,8 +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) {
public static @Nullable AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) {
if (anAdvisor instanceof AspectJPrecedenceInformation ajpi) {
return ajpi;
}
Expand Down
Loading

0 comments on commit 62daae0

Please sign in to comment.