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


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

    动态代理有两种写法,一种匿名内部类实现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的,因此需要进行类型转换,而且只能转换成接口类型,多态的方式进行变量引用。之后通过变量调用方法就可以

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

  • 相关阅读:
    shell 格式化输出
    Linux tar 修改终端命令
    uniqu 用法
    HashMap按照value值进行排序
    汇编语言系列教程之基础入门 (一)
    Linux权限管理
    linux用户管理
    vim的tab键设定
    HTTP请求(GET与POST区别)和响应
    JS eval()
  • 原文地址:https://www.cnblogs.com/skyvalley/p/14007627.html
Copyright © 2020-2023  润新知