Resolve the issue of creating mixin classes using IntroductionInterceptor resulting in dynamic proxies instead of CGLIB proxies ;
Corresponding issue address: https://github.com/spring-projects/spring-framework/issues/31304
Comment From: pivotal-cla
@BinaryOracle Please sign the Contributor License Agreement!
Click here to manually synchronize the status of this Pull Request.
See the FAQ for frequently asked questions.
Comment From: pivotal-cla
@BinaryOracle Thank you for signing the Contributor License Agreement!
Comment From: BinaryOracle
@sbrannen The following is a previously failed test case :
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
public class TestMain {
public static void main(String[] args) {
People peo = new People();
ProxyFactory pf = new ProxyFactory();
DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor((Developer) () -> System.out.println("Coding"));
pf.addAdvice(dii);
pf.setTarget(peo);
peo = (People) pf.getProxy();
peo.drink();
peo.eat();
Developer developer = (Developer) peo;
developer.code();
}
public static class People {
void eat() {
System.out.println("eat");
}
void drink() {
System.out.println("drink");
}
}
public interface Developer {
void code();
}
}
The exception result thrown before the issue was resolved:
Exception in thread "main" java.lang.ClassCastException: class com.sun.proxy.$Proxy0 cannot be cast to class com.spring.TestMain$People (com.sun.proxy.$Proxy0 and com.spring.TestMain$People are in unnamed module of loader 'app')
at com.spring.TestMain.main(TestMain.java:13)
The modified implementation of the DefaultAopProxyFactory class results in the expected output:
drink
eat
Coding
Comment From: sbrannen
The following is a previously failed test case :
Please introduce an equivalent of that in our test suite in the form of a JUnit Jupiter based test.
If you need help determining where such a test should reside, let us know.
Comment From: BinaryOracle
@sbrannen
Sure, thank you. It's getting late today, but I will submit a more comprehensive set of test cases by tomorrow evening.
Comment From: BinaryOracle
@sbrannen
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DelegatePerTargetObjectIntroductionInterceptor;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
/**
* @author 占道宏
* @create 2023/10/10 9:59
*/
public class ProxyFactoryTests {
/**
* The target object does not implement any interfaces, and in this case, you want to use CGLIB for dynamic proxying.
*/
@Test
public void testDelegatingIntroductionInterceptorWithoutInterface() {
People peo = new People();
ProxyFactory pf = new ProxyFactory();
DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor((Developer) () -> System.out.println("Coding"));
pf.addAdvice(dii);
pf.setTarget(peo);
Object proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isCglibProxy(proxy));
Assertions.assertTrue(proxy instanceof People);
Assertions.assertTrue(proxy instanceof Developer);
People people = (People) proxy;
Assertions.assertDoesNotThrow(people::eat);
Developer developer = (Developer) proxy;
Assertions.assertDoesNotThrow(developer::code);
}
/**
* The target object implements the Teacher interface, and in this case, you want to use JDK for dynamic proxying
*/
@Test
public void testDelegatingIntroductionInterceptorWithInterface() {
Teacher teacher = () -> System.out.println("teach");
ProxyFactory pf = new ProxyFactory();
DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor((Developer) () -> System.out.println("Coding"));
pf.addAdvice(dii);
pf.addInterface(Teacher.class);
pf.setTarget(teacher);
Object proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isJdkDynamicProxy(proxy));
Assertions.assertTrue(proxy instanceof Teacher);
Assertions.assertTrue(proxy instanceof Developer);
Teacher teacher1 = (Teacher) proxy;
Assertions.assertDoesNotThrow(teacher1::teach);
Developer developer = (Developer) proxy;
Assertions.assertDoesNotThrow(developer::code);
}
/**
* The target object does not implement any interfaces, and in this case, you want to use CGLIB for dynamic proxying.
*/
@Test
public void testDelegatePerTargetObjectIntroductionInterceptorWithoutInterface() {
People peo = new People();
ProxyFactory pf = new ProxyFactory();
DelegatePerTargetObjectIntroductionInterceptor dii = new DelegatePerTargetObjectIntroductionInterceptor(DeveloperImpl.class, Developer.class);
pf.addAdvice(dii);
pf.setTarget(peo);
Object proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isCglibProxy(proxy));
Assertions.assertTrue(proxy instanceof People);
Assertions.assertTrue(proxy instanceof Developer);
People people = (People) proxy;
Assertions.assertDoesNotThrow(people::eat);
Developer developer = (Developer) proxy;
Assertions.assertDoesNotThrow(developer::code);
}
/**
* The target object implements the Teacher interface, and in this case, you want to use JDK for dynamic proxying
*/
@Test
public void testDelegatePerTargetObjectIntroductionInterceptorWithInterface() {
Teacher teacher = () -> System.out.println("teach");
ProxyFactory pf = new ProxyFactory();
DelegatePerTargetObjectIntroductionInterceptor dii = new DelegatePerTargetObjectIntroductionInterceptor(DeveloperImpl.class, Developer.class);
pf.addAdvice(dii);
pf.addInterface(Teacher.class);
pf.setTarget(teacher);
Object proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isJdkDynamicProxy(proxy));
Assertions.assertTrue(proxy instanceof Teacher);
Assertions.assertTrue(proxy instanceof Developer);
Teacher teacher1 = (Teacher) proxy;
Assertions.assertDoesNotThrow(teacher1::teach);
Developer developer = (Developer) proxy;
Assertions.assertDoesNotThrow(developer::code);
}
/**
* The target object does not implement any interfaces, so it is necessary to use CGLIB for proxying
*/
@Test
public void testProxyFactoryWithoutInterface() {
People people = new People();
ProxyFactory pf = new ProxyFactory();
pf.setTarget(people);
Object proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isCglibProxy(proxy));
Assertions.assertTrue(proxy instanceof People);
Assertions.assertDoesNotThrow(((People)proxy)::eat);
pf.addInterface(Teacher.class);
proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isCglibProxy(proxy));
Assertions.assertTrue(proxy instanceof Teacher);
Assertions.assertTrue(proxy instanceof People);
Assertions.assertDoesNotThrow(((People)proxy)::eat);
}
/**
* When the target object implements the Teacher interface
* but we have not explicitly called the addInterface method,
* we expect to use CGLIB; however, after calling it, we expect to use JDK
*/
@Test
public void testProxyFactoryWithInterface() {
Teacher teacher = () -> System.out.println("teach");
ProxyFactory pf = new ProxyFactory();
pf.setTarget(teacher);
Object proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isCglibProxy(proxy));
Assertions.assertTrue(proxy instanceof Teacher);
Assertions.assertDoesNotThrow(((Teacher)proxy)::teach);
pf.addInterface(Teacher.class);
proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isJdkDynamicProxy(proxy));
Assertions.assertTrue(proxy instanceof Teacher);
Assertions.assertDoesNotThrow(((Teacher)proxy)::teach);
}
public static class People {
void eat() {
System.out.println("eat");
}
}
public interface Teacher {
void teach();
}
public interface Developer {
void code();
}
public static class DeveloperImpl implements Developer {
@Override
public void code() {
System.out.println("Coding");
}
}
}
Before the issue was corrected, only three out of the six test cases could pass. The specific results were as follows:
success:
testDelegatingIntroductionInterceptorWithInterface
testDelegatePerTargetObjectIntroductionInterceptorWithInterface
testProxyFactoryWithInterface
failure:
testDelegatingIntroductionInterceptorWithoutInterface
testDelegatePerTargetObjectIntroductionInterceptorWithoutInterface
testProxyFactoryWithoutInterface
After the issue was corrected, all test cases passed successfully
Comment From: snicoll
@BinaryOracle the tests need to be added to the PR, not as a comment here. Can you please upgrade the PR? You can push more commits to your branch.
Comment From: BinaryOracle
@snicoll Okay, I'll go update it right away
Comment From: jhoeller
I'm addressing this in a slightly different way for 6.2, reopening #31304 for that purpose.