探究常用设计模式(六)代理模式

代理模式是很多框架中非常重要且常用的设计模式,分为静态代理和动态代理,下面我们从一个简单的例子一起走进代理模式。

需求:现在需要实现加减乘除功能

新增一个功能接口

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

实现功能

class MyCalculator implements Calculator{

        @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;
        }
    }

客户端

public class negtive {
        public static void main(String[] args) {
           Calculator c = new MyCalculator();
           System.out.println(c.add(2, 3));
           System.out.println(c.sub(10, 3));
           System.out.println(c.mul(8, 3));
           System.out.println(c.div(99, 3));
    }
}

现在增加日志系统

```java
class MyCalculator implements Calculator{

        @Override
        public int add(int a, int b) {
            System.out.println("初始值为:a=" + " " + a + ",b= " + b);
            int r = a + b;
            System.out.println("结果为:" + r);
            return r;
        }
    }

可见,代码的侵入性很强,严重违反开闭原则,并不适合真正的开发。那如何解决这个问题呢?可能有些同学想到策略模式,开的多套不同的策略以此解决问题。确实方法是可行的,但是不是太过于复杂了呢?下面我们来介绍一种高效的方式------动态代理。

什么是动态代理呢?实现动态代理有两种方式:JDK动态代理和CGlib动态代理。
JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGlib动态代理利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
两者各自优势不同,如感兴趣请读者自行查阅资料。

我们这里探索JDK自有一套动态代理API使用方法

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  • 第1个参数:ClassLoader(动态代理的对象的类加载器)
    我们都知道,要实例化一个对象,是需要调用类的构造器的,在程序运行期间第一次调用构造器时,就会引起类的加载,加载类的时候,就是jvm会自动找到类加载器,拿着ClassLoader去加载类的字节码的,只有字节码被加载到了内存中,才能进一步去实例化出类的对象。简单来说,就是只要涉及实例化类的对象,就一定要加载类的字节码,而加载字节码就必须使用类加载器!下面我们使用的是动态代理的api来创建一个类的对象,这是一种不常用的实例化类对象的方式,尽管不常用,但毕竟涉及实例化类的对象,那就一定也需要加载类的字节码,也就一定需要类加载器,所以我们手动把类加载器传入!
  • 第2个参数:Class[](需要调用其方法的接口)
    我们已经知道,下面的代码,是用来实例化一个对象的,实例化对象,就一定是实例化某一个类的对象,问题是,到底是哪个类呢?类在哪里?字节码又在哪里?
    比如我们new String() JVM会加载String.class、new Date() 会加载Date.class,而动态代理的这个类,其实并不在硬盘上,而是在内存中!是由动态代理在内存中"动态生成的!要知道,这个在内存中直接生成的字节码,会去自动实现下面方法中的第2个参数中,所指定的接口!所以,利用动态代理生成的代理对象,就能转成Calculator接口类型!那么这个代理对象就拥有add、sub、mul、div方法!

动态代理JVM原理:在这里插入图片描述

  • 第3个参数:InvocationHandler(调用方法时的处理程序)
    我们已经知道,下面的代理对象porxy所属的类,实现了alculator接口,所以,这个代理对象就拥有add、 sub、 mul、div方法!我们就可以通过代理对象调用add、 sub、 mul、div方法!注意,每次对代理对象任何方法的调用,都不会进入真正的实现方法中。而是统统进入第3个参数的invoke方法中!
	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
  • Object proxy:代理对象
  • Method:代理对象调用的方法
  • Object[] args:调用方法的参数

有了以上JDK代理知识,现在我们进行JDK动态代理将日志融合:

写一个InvocationHandler的实现类。在实现类的内部关联Calculator,用于调用Calculator的方法。

public class MyHandler implements InvocationHandler {

    private Calculator calculator ;
    
    public MyHandler(Calculator c){
        this.calculator = c;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用"+method.getName()+",  参数是"+ Arrays.toString(args));
        // 调用真实对象的真实方法
        int res = (int) method.invoke(calculator, args);
        System.out.println("结果是 "+res);
        return res;
    }
}

客户端

public class Postive {
    public static void main(String[] args) {
        Calculator c = new MyCalculator();

        ClassLoader loader = postive.class.getClassLoader();
        Calculator proxy = (Calculator)Proxy.newProxyInstance(loader, new Class[]{Calculator.class}, new MyHandler(c));

        proxy.add(22,33);
        proxy.sub(55,22);
        proxy.div(10,2);
        proxy.mul(50,5);
    }
}

输出

调用add,  参数是[22, 33]
结果是 55
调用sub,  参数是[55, 22]
结果是 33
调用div,  参数是[10, 2]
结果是 5
调用mul,  参数是[50, 5]
结果是 250

进行封装

class MyProxy{
    public static Object getProxy(Object target) {
        // 获取类加载器
        ClassLoader loader = Postive.class.getClassLoader();
        // 获取代理目标实现接口字节码
        Class[] interfaces = target.getClass().getInterfaces();
        // 生成代理对象
        Object proxy =  Proxy.newProxyInstance(loader, interfaces, new MyHandler(target));
        return proxy;
    }
}

客户端

/*===================客户端=============*/
public class Postive{
    public static void main(String[] args) {
        Calculator c = new MyCalculator();
        Calculator proxy = (Calculator)MyProxy.getProxy(c);
        proxy.add(22,33);
        proxy.sub(55,22);
        proxy.div(10,2);
        proxy.mul(50,5);
    }
}

现在,我们可以已经可以看到代理模式的雏形了。下面我们继续推进。

仔细想想上面的例子,如果我们不想加日志功能呢?
我们把增加的功能抽象出来

编写一个拦截器接口

interface Interceptor {
    void before(Method method, Object[] args);
    void after(Method method, Object res);
}

用户实现接口

class A implements Interceptor {

    @Override
    public void before(Method method, Object[] args) {
        System.out.println(method.getName()+"前置处理"+"=>参数是:"+Arrays.toString(args));
    }

    @Override
    public void after(Method method, Object args) {
        System.out.println(method.getName()+"后置处理结果是=>"+args);
    }
}

修改处理器handler

class MyHandler implements InvocationHandler {

    private Object obj;
    private Interceptor interceptor;
    // 将拦截器传入构造器
    MyHandler(Object obj, Interceptor interceptor) {
        this.obj = obj;
        this.interceptor = interceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置通知
        interceptor.before(method, args);
        // 调用真实对象的真实方法
        int res = (int) method.invoke(obj, args);
        // 后置通知
         interceptor.after(method,res);
        return res;
    }
}

class MyProxy {
    public static Object getProxy(Object target, Interceptor interceptor) {
        // 获取类加载器
        ClassLoader loader = Proxy1.class.getClassLoader();
        // 获取代理目标实现接口字节码
        Class[] interfaces = target.getClass().getInterfaces();
        // 生成代理对象
        Object proxy = Proxy.newProxyInstance(loader, interfaces, new MyHandler(target, interceptor));
        return proxy;
    }
}

客户端

public class Postive{
    public static void main(String[] args) {
        Calculator c = new MyCalculator();
        Calculator proxy = (Calculator) MyProxy.getProxy(c, new A());
        proxy.add(22, 33);
    }
}

测试结果

add前置处理=>参数是:[22, 33]
add后置处理结果是=>55

那如果需求变了呢?比如加法用中文日志,减法用英文日志,乘法用日文。这时我们很容易想到用if else来实现需求。
但这样做会造成大量代码冗余,且违反了单一职责原则,下面我们来尝试解决这个问题。

未完,待更---------------------------------

============================
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值