Skip to content

Commit 744727b

Browse files
committed
AbstractAdvisingBeanPostProcessor uses target class check for existing proxy but checks against actual exposed object otherwise (catching introductions)
Issue: SPR-11725 (cherry picked from commit a0658c5)
1 parent 9fbb739 commit 744727b

File tree

2 files changed

+246
-25
lines changed

2 files changed

+246
-25
lines changed

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

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -91,9 +91,11 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
9191
// Ignore AOP infrastructure such as scoped proxies.
9292
return bean;
9393
}
94-
if (isEligible(bean, beanName)) {
95-
if (bean instanceof Advised) {
96-
Advised advised = (Advised) bean;
94+
95+
if (bean instanceof Advised) {
96+
Advised advised = (Advised) bean;
97+
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
98+
// Add our local Advisor to the existing proxy's Advisor chain...
9799
if (this.beforeExistingAdvisors) {
98100
advised.addAdvisor(0, this.advisor);
99101
}
@@ -102,32 +104,47 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
102104
}
103105
return bean;
104106
}
105-
else {
106-
ProxyFactory proxyFactory = new ProxyFactory(bean);
107-
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
108-
proxyFactory.copyFrom(this);
109-
proxyFactory.addAdvisor(this.advisor);
110-
return proxyFactory.getProxy(this.beanClassLoader);
111-
}
112107
}
113-
else {
114-
// No async proxy needed.
115-
return bean;
108+
109+
if (isEligible(bean, beanName)) {
110+
ProxyFactory proxyFactory = new ProxyFactory(bean);
111+
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
112+
proxyFactory.copyFrom(this);
113+
proxyFactory.addAdvisor(this.advisor);
114+
return proxyFactory.getProxy(this.beanClassLoader);
116115
}
116+
117+
// No async proxy needed.
118+
return bean;
117119
}
118120

119121
/**
120122
* Check whether the given bean is eligible for advising with this
121123
* post-processor's {@link Advisor}.
122-
* <p>Implements caching of {@code canApply} results per bean target class.
124+
* <p>Delegates to {@link #isEligible(Class)} for target class checking.
123125
* Can be overridden e.g. to specifically exclude certain beans by name.
126+
* <p>Note: Only called for regular bean instances but not for existing
127+
* proxy instances which implement {@link Advised} and allow for adding
128+
* the local {@link Advisor} to the existing proxy's {@link Advisor} chain.
129+
* For the latter, {@link #isEligible(Class)} is being called directly,
130+
* with the actual target class behind the existing proxy (as determined
131+
* by {@link AopUtils#getTargetClass(Object)}).
124132
* @param bean the bean instance
125133
* @param beanName the name of the bean
126-
* @see AopUtils#getTargetClass(Object)
127-
* @see AopUtils#canApply(Advisor, Class)
134+
* @see #isEligible(Class)
128135
*/
129136
protected boolean isEligible(Object bean, String beanName) {
130-
Class<?> targetClass = AopUtils.getTargetClass(bean);
137+
return isEligible(bean.getClass());
138+
}
139+
140+
/**
141+
* Check whether the given class is eligible for advising with this
142+
* post-processor's {@link Advisor}.
143+
* <p>Implements caching of {@code canApply} results per bean target class.
144+
* @param targetClass the class to check against
145+
* @see AopUtils#canApply(Advisor, Class)
146+
*/
147+
protected boolean isEligible(Class<?> targetClass) {
131148
Boolean eligible = this.eligibleBeans.get(targetClass);
132149
if (eligible != null) {
133150
return eligible;

spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java

Lines changed: 211 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@
1818

1919
import java.lang.annotation.Retention;
2020
import java.lang.annotation.RetentionPolicy;
21+
import java.util.HashMap;
2122
import java.util.concurrent.Future;
2223

23-
import org.junit.Before;
24+
import org.aopalliance.intercept.MethodInterceptor;
25+
import org.aopalliance.intercept.MethodInvocation;
2426
import org.junit.Test;
2527

28+
import org.springframework.aop.framework.ProxyFactory;
2629
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
30+
import org.springframework.aop.support.DefaultIntroductionAdvisor;
31+
import org.springframework.beans.factory.FactoryBean;
2732
import org.springframework.beans.factory.support.RootBeanDefinition;
2833
import org.springframework.context.ApplicationEvent;
2934
import org.springframework.context.ApplicationListener;
3035
import org.springframework.context.support.GenericApplicationContext;
3136
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
32-
import org.springframework.tests.Assume;
33-
import org.springframework.tests.TestGroup;
3437

3538
import static org.junit.Assert.*;
3639

@@ -46,10 +49,6 @@ public class AsyncExecutionTests {
4649

4750
private static int listenerConstructed = 0;
4851

49-
@Before
50-
public void setUp() {
51-
Assume.group(TestGroup.PERFORMANCE);
52-
}
5352

5453
@Test
5554
public void asyncMethods() throws Exception {
@@ -135,6 +134,46 @@ public void asyncClass() throws Exception {
135134
assertEquals("20", future.get());
136135
}
137136

137+
@Test
138+
public void asyncClassWithPostProcessor() throws Exception {
139+
originalThreadName = Thread.currentThread().getName();
140+
GenericApplicationContext context = new GenericApplicationContext();
141+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassBean.class));
142+
context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
143+
context.refresh();
144+
AsyncClassBean asyncTest = context.getBean("asyncTest", AsyncClassBean.class);
145+
asyncTest.doSomething(10);
146+
Future<String> future = asyncTest.returnSomething(20);
147+
assertEquals("20", future.get());
148+
}
149+
150+
@Test
151+
public void asyncClassWithInterface() throws Exception {
152+
originalThreadName = Thread.currentThread().getName();
153+
GenericApplicationContext context = new GenericApplicationContext();
154+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassBeanWithInterface.class));
155+
context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class));
156+
context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class));
157+
context.refresh();
158+
RegularInterface asyncTest = context.getBean("asyncTest", RegularInterface.class);
159+
asyncTest.doSomething(10);
160+
Future<String> future = asyncTest.returnSomething(20);
161+
assertEquals("20", future.get());
162+
}
163+
164+
@Test
165+
public void asyncClassWithInterfaceAndPostProcessor() throws Exception {
166+
originalThreadName = Thread.currentThread().getName();
167+
GenericApplicationContext context = new GenericApplicationContext();
168+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassBeanWithInterface.class));
169+
context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
170+
context.refresh();
171+
RegularInterface asyncTest = context.getBean("asyncTest", RegularInterface.class);
172+
asyncTest.doSomething(10);
173+
Future<String> future = asyncTest.returnSomething(20);
174+
assertEquals("20", future.get());
175+
}
176+
138177
@Test
139178
public void asyncInterface() throws Exception {
140179
originalThreadName = Thread.currentThread().getName();
@@ -149,6 +188,46 @@ public void asyncInterface() throws Exception {
149188
assertEquals("20", future.get());
150189
}
151190

191+
@Test
192+
public void asyncInterfaceWithPostProcessor() throws Exception {
193+
originalThreadName = Thread.currentThread().getName();
194+
GenericApplicationContext context = new GenericApplicationContext();
195+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncInterfaceBean.class));
196+
context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
197+
context.refresh();
198+
AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class);
199+
asyncTest.doSomething(10);
200+
Future<String> future = asyncTest.returnSomething(20);
201+
assertEquals("20", future.get());
202+
}
203+
204+
@Test
205+
public void dynamicAsyncInterface() throws Exception {
206+
originalThreadName = Thread.currentThread().getName();
207+
GenericApplicationContext context = new GenericApplicationContext();
208+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(DynamicAsyncInterfaceBean.class));
209+
context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class));
210+
context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class));
211+
context.refresh();
212+
AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class);
213+
asyncTest.doSomething(10);
214+
Future<String> future = asyncTest.returnSomething(20);
215+
assertEquals("20", future.get());
216+
}
217+
218+
@Test
219+
public void dynamicAsyncInterfaceWithPostProcessor() throws Exception {
220+
originalThreadName = Thread.currentThread().getName();
221+
GenericApplicationContext context = new GenericApplicationContext();
222+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(DynamicAsyncInterfaceBean.class));
223+
context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
224+
context.refresh();
225+
AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class);
226+
asyncTest.doSomething(10);
227+
Future<String> future = asyncTest.returnSomething(20);
228+
assertEquals("20", future.get());
229+
}
230+
152231
@Test
153232
public void asyncMethodsInInterface() throws Exception {
154233
originalThreadName = Thread.currentThread().getName();
@@ -164,6 +243,33 @@ public void asyncMethodsInInterface() throws Exception {
164243
assertEquals("20", future.get());
165244
}
166245

246+
@Test
247+
public void asyncMethodsInInterfaceWithPostProcessor() throws Exception {
248+
originalThreadName = Thread.currentThread().getName();
249+
GenericApplicationContext context = new GenericApplicationContext();
250+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncMethodsInterfaceBean.class));
251+
context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
252+
context.refresh();
253+
AsyncMethodsInterface asyncTest = context.getBean("asyncTest", AsyncMethodsInterface.class);
254+
asyncTest.doNothing(5);
255+
asyncTest.doSomething(10);
256+
Future<String> future = asyncTest.returnSomething(20);
257+
assertEquals("20", future.get());
258+
}
259+
260+
@Test
261+
public void dynamicAsyncMethodsInInterfaceWithPostProcessor() throws Exception {
262+
originalThreadName = Thread.currentThread().getName();
263+
GenericApplicationContext context = new GenericApplicationContext();
264+
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(DynamicAsyncMethodsInterfaceBean.class));
265+
context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
266+
context.refresh();
267+
AsyncMethodsInterface asyncTest = context.getBean("asyncTest", AsyncMethodsInterface.class);
268+
asyncTest.doSomething(10);
269+
Future<String> future = asyncTest.returnSomething(20);
270+
assertEquals("20", future.get());
271+
}
272+
167273
@Test
168274
public void asyncMethodListener() throws Exception {
169275
originalThreadName = Thread.currentThread().getName();
@@ -304,6 +410,28 @@ public Future<String> returnSomething(int i) {
304410
}
305411

306412

413+
public interface RegularInterface {
414+
415+
void doSomething(int i);
416+
417+
Future<String> returnSomething(int i);
418+
}
419+
420+
421+
@Async
422+
public static class AsyncClassBeanWithInterface implements RegularInterface {
423+
424+
public void doSomething(int i) {
425+
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
426+
}
427+
428+
public Future<String> returnSomething(int i) {
429+
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
430+
return new AsyncResult<String>(Integer.toString(i));
431+
}
432+
}
433+
434+
307435
@Async
308436
public interface AsyncInterface {
309437

@@ -328,6 +456,44 @@ public Future<String> returnSomething(int i) {
328456
}
329457

330458

459+
public static class DynamicAsyncInterfaceBean implements FactoryBean<AsyncInterface> {
460+
461+
private final AsyncInterface proxy;
462+
463+
public DynamicAsyncInterfaceBean() {
464+
ProxyFactory pf = new ProxyFactory(new HashMap<>());
465+
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(new MethodInterceptor() {
466+
@Override
467+
public Object invoke(MethodInvocation invocation) throws Throwable {
468+
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
469+
if (Future.class.equals(invocation.getMethod().getReturnType())) {
470+
return new AsyncResult<String>(invocation.getArguments()[0].toString());
471+
}
472+
return null;
473+
}
474+
});
475+
advisor.addInterface(AsyncInterface.class);
476+
pf.addAdvisor(advisor);
477+
this.proxy = (AsyncInterface) pf.getProxy();
478+
}
479+
480+
@Override
481+
public AsyncInterface getObject() {
482+
return this.proxy;
483+
}
484+
485+
@Override
486+
public Class<?> getObjectType() {
487+
return this.proxy.getClass();
488+
}
489+
490+
@Override
491+
public boolean isSingleton() {
492+
return true;
493+
}
494+
}
495+
496+
331497
public interface AsyncMethodsInterface {
332498

333499
void doNothing(int i);
@@ -360,6 +526,44 @@ public Future<String> returnSomething(int i) {
360526
}
361527

362528

529+
public static class DynamicAsyncMethodsInterfaceBean implements FactoryBean<AsyncMethodsInterface> {
530+
531+
private final AsyncMethodsInterface proxy;
532+
533+
public DynamicAsyncMethodsInterfaceBean() {
534+
ProxyFactory pf = new ProxyFactory(new HashMap<>());
535+
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(new MethodInterceptor() {
536+
@Override
537+
public Object invoke(MethodInvocation invocation) throws Throwable {
538+
assertTrue(!Thread.currentThread().getName().equals(originalThreadName));
539+
if (Future.class.equals(invocation.getMethod().getReturnType())) {
540+
return new AsyncResult<String>(invocation.getArguments()[0].toString());
541+
}
542+
return null;
543+
}
544+
});
545+
advisor.addInterface(AsyncMethodsInterface.class);
546+
pf.addAdvisor(advisor);
547+
this.proxy = (AsyncMethodsInterface) pf.getProxy();
548+
}
549+
550+
@Override
551+
public AsyncMethodsInterface getObject() {
552+
return this.proxy;
553+
}
554+
555+
@Override
556+
public Class<?> getObjectType() {
557+
return this.proxy.getClass();
558+
}
559+
560+
@Override
561+
public boolean isSingleton() {
562+
return true;
563+
}
564+
}
565+
566+
363567
public static class AsyncMethodListener implements ApplicationListener<ApplicationEvent> {
364568

365569
@Override

0 commit comments

Comments
 (0)