spring源码学习 七:aop的代理技术

前言

我们知道spring aop是通过动态代理来实现的,而代理技术有两种,一种是JDK自带的代理实现,另一种是Cglib的扩展技术。

一、代理之JDK实现

jdk代理只能针对接口去实现,所以我们先写一个接口Foo,里边定义一个foo方法,同时写一个Target类去实现Foo接口:

public class JDKproxy {
    interface Foo{
        void foo();
    }

    static class Target implements Foo{
        @Override
        public void foo() {
            System.out.println("target里的foo方法");
        }
    }
}

接下来我们需要调用JDK的Proxy类里的newProxyInstance方法来创建代理,我们先看这个方法需要什么参数:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) {
  • ClassLoader loader

我们是没有写过代理类的源代码的,所以编译的时候也没有编译代理类,它的字节码是在运行期间生成的,因此需要一个classloader去做加载代理类字节码的工作。

  • Class<?>[] interfaces

传入接口的Class,即指定代理类去实现哪个接口(JDK代理基于接口)。

  • InvocationHandler h

指定代理类要做什么动作(核心)。

我们在main方法里调用此方法:

public static void main(String[] args) {
    ClassLoader classLoader = JDKproxy.class.getClassLoader();//用来加载在运行期间动态生成的字节码
    Foo proxy = (Foo)Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before 前置增强");
            return null;
        }
    });
    proxy.foo();
}

我们通过class.getClassLoader()拿到main方法所在类的classLoader

第二个参数传入Foo.class,注意这里是Class数组,代表可以实现多个接口

第三个参数我们通过匿名内部类实现InvocationHandler接口,invoke方法代表需要代理类执行的具体逻辑,这里我们就让它先打印一句话"before 前置增强",接着把newProxyInstance的返回值转为Foo,这里可以实现强转是因为代理类实现了Foo接口(参数传进去了),然后我们调用我们创建的代理的foo方法。

输出:

请添加图片描述

现在我们来看看invoke方法的参数

  • 第一个是Object proxy,从名字可以看出,这个是代理类自己
  • 第二个参数是Method method,它的作用是通过反射去执行被代理类的原始方法
  • 第三个参数Object[] args代表原始方法的参数

现在我们就手动去new一个Target类(实现Foo接口),充当要被代理的对象,在invoke方法里通过method去调用它的foo方法,完成代理增强。

我们再加一行代码,充当后置增强逻辑:

public static void main(String[] args) {
    //目标对象
    Target target = new Target();
    ClassLoader classLoader = JDKproxy.class.getClassLoader();//用来加载在运行期间动态生成的字节码
    Foo proxy = (Foo)Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before 前置增强");
            Object result = method.invoke(target, args);//调用目标方法
            System.out.println("after 后置增强");
            return result;//返回结果
        }
    });
    proxy.foo();
}

要注意的是我们的method.invoke是可以返回一个Object的你虽然我们的foo方法返回值是void,但是还是要把result当作InvocationHandler.invoke的返回值,因为需要留给有返回值的函数使用。

运行后输出:

请添加图片描述

我们可以知道,jdk代理生成的代理类与目标类之间是平级的关系,因为都实现的同一个接口,是兄弟关系,不可以相互强制类型转换,但是可以通过共同的接口来转换(Foo)。

二、代理之Cglib实现

接下来我们来看Cglib的代理。

我们通过它的EnHancer类的create方法来创建代理,与jdk代理不同的是,它是基于继承来创建代理类的,需要指定它的父类。

首先我们创建一个Target类,里面定义一个foo方法,这次不需要去实现某个接口:

public class CglibProxy {

    static class Target{
        public void foo(){
            System.out.println("Target里的foo方法");
        }
    }

}

我们来看看EnHancer类的其中一个create方法:

public static Object create(Class type, Callback callback){
	......
}

第一个参数代表需要代理的类型,充当父类,第二个Callback定义代理类要做的事,我们一般使用它的一个子接口,叫做MethodInterceptor

public static void main(String[] args) {
    Enhancer.create(Target.class, new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            return null;
        }
    });
}

Object obj表代理本身,Method method用来执行目标类方法,Object[] args代表方法参数,后面的MethodProxy proxy也可以看着目标类方法,但是有些差异,后续再聊。

这样看来,它其实和InvocationHandler非常像,那我们如法炮制,new一个Target目标对象,添加前后增强逻辑,反射调用目标方法,添加返回结果:

public static void main(String[] args) {
    Target target = new Target();//代理目标

    Target proxy = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("before 前置增强");
            Object result = method.invoke(target, args);
            System.out.println("after 后置增强");
            return result;
        }
    });
    
    proxy.foo();
}

这里我们用Target类型做强转,是允许的,因为代理类就是Target类的子类,是父子关系,不是兄弟关系。

运行结果:

请添加图片描述

这里我们思考一下

  • Target类可以加final修饰吗?

肯定不行,因为加了final就不能被继承了,Cglib这种基于继承的方式代理就没用了。

  • 那foo方法可以加final修饰吗?

也不行,因为这种继承的方式其实就是去重写foo方法,把它变成增强后的方法,如果加了final修饰,方法就不能被重写,那就不能被增强。

我们在foo方法加上final修饰:

static class Target{
    public final void foo(){
        System.out.println("Target里的foo方法");
    }
}

在运行一下main:

请添加图片描述

增强不见了,因为是调用父类Target的原方法。

  • 那jdk的代理,目标类可以加final吗?

可以,因为它是基于接口的方式去创建代理类,接口是可以被实现的,不会受Target的final修饰受影响。

接下来我们来看看MethodInterceptor.intercept的第四个参数:MethodProxy proxy,它可以避免用反射的方式去调用目标方法。

    public static void main(String[] args) {
        Target target = new Target();//代理目标

        Target proxy = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before 前置增强");
//                Object result = method.invoke(target, args);//反射调用
                //MethodProxy可以避免反射调用
                Object result = proxy.invoke(target, args); //内部不是反射调用
                System.out.println("after 后置增强");
                return result;
            }
        });

        proxy.foo();
    }

我们看到这次调用的是MethodProxy的invoke方法,名字一样,参数也一样,但是内部不是反射调用!

MethodProxy还有一个方法是invokeSuper,传进去的参数obj,也就是代理类自己,也可以不反射调用实现目标方法,这下连目标类也不需要创建了。

    public static void main(String[] args) {
//        Target target = new Target();//代理目标

        Target proxy = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before 前置增强");
//                Object result = method.invoke(target, args);//反射调用
                //MethodProxy可以避免反射调用
//                Object result = proxy.invoke(target, args); //内部不是反射调用
                Object result = proxy.invokeSuper(obj, args);// new
                System.out.println("after 后置增强");
                return result;
            }
        });

        proxy.foo();
    }

spring用的是proxy.invoke(target, args),没有使用invokeSuper。

后续文章我们再深入学习里边的精髓…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值