public class TestMain {
public static void main(String[] args) {
// 1. 准备被代理的目标对象
People peo = new People();
// 2. 准备代理工厂
ProxyFactory pf = new ProxyFactory();
// 3. 准备introduction advice,advice 持有需要额外添加的接口Developer和Developer接口的实现类
DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor((Developer) () -> System.out.println("编码"));
// 4. 添加advice和代理对象需要继承的接口
pf.addAdvice(dii);
// 5. 设置被代理对象
pf.setTarget(peo);
// 6. 这里强制类型转换会失败,因为代理对象采用JDK进行动态代理,只实现了Developer接口和Spring AOP内部接口
// 这里按理应该采用Cglib代理才对 !!!
peo = (People) pf.getProxy();
peo.drink();
peo.eat();
// 7. 强制转换为Developer接口,实际方法调用会被introduction advice拦截,调用请求转发给了advice内部持有的Developer接口实现类
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();
}
}
运行结果:
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:20)
这里原本是期望代理对象能够采用Cglib进行代理的,因为目标对象没有实现任何接口,但是却因为ProxyFactory特殊处理了类型为IntroductionAdvisor的切面,将IntroductionAdvisor提供的接口都加入到了AdvisedSupport的interfaces接口集合中;导致DefaultAopProxyFactory最终执行代理时,选择采用jdk而非cglib。
所以我们得到的代理对象实际采用jdk实现动态代理,实现了Spring AOP模块内部相关接口和Developer接口,当我们强制将代理对象转换为People类型时,会抛出类型转换异常。
Spring AOP 模块版本为: 5.3.9
原因:
AdvisedSupport 在添加advice的时候会特殊处理IntroductionInfo类型的Advice , 将其额外实现的接口添加到interfaces接口集合中去 :
@Override
public void addAdvice(Advice advice) throws AopConfigException {
int pos = this.advisors.size();
addAdvice(pos, advice);
}
@Override
public void addAdvice(int pos, Advice advice) throws AopConfigException {
Assert.notNull(advice, "Advice must not be null");
if (advice instanceof IntroductionInfo) {
// We don't need an IntroductionAdvisor for this kind of introduction:
// It's fully self-describing.
addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
}
...
}
@Override
public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
if (advisor instanceof IntroductionAdvisor) {
validateIntroductionAdvisor((IntroductionAdvisor) advisor);
}
addAdvisorInternal(pos, advisor);
}
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
advisor.validateInterfaces();
// If the advisor passed validation, we can make the change.
Class<?>[] ifcs = advisor.getInterfaces();
for (Class<?> ifc : ifcs) {
addInterface(ifc);
}
}
此时即便目标对象没有实现接口,interfaces集合也不会为空:
private List<Class<?>> interfaces = new ArrayList<>();
这会导致DefaultAopProxyFactory选择是采用jdk还是cglib进行动态代理时,错误的选择JDK而非cglib进行动态代理,因此最终得到的代理对象不能够强制转换为目标对象类型,这与我们预期目标不符合:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || 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.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
// interfaces集合此时不为空,所以会采用jdk进行动态代理
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
我不确定这边是否算是一个bug , 如果可以的话, 我更期望这边能够单独处理一下IntroductionAdvisor额外提供的接口列表,避免在目标对象没有实现接口的前提下,还是选择采用JDK动态代理。
笔者目前不太确定这是否算做一个bug,目前已将该问题反馈给Spring官方团队,Issue链接如下:
关于IntroductionAdvisor的用法,可以参考我之前写的这篇文章进行学习:
2023-09-26 Spring官方回复
简而言之就是确实存在这个bug,但是目前只能临时性强制采用cglib动态代理解决,后期会改进。
各位小伙伴使用IntroductionAdvisor的时候可以注意一下,不要踩了这个坑。