静态代理模式(接口的应用)与动态代理模式(反射的应用)

1 代理模式概述:

        代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理,以控制对这个对象的访问。代理模式的原理是将真实类的功能封装在被代理类中,使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

        最基本的方式就是创建一个代理类,代理类实现和真实类相同的接口,并且在代理类中引用真实类的实例。这样就可以在不修改真实类代码的情况下,通过代理类来控制对真实类的访问。代理类还可以在调用真实类的方法前后添加一些额外的逻辑,来实现对真实类的访问控制、缓存、日志记录等功能。      

2 静态代理

2.1 原理及应用介绍

        通俗的讲,静态代理模式的初衷,就是为了让代码更好的进行解耦。将核心代码中,稳定不易变动的那一部分代码设为被代理类。其外围代码,因需求的不同需要不断更改代码与其适配的那部分代码写入代理类中,从而完成被代理类与代理类的解耦。为了让读者更方便理解静态代理模式,在次我举两个经典的例子辅助理解:

        明星和经纪人:明星负责演戏,而他的经纪人就是他的代理,经纪人负责与制片公司谈演戏前后的相关事宜,比如合同签订,开具发票,结清尾款等工作,因为不同的制片公司,其流程不同,经纪人去适配各种不同的制片公司的流程并与其完成对接工作。而明星只需要专注于演戏本身即可,根据档期进行出演,无需因其他事情而费心劳力。

        房东和房屋中介:房东负责提供房子,而他的房屋中介就是他的代理,房屋中介负责与租客进行谈租赁合同的签订,押金的交纳,退房检查,押金退款等相关工作。而房东只需要提供房子即可,租房前后等工作由房屋中介执行,因为不同的租客其偏好不同,有的租客对交通便利比较看重,有的租客对租金比较敏感,有的租客喜欢拧包入住,这些繁琐经常变更的事情,就由代理进行对接完成,而房东只需要提供房子即可。

2.2 实现静态代理

        比如我们有一个生产耐克的工厂类,这个类专门负责生产耐克产品的核心工序,它作为真实类,即被代理类。为了生产一件完整的耐克产品,除了核心工序外,还需要进行前后很多繁琐的辅组工序,而且这些工序会因为工厂开设的国家和地区不同,为了符合当地的法规进行变更,并且随着时间推移,这些非核心工序还需要不断优化,因此把这些工序作为代理类进行封装。具体构建步骤如下。

2.2.1 构建接口

        创建一个代理类和被代理类的公共接口,接口中拥有一个抽象方法

package stateProxy;

interface InterfaceFactory {
    void produceNick();
}

2.2.2 创建真实类(被代理类)

        创建真实类,并实现接口的抽象方法,这里的抽象方法的实现,即为那部分相对稳定的代码,不易改变的模块。

package stateProxy;

//被代理类
public class NickFactory implements InterfaceFactory {
    public void produceNick() {
        System.out.println("*********生产Nick产品核心工艺流程**********");
    }
}

2.2.3 创建代理类

        创建代理类相对比较复杂,同样代理类需要实现接口的抽象方法,同时,在代理类中创建引用真实类的变量并设为私有类型,将真实类的实例作为入参传入构造器内。这样就可以在不修改真实类代码的情况下,通过真实类实例来调用其中的方法及属性,并在调用真实类的方法前后添加一些额外的逻辑,作为代理类的接口抽象方法的实现。当调用代理类的接口方法时,除了会运行被代理类的同名方法,还会运行代理类接口方法中额外的逻辑和方法。

package stateProxy;

// 代理类
public class NickFactoryPoxy implements InterfaceFactory {
    // 1 创建引用真实类的变量,并设为私有类型
    private NickFactory nickProduce;

    // 2 将真实类的实例作为入参传入构造器内
    NickFactoryPoxy(NickFactory nickProduce) {
        this.nickProduce = nickProduce;
    }

    // 3 添加额外的逻辑及方法
    private void  preProduction(){
        System.out.println("-----------给料系统,供电系统,照明系统----------");
    }

    private void  afterProduction(){
        System.out.println("------------清理废料,处理污水,关闭电源----------");
    }

    // 4 代理类中实现接口抽象方法,通过真实类实例来调用其中的方法及属性
    public void produceNick() {
        preProduction();
        nickProduce.produceNick();
        afterProduction();
    }
}

2.2.4 代理类实例化测试

        通过实例化一个代理类,并运行代理类中的接口方法,可以看到,代理类的方法不仅实现了代理类中额外的逻辑及方法,还实现了被代理类(真实类)中的接口方法。这样就有效的完成了真实类和代理类之间的解耦,如果核心工序之外的工序有变更,只需修改代理类中的方法即可,真实类被封装好了,无需更改。

package stateProxy;

public class NickPoxyFactoryTest {
    public static void main(String[] args) {
        // 创建真实类对象
        NickFactery nick = new NickFactery();
        // 创建代理类对象
        NickFactoryPoxy nickPoxy = new NickFactoryPoxy(nick);
        // 通过代理类的对象调用代理类的方法,这个方法封装了真实类对象nick的同名方法
        nickPoxy.produceNick();
    }
}

        测试结果显示,静态代理对象调用接口中的方法,成功的调用了被代理对象的同名方法,并且还运行了静态代理接口方法中额外的逻辑和方法。

3 动态代理

 3.1 原理及应用介绍       

        通过上述静态代理的创建,相信读者对代理模式有了更为深刻的认识,大家有没有想过,上述的静态代理是不是有很多局限性啊,比如它只能代理耐克工厂真实类,只能为一个接口服务,如果换一个真实类,就无法完成代理工作了,因为他的构造器入参类型已经决定了它能够代理什么类,实现接口的类型决定了它能为哪个接口服务。

        在程序运行前,静态代理类就已经确定了被代理类和所实现的接口,因为构建代理类时已经在代码中写死了被代理类和实现接口,所以在编译阶段也就确定了被代理类和实现接口。为了扩充代理类的应用性,世界上有没有一种万机油的代理类,无论什么样的真实类都可以被它所代理,无论什么样的接口都可以被它所实现呢?如果真的存在,那么这个万机油的代理类在编译时(即创建它的代码里)就不能写死代理类和接口,而是在运行代码时,动态获取真实类和接口,然后再决定代理类所要代理的类以及要实现的接口。

        综上所述:动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

        那么动态代理有什么应用呢,其主要应用就是AOP面向切面编程:

        其AOP面向切面编程的初衷也是为了代码能够更好的进行解耦,和静态代理不同的是,通用不易更改的代码不是在被代理方法中,而是在代理方法中,被代理类的方法恰恰是经常变更和替换的方法。

3.2 实现动态代理

        还是以产品生产为例,如果有很多个工厂生产产品,比如耐克、李宁,他们都具备核心生产产品的工序,但在生产产品前的 "成立公司,创建品牌,设计logo",生产完产品后的 "物流运输,销售渠道,财务回款" 等相关事宜其实都是通用的,任何一家公司都需要商标注册、物流运输、财务报税等工作,而且无论是生产什么产品的公司,这部分工作都是固定流程化一模一样不变的。所以动态代理中,动态代理所增加的方法反而是不容易变动的固定通用方法,这一点和静态代理是恰恰相反的。

3.2.1 构建接口

和静态代理一样,构建同样的工厂类接口

package dynamicProxy;

public interface InterfaceFactory {
    void produce();
}

3.2.2 创建真实类(被代理类)

我们在这里创建两个类,一个耐克工厂类,一个李宁工厂类

package dynamicProxy;

class LiningFactory implements InterfaceFactory{
    @Override
    public void produce() {
        System.out.println("---------生产Lining产品核心工艺流程-----------");
    }
}

