Spring Aop 动态代理(三)

问题:aop无法拦截lambda 表达式实现接口的方法,类实现接口重写的方法可以被拦截。

涉及两块内容:1、Aop 的原理。2、lambda表达式实现接口的原理

1、Aop 的原理

一、JDK代理

JDK代理代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
 
interface Echor {
    public void echo();
}
 
 
class EchorImpl implements Echor {
 
    @Override
    public void echo() {
        System.out.println("echo ~");
    }
}
 
class MethodInvoker<T> implements InvocationHandler {
 
    private T invoker;
 
    public MethodInvoker(T invoker) {
        this.invoker = invoker;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
        System.out.println("start ~");
        Object result = method.invoke(invoker, args);
        System.out.println("end ~");
        return result;
    }
}
 
public class DebugJdkProxy {
 
    public static void main(String[] args) {
        Echor proxy = (Echor) Proxy.newProxyInstance(DebugJdkProxy.class.getClassLoader(), new Class[]{Echor.class}, new MethodInvoker<Echor>(new EchorImpl()));
        proxy.echo();
    }
 
}
JVM实现代理类比较重要的类sun.misc.ProxyGenerator,生成代理类的方法为generateClassFile源码:

private byte[] generateClassFile() {
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        Class[] var1 = this.interfaces;
        int var2 = var1.length;
 
        int var3;
        Class var4;
        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
             //重点:代理那些方法?实例方法
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;
            
            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                this.addProxyMethod(var8, var4);
            }
        }
 
        Iterator var11 = this.proxyMethods.values().iterator();
 
        List var12;
        while(var11.hasNext()) {
            var12 = (List)var11.next();
            checkReturnTypes(var12);
        }
 
        Iterator var15;
        try {
            this.methods.add(this.generateConstructor());
            var11 = this.proxyMethods.values().iterator();
 
            while(var11.hasNext()) {
                var12 = (List)var11.next();
                var15 = var12.iterator();
 
                while(var15.hasNext()) {
                    ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                    this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                    this.methods.add(var16.generateMethod());
                }
            }
 
            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var10) {
            throw new InternalError("unexpected I/O Exception", var10);
        }
 
        if (this.methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        } else if (this.fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        } else {
            this.cp.getClass(dotToSlash(this.className));
            this.cp.getClass("java/lang/reflect/Proxy");
            var1 = this.interfaces;
            var2 = var1.length;
 
            for(var3 = 0; var3 < var2; ++var3) {
                var4 = var1[var3];
                this.cp.getClass(dotToSlash(var4.getName()));
            }
 
            this.cp.setReadOnly();
            ByteArrayOutputStream var13 = new ByteArrayOutputStream();
            DataOutputStream var14 = new DataOutputStream(var13);
 
            try {
                var14.writeInt(-889275714);
                var14.writeShort(0);
                var14.writeShort(49);
                this.cp.write(var14);
                var14.writeShort(this.accessFlags);
                var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
                var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                var14.writeShort(this.interfaces.length);
                Class[] var17 = this.interfaces;
                int var18 = var17.length;
 
                for(int var19 = 0; var19 < var18; ++var19) {
                    Class var22 = var17[var19];
                    var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
                }
 
                var14.writeShort(this.fields.size());
                var15 = this.fields.iterator();
 
                while(var15.hasNext()) {
                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                    var20.write(var14);
                }
 
                var14.writeShort(this.methods.size());
                var15 = this.methods.iterator();
 
                while(var15.hasNext()) {
                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                    var21.write(var14);
                }
 
                var14.writeShort(0);
                return var13.toByteArray();
            } catch (IOException var9) {
                throw new InternalError("unexpected I/O Exception", var9);
            }
        }
    }
 

具体实现原理:

1、通过实现InvocationHandlet接口创建自己的调用处理器

2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理

3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型

4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,

Spring通过java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

 

到此处,已经清楚JDK底层生成代理类时代理哪些方法,其中反射getMethods是可以获取到Class中所有public方法,包括静态方法。

由于JDK代理是基于接口的,而接口里面又不允许有静态方法,所以是无法代理静态方法的。换个角度:基于接口的Jdk代理与基于继承Class的代理本质都是基于继承之后重写指定方法实现的代理,而static方法是属于class的,而不是类实例的,无法被重写所以static方法无法代理。除此之外,JDK代理类是基于接口实现生成的,因此对于子类的final方法是可以代理的。

