Skip to content

Commit 47ec966

Browse files
committed
TargetSource.getTarget() is nullable again (for compatibility with MethodInvocation.getThis)
Issue: SPR-15651
1 parent 233c15b commit 47ec966

File tree

8 files changed

+67
-47
lines changed

8 files changed

+67
-47
lines changed

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

Lines changed: 6 additions & 2 deletions
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
@@ -54,14 +56,16 @@ public interface TargetSource extends TargetClassAware {
5456
/**
5557
* Return a target instance. Invoked immediately before the
5658
* AOP framework calls the "target" of an AOP method invocation.
57-
* @return the target object, which contains the joinpoint
59+
* @return the target object which contains the joinpoint,
60+
* or {@code null} if there is no actual target instance
5861
* @throws Exception if the target object can't be resolved
5962
*/
63+
@Nullable
6064
Object getTarget() throws Exception;
6165

6266
/**
6367
* Release the given target object obtained from the
64-
* {@link #getTarget()} method.
68+
* {@link #getTarget()} method, if any.
6569
* @param target object obtained from a call to {@link #getTarget()}
6670
* @throws Exception if the object can't be released
6771
*/

spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -326,12 +326,8 @@ private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
326326
// TODO: small memory optimization here (can skip creation for methods with no advice)
327327
for (int x = 0; x < methods.length; x++) {
328328
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
329-
Object target = this.advised.getTargetSource().getTarget();
330-
Class<?> targetClass = this.advised.getTargetClass();
331-
if (targetClass == null) {
332-
targetClass = target.getClass();
333-
}
334-
fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(chain, target, targetClass);
329+
fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
330+
chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
335331
this.fixedInterceptorMap.put(methods[x].toString(), x);
336332
}
337333

@@ -378,20 +374,22 @@ private static boolean implementsInterface(Method method, Set<Class<?>> ifcs) {
378374
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
379375
*/
380376
@Nullable
381-
private static Object processReturnType(Object proxy, Object target, Method method, @Nullable Object retVal) {
377+
private static Object processReturnType(
378+
Object proxy, @Nullable Object target, Method method, @Nullable Object returnValue) {
379+
382380
// Massage return value if necessary
383-
if (retVal != null && retVal == target &&
381+
if (returnValue != null && returnValue == target &&
384382
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
385383
// Special case: it returned "this". Note that we can't help
386384
// if the target sets a reference to itself in another returned object.
387-
retVal = proxy;
385+
returnValue = proxy;
388386
}
389387
Class<?> returnType = method.getReturnType();
390-
if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
388+
if (returnValue == null && returnType != Void.TYPE && returnType.isPrimitive()) {
391389
throw new AopInvocationException(
392390
"Null return value from advice does not match primitive return type for: " + method);
393391
}
394-
return retVal;
392+
return returnValue;
395393
}
396394

397395

@@ -413,7 +411,7 @@ private static class StaticUnadvisedInterceptor implements MethodInterceptor, Se
413411

414412
private final Object target;
415413

416-
public StaticUnadvisedInterceptor(Object target) {
414+
public StaticUnadvisedInterceptor(@Nullable Object target) {
417415
this.target = target;
418416
}
419417

@@ -434,7 +432,7 @@ private static class StaticUnadvisedExposedInterceptor implements MethodIntercep
434432

435433
private final Object target;
436434

437-
public StaticUnadvisedExposedInterceptor(Object target) {
435+
public StaticUnadvisedExposedInterceptor(@Nullable Object target) {
438436
this.target = target;
439437
}
440438

@@ -476,7 +474,9 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
476474
return processReturnType(proxy, target, method, retVal);
477475
}
478476
finally {
479-
this.targetSource.releaseTarget(target);
477+
if (target != null) {
478+
this.targetSource.releaseTarget(target);
479+
}
480480
}
481481
}
482482
}
@@ -505,7 +505,9 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
505505
}
506506
finally {
507507
AopContext.setCurrentProxy(oldProxy);
508-
this.targetSource.releaseTarget(target);
508+
if (target != null) {
509+
this.targetSource.releaseTarget(target);
510+
}
509511
}
510512
}
511513
}
@@ -612,7 +614,9 @@ private static class FixedChainStaticTargetInterceptor implements MethodIntercep
612614

