Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure CGLIB proxy is created for mixin with IntroductionInterceptor #31389

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

BinaryOracle
Copy link

Resolve the issue of creating mixin classes using IntroductionInterceptor resulting in dynamic proxies instead of CGLIB proxies ;

Corresponding issue address: #31304

…ptor resulting in dynamic proxies instead of CGLIB proxies
@pivotal-cla
Copy link

@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.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 9, 2023
@pivotal-cla
Copy link

@BinaryOracle Thank you for signing the Contributor License Agreement!

Copy link
Member

@sbrannen sbrannen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please introduce a test case that fails before the proposed changes and passes after the changes.

Thanks

@sbrannen sbrannen added type: bug A general bug in: core Issues in core modules (aop, beans, core, context, expression) status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 9, 2023
@sbrannen sbrannen changed the title Resolve the issue of creating mixin classes using IntroductionInterce… Ensure dynamic proxy is created instead of CGLIB proxy for mixin with IntroductionInterceptor Oct 9, 2023
@sbrannen sbrannen changed the title Ensure dynamic proxy is created instead of CGLIB proxy for mixin with IntroductionInterceptor Ensure CGLIB proxy is created instead of dynamic proxy for mixin with IntroductionInterceptor Oct 9, 2023
@sbrannen sbrannen changed the title Ensure CGLIB proxy is created instead of dynamic proxy for mixin with IntroductionInterceptor Ensure CGLIB proxy is created for mixin with IntroductionInterceptor Oct 9, 2023
@BinaryOracle
Copy link
Author

BinaryOracle commented Oct 9, 2023

@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

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 9, 2023
@sbrannen
Copy link
Member

sbrannen commented Oct 9, 2023

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.

@sbrannen sbrannen added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Oct 9, 2023
@BinaryOracle
Copy link
Author

@sbrannen

Sure, thank you. It's getting late today, but I will submit a more comprehensive set of test cases by tomorrow evening.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 9, 2023
@BinaryOracle
Copy link
Author

@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

@snicoll
Copy link
Member

snicoll commented Oct 14, 2023

@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.

@snicoll snicoll added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Oct 14, 2023
@BinaryOracle
Copy link
Author

@snicoll Okay, I'll go update it right away

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 14, 2023
Copy link
Member

@sbrannen sbrannen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for supplying the tests!

I've requested a few minor changes.

Comment on lines +78 to +83
private boolean hashNoInterfaceImplement(org.springframework.aop.framework.AdvisedSupport config) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) return false;
return targetClass.getInterfaces().length == 0;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private boolean hashNoInterfaceImplement(org.springframework.aop.framework.AdvisedSupport config) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) return false;
return targetClass.getInterfaces().length == 0;
}
private boolean targetDoesNotImplementInterfaces(AdvisedSupport config) {
Class<?> targetClass = config.getTargetClass();
return (targetClass != null ? targetClass.getInterfaces().length == 0 : false);
}

Let's revise this method like this.

Also, please add Javadoc for this method, similar to the Javadoc for hasNoUserSuppliedProxyInterfaces(...).

Comment on lines +11 to +12
* @author 占道宏
* @create 2023/10/10 9:59
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @author 占道宏
* @create 2023/10/10 9:59
* @author 占道宏
* @since 6.1

If possible, please provide your full name using the Latin alphabet for the @author tag.

@@ -0,0 +1,175 @@
package java.org.springframework.aop.framework;

import org.junit.jupiter.api.Assertions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use AssertJ for assertions.

The build will fail if you attempt to use JUnit Jupiter's Assertions class.

Speaking of which, please run a full build locally before submitting a PR (./gradlew check) to ensure that the build succeeds.

Assertions.assertDoesNotThrow(teacher1::teach);

Developer developer = (Developer) proxy;
Assertions.assertDoesNotThrow(developer::code);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of asserting that the code does not throw an exception, please change the void methods in your test fixture to return a String whose value can be asserted.

Assertions.assertTrue(AopUtils.isCglibProxy(proxy));
Assertions.assertTrue(proxy instanceof Teacher);
Assertions.assertTrue(proxy instanceof People);
Assertions.assertDoesNotThrow(((People)proxy)::eat);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

proxy = pf.getProxy();
Assertions.assertTrue(AopUtils.isJdkDynamicProxy(proxy));
Assertions.assertTrue(proxy instanceof Teacher);
Assertions.assertDoesNotThrow(((Teacher)proxy)::teach);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


public static class People {
void eat() {
System.out.println("eat");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not use System.out.println() in tests.

Please change the void methods to return the String instead.

@sbrannen sbrannen added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Oct 14, 2023
@snicoll snicoll added status: waiting-for-triage An issue we've not yet triaged or decided on and removed type: bug A general bug labels Oct 16, 2023
@jhoeller jhoeller added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Dec 29, 2023
@jhoeller jhoeller added this to the General Backlog milestone Dec 29, 2023
@jhoeller jhoeller modified the milestones: General Backlog, 6.x Backlog Jan 11, 2024
@snicoll snicoll removed the status: waiting-for-feedback We need additional information before we can continue label Feb 16, 2024
@sbrannen sbrannen modified the milestones: 6.x Backlog, 6.2.x Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants