【朝花夕拾】写一个动态代理类

什么是动态代理:不管我们的对象是什么,我都可以通过这样一个类去生成一个相应的代理对象,帮助开发者去完成功能,和目标对象(被代理者)没有任何关系

动态代理有两种写法,一种匿名内部类实现InvocationHandler,一种写一个处理器类继承InvocationHandler,本文选用第一种

准备步骤:

1.创建有一个接口,里面定义了一个或多个抽象方法

package com.atguigu.spring.proxy;

public interface Math {
    int add(int a,int b);
    int sub(int a,int b);
    int mul(int a,int b);
    int div(int a,int b);
}

2.创建一个实现类(即我们的被代理类),实现这个接口,并重写了方法

package com.atguigu.spring.proxy;

public class MathImpl implements Math {
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int sub(int a, int b) {
        return a-b;
    }

    @Override
    public int mul(int a, int b) {
        return a*b;
    }

    @Override
    public int div(int a, int b) {
        return a/b;
    }
}

3.创建一个日志log类

package com.atguigu.spring.proxy;

public class MyLogger {
    public static void before(String methodName,String args){
        System.out.println("method:"+methodName+",arguments:"+args);
    }

    public static void after(String methodName,Object result){
        System.out.println("method:"+methodName+",result:"+result);
    }
}

开始写代理类步骤:

1.创建一个能够获取代理类的工具类 

思路分析:动态代理的目的是获取一个代理类来完成工作

获取代理类java有一个通用的方法就是使用Proxy类的静态方法newProxyInstance

  该方法返回一个代理类,所以是有返回值的,一般为Object,和本文首行的动态代理的定义相呼应,但是需要传入三个参数

    参数1:类加载器  参数2:被代理者接口的Class对象数组 参数3:一个代理类处理器

    参数1分析:我们要加载一个类需要类加载器,而代理类就是我们需要加载的类,所以我们需要传入一个类加载器

         那么这个类加载器传什么比较好,因为工具类也是由类加载器加载的,所以我们直接this.getClass().getClassLoader()来获取类加载器,

         其实这个类加载器属于应用程序类加载器,如果你使用工具类中存在的被代理者对象的getClass().getClassLoader()也是一样的效果

      参数2分析:代理类需要完成的是和被代理者相同的事,只不过加了一些其他的业务逻辑,那么被代理者继承了接口,重写了方法,同理,我代理类

          也需要继承它继承过的接口,这样被代理者重写过的方法,代理类也需要重写,就能达到调用相同业务逻辑的目的

    参数3分析:一个代理类处理器,这个处理器就是帮助我们完成业务逻辑的,我们一般使用匿名内部类来完成传参,匿名内部类中需要重写一个invoke

  方法,该方法有三个形参,分别会传入代理类对象,重写的方法对象和方法的参数。第一个参数是代理类对象,这个很好理解,因为代理类继承了接口,

  那么肯定也需要重写接口方法,并且我们需要调用代理类重写的方法,所以传入代理类这个对象是必要的。并且在这里是底层自己传入,

  因为我们创建的是代理类是匿名内部类,开发者是无法传过去的,是底层自己传过去。第二个可能会疑问为什么这里的方法对象不是数组呢,因为

  我们调用一个方法是一个个调用的,不能一下子把所有方法全调用了,就比如使用方法是mathImpl.add(),总不能mathimpl.add[]()或者

  mathimpl.add().sub().div()这样的传。第三个参数就是接口方法中可能需要传递的参数了。invoke()方法的形参传完之后,invoke()方法的方法体

  就是进行方法调用,也就是实现的是代理者对接口的方法重写,这里是反射的知识,即method.invoke(被调用的方法所属的对象,参数),代理者的接

  口方法重写里面调用被代理者写好的业务逻辑,有返回值或者没返回值都return一下,保证和被代理类的返回结果一样

  回归以上,在上面这个方法调用获取代理类的时候,我们有一个点没有提到,那就是被代理者,在上面的newProxyInstance()的参数二和method.invoke()里面的参数

  都会用到被代理者的对象,这也说明了工具类中需要持有被代理者的对象,用于调用它的方法,因为动态代理的核心业务逻辑仍是被代理类的方法完成的。所以工具

  类中需要定义一个被代理对象的属性,并写出有参构造,才能在创建代理工具类的同时赋值一个被代理者对象

代码如下

package com.atguigu.spring.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyUtil {
    //注意一下这里写的是MathImpl,其实应该写Object,因为是动态代理,对所有的对象都适用
    private MathImpl mathImpl;

    public ProxyUtil(MathImpl mathImpl) {
        this.mathImpl = mathImpl;
    }


    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), mathImpl.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                MyLogger.before(method.getName(), Arrays.toString(args));
                Object result = method.invoke(mathImpl, args);
                MyLogger.after(method.getName(),result);
                return result;
            }
        });
    }
}

2.测试类

package com.atguigu.spring.proxy;

public class ProxyTest {
    public static void main(String[] args) {

        ProxyUtil proxyUtil = new ProxyUtil(new MathImpl());
        Math proxy = (Math)proxyUtil.getProxy();
        System.out.println(proxy.add(1,1));
    }
}

代码分析,首先创建了一个代理类对象,给对象中的属性:被代理者对象  赋值,之后调用获取代理类对象的方法,这个代理类对象是实现了和被代理类对象MathImpl相同的接口Math的,因此需要进行类型转换,而且只能转换成接口类型,多态的方式进行变量引用。之后通过变量调用方法就可以

原理如下,看上去有些乱,其实理解了很简单,画的不好见谅

原文地址:https://www.cnblogs.com/skyvalley/p/14007627.html