613615
private final Class<?> targetClass;
614616

615-
public FixedChainStaticTargetInterceptor(List<Object> adviceChain, Object target, Class<?> targetClass) {
617+
public FixedChainStaticTargetInterceptor(
618+
List<Object> adviceChain, @Nullable Object target, @Nullable Class<?> targetClass) {
619+
616620
this.adviceChain = adviceChain;
617621
this.target = target;
618622
this.targetClass = targetClass;
@@ -649,15 +653,16 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
649653
Object oldProxy = null;
650654
boolean setProxyContext = false;
651655
Object target = null;
656+
TargetSource targetSource = this.advised.getTargetSource();
652657
try {
653658
if (this.advised.exposeProxy) {
654659
// Make invocation available if necessary.
655660
oldProxy = AopContext.setCurrentProxy(proxy);
656661
setProxyContext = true;
657662
}
658663
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
659-
target = getTarget();
660-
Class<?> targetClass = target.getClass();
664+
target = targetSource.getTarget();
665+
Class<?> targetClass = (target != null ? target.getClass() : null);
661666
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
662667
Object retVal;
663668
// Check whether we only have one InvokerInterceptor: that is,
@@ -678,8 +683,8 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
678683
return retVal;
679684
}
680685
finally {
681-
if (target != null) {
682-
releaseTarget(target);
686+
if (target != null && !targetSource.isStatic()) {
687+
targetSource.releaseTarget(target);
683688
}
684689
if (setProxyContext) {
685690
// Restore old proxy.
@@ -702,14 +707,6 @@ public boolean equals(Object other) {
702707
public int hashCode() {
703708
return this.advised.hashCode();
704709
}
705-
706-
protected Object getTarget() throws Exception {
707-
return this.advised.getTargetSource().getTarget();
708-
}
709-
710-
protected void releaseTarget(Object target) throws Exception {
711-
this.advised.getTargetSource().releaseTarget(target);
712-
}
713710
}
714711

715712

@@ -722,8 +719,9 @@ private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
722719

723720
private final boolean publicMethod;
724721

725-
public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,
726-
Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
722+
public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method,
723+
Object[] arguments, @Nullable Class<?> targetClass,
724+
List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
727725

728726
super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
729727
this.methodProxy = methodProxy;

spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
191191
// Get as late as possible to minimize the time we "own" the target,
192192
// in case it comes from a pool.
193193
target = targetSource.getTarget();
194-
Class<?> targetClass = target.getClass();
194+
Class<?> targetClass = (target != null ? target.getClass() : null);
195195

196196
// Get the interception chain for this method.
197197
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea
103103
* but would complicate the code. And it would work only for static pointcuts.
104104
*/
105105
protected ReflectiveMethodInvocation(
106-
Object proxy, Object target, Method method, Object[] arguments,
107-
Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
106+
Object proxy, @Nullable Object target, Method method, Object[] arguments,
107+
@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
108108

109109
this.proxy = proxy;
110110
this.target = target;
@@ -121,6 +121,7 @@ public final Object getProxy() {
121121
}
122122

123123
@Override
124+
@Nullable
124125
public final Object getThis() {
125126
return this.target;
126127
}

spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvi
329329
* @throws org.springframework.aop.AopInvocationException in case of a reflection error
330330
*/
331331
@Nullable
332-
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
332+
public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
333333
throws Throwable {
334334

335335
// Use reflection to invoke the method.

spring-aop/src/main/java/org/springframework/aop/target/EmptyTargetSource.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,6 @@
3232
*/
3333
public class EmptyTargetSource implements TargetSource, Serializable {
3434

35-
/**
36-
* The canonical (Singleton) instance of this {@link EmptyTargetSource}.
37-
*/
38-
public static final EmptyTargetSource INSTANCE = new EmptyTargetSource(null, true);
39-
40-
private static final Object EMPTY_TARGET = new Object();
41-
4235
/** use serialVersionUID from Spring 1.2 for interoperability */
4336
private static final long serialVersionUID = 3680494563553489691L;
4437

@@ -47,6 +40,12 @@ public class EmptyTargetSource implements TargetSource, Serializable {
4740
// Static factory methods
4841
//---------------------------------------------------------------------
4942

43+
/**
44+
* The canonical (Singleton) instance of this {@link EmptyTargetSource}.
45+
*/
46+
public static final EmptyTargetSource INSTANCE = new EmptyTargetSource(null, true);
47+
48+
5049
/**
5150
* Return an EmptyTargetSource for the given target Class.
5251
* @param targetClass the target Class (may be {@code null})
@@ -106,11 +105,11 @@ public boolean isStatic() {
106105
}
107106

108107
/**
109-
* Always returns {@code DUMMY_TARGET}.
108+
* Always returns {@code null}.
110109
*/
111110
@Override
112111
public Object getTarget() {
113-
return EMPTY_TARGET;
112+
return null;
114113
}
115114

116115
/**

spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
3636
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3737
import org.springframework.core.annotation.Order;
38+
import org.springframework.lang.Nullable;
3839
import org.springframework.tests.TimeStamped;
3940
import org.springframework.tests.aop.advice.CountingBeforeAdvice;
4041
import org.springframework.tests.aop.interceptor.NopInterceptor;
@@ -369,6 +370,16 @@ public void testTargetClassProxiesCanBeOrderedThroughAnnotations() {
369370
assertSame(proxy1, list.get(1));
370371
}
371372

373+
@Test
374+
public void testInterceptorWithoutJoinpoint() {
375+
final TestBean target = new TestBean("tb");
376+
ITestBean proxy = ProxyFactory.getProxy(ITestBean.class, (MethodInterceptor) invocation -> {
377+
assertNull(invocation.getThis());
378+
return invocation.getMethod().invoke(target, invocation.getArguments());
379+
});
380+
assertEquals("tb", proxy.getName());
381+
}
382+
372383

373384
@SuppressWarnings("serial")
374385
private static class TimestampIntroductionInterceptor extends DelegatingIntroductionInterceptor

spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* {@link org.springframework.aop.framework.AopProxyUtils AopProxyUtils}.
3030
*
3131
* @author Sam Brannen
32+
* @author Juergen Hoeller
3233
* @since 4.2
3334
* @see org.springframework.aop.support.AopUtils
3435
* @see org.springframework.aop.framework.AopProxyUtils
@@ -54,7 +55,10 @@ public static <T> T getTargetObject(Object candidate) {
5455
Assert.notNull(candidate, "Candidate must not be null");
5556
try {
5657
if (AopUtils.isAopProxy(candidate) && candidate instanceof Advised) {
57-
return (T) ((Advised) candidate).getTargetSource().getTarget();
58+
Object target = ((Advised) candidate).getTargetSource().getTarget();
59+
if (target != null) {
60+
return (T) target;
61+
}
5862
}
5963
}
6064
catch (Throwable ex) {
@@ -83,7 +87,10 @@ public static <T> T getUltimateTargetObject(Object candidate) {
8387
Assert.notNull(candidate, "Candidate must not be null");
8488
try {
8589
if (AopUtils.isAopProxy(candidate) && candidate instanceof Advised) {
86-
return (T) getUltimateTargetObject(((Advised) candidate).getTargetSource().getTarget());
90+
Object target = ((Advised) candidate).getTargetSource().getTarget();
91+
if (target != null) {
92+
return (T) getUltimateTargetObject(target);
93+
}
8794
}
8895
}
8996
catch (Throwable ex) {

0 commit comments

Comments
 (0)