需要注意:Jdk8中的default方式是实例方法,而静态方法。

jdk动态代理原理:https://www.jianshu.com/p/a5e7f61db26d

二:CGLIB代理

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
interface Echor {
    public void echo();
 
    public static void hello() {
        System.out.println("hello world!");
    }
}
 
 
abstract class AbsEchor implements Echor {
 
    public static void abs() {
        System.out.println("abs~~");
    }
 
 
    public static void hello() {
        System.out.println("hello world!");
    }
}
 
class EchorImpl implements Echor {
 
    public static void hello2() {
        System.out.println("hello world!");
    }
 
    @Override
    public void echo() {
        System.out.println("echo ~");
    }
}
 
 
 
class EchorMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("start ~");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("end ~");
 
        return result;
 
    }
}
 
class DebugCGlibProxy {
 
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(AbsEchor.class);
        enhancer.setCallback(new EchorMethodInterceptor());
 
        AbsEchor hello = (AbsEchor) enhancer.create();
        hello.abs();
    }
 
}

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。


CGlib是基于继承重写实现的代理,因此要求Class必须是非final class 与此同时被代理的方法必须是非final方法,因此final方法无法被子类重写,因此就无法代理。

小结
基于JDK代理与基于CGLIB代理的代理类生成本质都是基于继承重写实现的(实现接口可以认为是一种特殊的继承);对于static成员方法是无法子类重写的,static是归属于class所属。

至此:由于Spring使用的是JDK与CGLIB这两种方式实现AOP,因此结论就是Spring无法支持static方法的代理增强。
 

参考:https://blog.csdn.net/Dax1n/article/details/105684685

spring aop 不能对静态方法进行增强解决(通过实例方法调用静态方法)

https://www.jianshu.com/p/ad741b33ce06

关于spring aop对接口方法上的注解无法拦截问题

https://blog.csdn.net/kedong1991/article/details/106198248           

https://blog.csdn.net/xybz1993/article/details/80627432

mapper 添加aop注解不生效分析原因:https://blog.csdn.net/gao199108/article/details/77720569

从报错信息可以了解说是代理了final修饰的类。可是哪里来的final类? 原来,DAO层使用的是mybatis,可以只写接口不用写实现类。而我们项目中就是没有写实现类。但是spring也可以对接口进行代理,继续分析。

springboot2.x默认使用的代理是cglib代理,springboot 中注解@EnableTransactionManagement 或者@EnableAspectJAutoProxy默认就为false,说明这里面的属性不起作用

https://blog.csdn.net/u011242657/article/details/99747011

2、lambda表达式实现接口的原理

参考:https://blog.csdn.net/jiankunking/article/details/79825928

示例代码:

public class LambdaTest {
    public static void printString(String s, Print<String> print) {
        print.print(s);
    }
    public static void main(String[] args) {
        printString("test", (x) -> System.out.println(x));
    }
}

@FunctionalInterface
interface Print<T> {
    public void print(T x);
}
————————————————
最终执行代码:

public class LambdaTest {
    public static void PrintString(String s, Print<String> print) {
        print.print(s);
    }

    public static void main(String[] args) {
        PrintString("test", new LambdaTest$$Lambda$1());
    }

    private static void lambda$main$0(String x) {
        System.out.println(x);
    }

    static final class LambdaTest$$Lambda$1 implements Print {
        public void print(Object obj) {
            LambdaTest.lambda$main$0((String) obj);
        }
        private LambdaTest$$Lambda$1() {
        }
    }

}

@FunctionalInterface
interface Print<T> {
    public void print(T x);
}

结论:

  1. 在类编译时,会生成一个私有静态方法+一个内部类;
  2. 在内部类中实现了函数式接口,在实现接口的方法中,会调用编译器生成的静态方法;
  3. 在使用lambda表达式的地方,通过传递内部类实例,来调用函数式接口方法。

 

结合一二可知,lambda实现接口无法被拦截 的原因。

其他:下面文章提到了解决lambda表达式的切入点的方法,在springboot没有成功

Aspectj:lambda表达式的切入点 https://www.jb51.cc/java/128970.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值