-
Notifications
You must be signed in to change notification settings - Fork 38.3k
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
Mixin created with IntroductionInterceptor
results in dynamic proxy instead of CGLIB proxy
#31304
Comments
As a workaround, you can invoke
I can certainly understand the desire for that. We'll have to investigate what options we have to improve that. In the interim, I've added this to the general backlog. |
IntroductionInterceptor
results in dynamic proxy instead of CGLIB proxy
I've been quite busy recently, so I apologize for not getting back to you sooner. Regarding this issue, I think the solution is relatively straightforward, but I'm not sure if there might be any problems with the solution I'm about to suggest. Here are some of my thoughts: When we invoke the setTarget method of the ProxyFactory and specify the target object to be proxied, it doesn't automatically check whether the target object implements an interface. In this case, it defaults to using CGLIB for dynamic proxy generation. Only if we manually call addInterface to add the interfaces implemented by the target object will it use JDK dynamic proxy. public class TestMain {
public static void main(String[] args) {
Stu s = () -> System.out.println("Student");
ProxyFactory pf = new ProxyFactory();
pf.setTarget(s);
// Call addInterface actively for ProxyFactory to use JDK dynamic proxy
pf.addInterface(Stu.class);
Stu stu = (Stu) pf.getProxy();
stu.study();
}
public interface Stu {
void study();
}
} In the scenario illustrated above, manually invoking the addInterface method to add an interface may not pose a significant issue. However, in the following scenario, there are potential problems: public class TestMain {
public static void main(String[] args) {
Stu s = new Stu();
ProxyFactory pf = new ProxyFactory();
pf.setTarget(s);
pf.addInterface(Boy.class);
Object proxy = pf.getProxy();
Stu stu = (Stu) proxy;
stu.study();
}
public static class Stu {
void study() {
System.out.println("study");
}
}
public interface Boy {
void boy();
}
} In this case, a type casting exception will be thrown because JDK dynamic proxy is ultimately used. The proxy object only implements Spring AOP internal interfaces in addition to the Boy interface. As a result, the proxy object cannot be cast to the Stu type. Exception in thread "main" java.lang.ClassCastException: class com.sun.proxy.$Proxy0 cannot be cast to class com.spring.TestMain$Stu (com.sun.proxy.$Proxy0 and com.spring.TestMain$Stu are in unnamed module of loader 'app')
at com.spring.TestMain.main(TestMain.java:11) I believe that a proxy object is meant to enhance the behavior of the target object and should be capable of being cast to either the type of the target object itself or to one of its inherited interface types. However, in this scenario, the proxy object cannot be cast to the type of the target object, which goes against its original purpose. The issue mentioned earlier, which arises due to the addition of extra interfaces through the addInterface method indirectly called by DelegatingIntroductionInterceptor, can be addressed by considering to check whether the target object implements any interfaces in the createAopProxy method of the DefaultAopProxyFactory class. If it doesn't implement any interfaces, then you can opt for CGLIB for dynamic proxy generation: public class CustomDefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() ||
// Modification: If the target object doesn't implement any interfaces, still use CGLIB for dynamic proxy
hasNoInterfaceImplement(config) || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// This ensures that if targetClass is an interface, JDK dynamic proxy is still used
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}
// New Logic !!!
private boolean hasNoInterfaceImplement(AdvisedSupport config) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) return false;
return targetClass.getInterfaces().length == 0;
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
} Replace the default implementation of DefaultAopProxyFactory with custom implementation, and then continue running the tests: public class TestMain {
public static void main(String[] args) {
Stu s = new Stu();
ProxyFactory pf = new ProxyFactory();
// Use a custom dynamic proxy creation factory
pf.setAopProxyFactory(new CustomDefaultAopProxyFactory());
pf.setTarget(s);
pf.addInterface(Boy.class);
Object proxy = pf.getProxy();
Stu stu = (Stu) proxy;
stu.study();
// Output the interfaces implemented by the current proxy object
for (Class<?> anInterface : proxy.getClass().getInterfaces()) {
System.out.println(anInterface);
}
}
public static class Stu {
void study() {
System.out.println("study");
}
}
public interface Boy {
void boy();
}
} The output result: study
interface com.spring.TestMain$Boy
interface org.springframework.aop.SpringProxy
interface org.springframework.aop.framework.Advised
interface org.springframework.cglib.proxy.Factory Execute the test for the problem scenario provided initially: public class TestMain {
public static void main(String[] args) {
People peo = new People();
ProxyFactory pf = new ProxyFactory();
// Initial test case, replace the default proxy creation factory implementation here
pf.setAopProxyFactory(new CustomDefaultAopProxyFactory());
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 output result: drink
eat
Coding |
Hi @BinaryOracle, Thanks for the feedback and proposal. If I understand you correctly, you are proposing that we introduce a method like your If so, feel free to open a PR that introduces this method along with a test case that fails before the addition and passes after the addition. Cheers, Sam p.s. whenever possible, please refrain from including screenshots in issues. If you would like to point to a specific section of the code, please include a GitHuB perma-link to the lines of the file in question. |
The result of running the code is:
The original expectation here was for the proxy object to be able to use Cglib for proxying because the target object did not implement any interfaces. However, this situation occurred due to a special handling of IntroductionAdvisor within the ProxyFactory. It added all the interfaces provided by IntroductionAdvisor to the AdvisedSupport's interface collection. Consequently, when DefaultAopProxyFactory eventually executed the proxying process, it chose to use JDK dynamic proxy instead of Cglib.
As a result, the proxy object we obtain is actually implemented using JDK dynamic proxy. It implements internal Spring AOP module-related interfaces and the Developer interface. When we forcefully attempt to cast the proxy object to the People type, a type casting exception is thrown.
The Spring AOP module version is: 5.3.9
Reason:
When AdvisedSupport adds advice, it specifically handles Advice of the IntroductionInfo type and adds the interfaces implemented by it to the interfaces collection.
At this point, even if the target object does not implement any interfaces, the interfaces collection will not be empty :
This results in DefaultAopProxyFactory making an incorrect choice between using JDK or CGLIB for dynamic proxying. It chooses JDK instead of CGLIB for dynamic proxying, even when the target object does not implement any interfaces. Consequently, the resulting proxy object cannot be cast to the target object type, which is not in line with our expected behavior.
I'm not sure if this can be considered a bug. If possible, I would prefer that in this case, the IntroductionAdvisor's additional interface list be handled separately to avoid choosing JDK dynamic proxying when the target object does not implement interfaces.
The text was updated successfully, but these errors were encountered: