目录
为什么Spring使用CGLIB动态代理调用同类方法时不会重复增强?
JDK动态代理
基于InvocationHandler接口实现,demo如下
public class JdkProxy implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理增强...");
Object result = method.invoke(target, args);
return result;
}
public Object createProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
JdkProxy proxyFactory = new JdkProxy();
proxyFactory.setTarget(userService);
IUserService proxy = (IUserService) proxyFactory.createProxy();
proxy.sayHello();
}
}
被代理的接口
public interface IUserService {
void sayHello();
void otherMethod();
}
被代理的默认实现,sayHello内部会调用otherMethod方法
@Component
public class UserServiceImpl implements IUserService{
@Value("${user.name}")
private String username;
private Integer age;
@Log
@Override
public void sayHello(){
System.out.println("hello:"+username);
otherMethod();
}
@Override
public void otherMethod() {
System.out.println("内部调用的其他方法");
}
public String getUsername() {
return username;
}
public Integer getAge() {
return age;
}
}
输出结果
因为JDK动态代理基于反射实现,所以并不会重复增强
JDK代理增强...
hello:null
内部调用的其他方法
Process finished with exit code 0
CGLIB动态代理
CGLIB
是基于FastClass来实现的,动态生成一个新的类,继承FastClass,
向类中写入委托类实例直接调用方法的语句。
动态类为委托类方法调用语句建立索引,使用者根据方法签名(方法名+参数类型)得到索引值,再通过索引值进入相应的方法调用语句,得到调用结果。所以在方法内部调用方法时,会重新获取索引,使用代理类调用方法,所以会重复增强
public class CglibProxy implements MethodInterceptor{
public Object createProxy(Class<?> clazz){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理增强");
return methodProxy.invokeSuper(o, objects);
}
public static void main(String[] args) {
CglibProxy proxyFactory = new CglibProxy();
IUserService userService = (IUserService) proxyFactory.createProxy(UserServiceImpl.class);
userService.sayHello();
}
}
执行结果
cglib代理增强
hello:null
cglib代理增强
内部调用的其他方法
Process finished with exit code 0
为什么Spring使用CGLIB动态代理调用同类方法时不会重复增强?
因为spring使用的回调是DynamicAdvisedInterceptor,核心代码如下:如果切点命中,拦截器链非空,将不会调用methodProxy的Invoke方法,而是调用CglibMethodInvocation的proceed方法
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
proceed方法 在调用了各种增强通知之后,最终也是反射调用被代理对象,所以不会重复增强。
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}