class NickFactory implements InterfaceFactory{
    @Override
    public void produce() {
        System.out.println("---------生产Nick产品核心工艺流程-------------");
    }
}

3.2.3 创建通用方法类

package dynamicProxy;

class factoryUtil {
    void method1(){
        System.out.println("********成立公司,创建品牌,设计logo**********");
    }

    void method2(){
        System.out.println("********物流运输,销售渠道,财务回款***********");
    }
}

3.2.4 创建代理类

        Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。通过Proxy.newProxyInstance方法,将被代理对象(真实类对象)作为入参传入这个方法中,可以得到一个代理类的对象。

        实现InvocationHandler接口则可提供代理类对象调用被代理对象中同名方法的能力。创建代理类对象时,调用了MyInvocationHandler对象的bind()方法,MyInvocationHandler类实现了InvocationHandler接口,重写了invoke()方法。当执行动态代理对象里的接口方法时,实际上会替换成调用MyInvocationHandler对象的invoke()方法,而invoke()方法中的method.invoke()方法即为被代理对象的同名方法。

package dynamicProxy;

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

/*
* 1 根据加载到内存中的被代理类,动态的创建一个代理类及对象
* */
class proxyFactory {
    // 调用此方法,通过被代理类的对象 返回一个代理类的对象
    static Object getProxyInstance(Object obj){  // obj:被代理类的对象
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}

/*
 * 2 当通过代理类的对象调用方法时,动态的去调用被代理类中的同名方法。
 * */
class MyInvocationHandler implements InvocationHandler{
    private Object obj; // 赋值时,也需要使用真实类(被代理类)的对象进行赋值

    void bind(Object obj) {
        this.obj = obj;
    }

    // 当通过代理类的对象,调用方法 a 时,就会自动的调用如下的方法: invoke()
    // 将被代理类要执行的方法 a 的功能声明在 invoke() 中
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        // method: 即为代理类对象调用的方法,此方法也就作为了真实类(被代理类)对象要调用的方法
        // obj: 被代理类的对象
        // 上述方法的返回值就作为当前类中的invoke()的返回值
        factoryUtil factoryUtil = new factoryUtil();
        factoryUtil.method1();
        Object returnValue = method.invoke(obj, args);
        factoryUtil.method2();
        return returnValue;
    }
}

3.2.5 代理类实例化测试

        分别创建耐克和李宁类的实例化对象,然后作为入参通过proxyFactory类的静态方法getProxyInstance获得代理类对象。

        在使用代理对象时,请确保将其转换为接口类型而不是具体的真实类(被代理类)的类型。通过代理类对象调用produce()方法时,会自动的调用invoke()方法,而invoke()方法中执行的method.invoke()方法,即是被代理对象中的同名方法produce(),面向切面编程,即是在invoke()方法中,写入通用的方法,通过动态代理更换代理对象(入参),从而实现替换invoke()方法中的method.invoke()方法的目的。

package dynamicProxy;

class proxyFactoryTest {
    public static void main(String[] args) {
        LiningFactory liningFactory = new LiningFactory();
        NickFactory nickFactory = new NickFactory();
        // liningProxyInstance 是 代理类的对象
        // 在使用代理对象时,请确保将其转换为接口类型而不是具体的真实类(被代理类)的类型。
        InterfaceFactory liningProxyInstance = (InterfaceFactory) proxyFactory.getProxyInstance(nickFactory);
        // 当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        liningProxyInstance.produce();

        System.out.println("\n\n");

        InterfaceFactory nickProxyInstance = (InterfaceFactory)proxyFactory.getProxyInstance(liningFactory);
        nickProxyInstance.produce();

    }
}

         测试运行结果如下:通过传入不同的真实类(被代理类)对象,最后通过代理对象执行接口中的抽象方法,实现了被代理类对象的同名方法调用,以及代理类invoke()方法中额外的逻辑和方法的调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值