美文网首页Java设计模式
Java代理模式中的静态代理和动态代理

Java代理模式中的静态代理和动态代理

作者: Geeny | 来源:发表于2018-09-09 16:16 被阅读3次

    代理模式

    假如一个A类具备做甲事情的能力,我们希望它做甲事情之前或之后做多一些操作(比如记录日志),同时又不想去修改A类的结构(因为A类可能有很多其它引用,如果一改A类,所有地方都变动了)。这时候怎么办?

    针对这个情况,我们可以在A类外面包多一个类(叫它B类吧),B类同样实现甲方法,B类的甲方法里调用了A类的甲方法,除此之外,B类的甲方法中还额外加多一些操作。随后,调用B类的甲方法,就等同于调用A类的甲方法,同时也执行了我们想要扩展的操作。

    一个比较形象的比喻,如果A类是明星,B类就是经纪人,我们想叫明星来演出,跟经纪人说就能实现明星演出这件事,但经纪人在安排明星演出这件事中,可以做其它一些额外的事情,比如出场费商讨、安保工作或行程安排等。经纪人就是明星的代理人,这里的B类就是A类的代理类。

    这就是代理模式的概念。来个代码Demo。

    定义一个明星接口StarObject

    public interface StarInterface {
    
        public void show(String name);
        
    }
    

    Star实现StarInterface接口,实现一些业务逻辑操作

    public class Star implements StarInterface {
        
        @Override
        public void show(String name) {
            System.out.println("我是明星" + name + ",我在演出。");
        }
    
    }
    

    实现经纪人类,即代理类

    public class Broker implements StarInterface {
    
        private StarInterface star;
        
        
        public Broker(StarInterface star) {
            this.star = star;
        }
        
        @Override
        public void show(String name) {
            System.out.println("我是经纪人,现在去叫" + name + "来演出。");
            star.show(name);
            System.out.println("我是经纪人," + name + "演出结束,谢谢大家。");
        }
    
    }
    

    运行代码

    StarInterface star = new Broker(new Star());
    star.show("Leslie Zhang");
    

    得到结果:

    我是经纪人,现在去叫Leslie Zhang来演出。
    我是明星Leslie Zhang,我在演出。
    我是经纪人,Leslie Zhang演出结束,谢谢大家。
    

    这就是代理模式了。但注意,上面所述的是指静态代理模式。有静态就会有动态,那动态代理是什么?

    动态代理

    上面的方式对于一个类扩展出一个代理类的情况来说,是可以解决问题了。但如果之后对“明星”类要实现更多的“经纪人”类时,我们都需要再写多个代理类,这会变得麻烦和复杂。JDK为此给出了动态代理的方案。

    先看Demo。

    实现一个动态代理类

    public class BrokerDynamic implements InvocationHandler {
    
        private Object star;
        
        public BrokerDynamic(Object star) {
            this.star = star;  // 传入实现类。
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            
            System.out.println("这里是经纪人在明星演出前的前置处理。");
            
            Object result = method.invoke(star, args);  // 得到并返回方法值
            
            System.out.println("这里是经纪人在明星演出后的后置处理。");
            
            return  result;
        }
        
        // 值得注意的是,这个动态代理类中,对象成员、方法调用返回值类型、方法参数都是Object类型
        // 这表示这个代理类能适用多种类型。
        // 在使用时通过强制转换类型就可以得到指定的对象了。
    }
    

    运行代码

    StarInterface star = (StarInterface) Proxy.newProxyInstance(StarInterface.class.getClassLoader()
                            ,new Class[] {StarInterface.class}
                            , new BrokerDynamic(new Star()));
    
    star.show("Leslie Zhang");
    
    // 在调用star的show()方法时,代理类会转为去调用BrokerDynamic类invoke方法,
    // invoke方法的参数proxy就是发起调用的代理类star实例,
    // method就是指show()方法,
    // args就是调用show()方法时传的参数"Leslie Zhang"
    

    得到运行结果

    这里是经纪人在明星演出前的前置处理。
    我是明星Leslie Zhang,我在演出。
    这里是经纪人在明星演出后的后置处理。
    

    Proxy.newProxyInstanc是JDK提供的得到一个动态代理类的静态方法。
    第一个参数为类加载器,我们这里使用StarInterface接口,就直接得到它对应的类加载器;
    第二个参数是指实类所实现的接口列表,我们这里只实现StarInterface接口,故只传一个;
    第三个参数为一个实现了调用处理器InvocationHandler接口的实例。

    Proxy.newProxyInstanc返回对应类型的代理类,因为返回值为Object类型,所以需要强转一下。

    得到代理类后,调用代理类对应的方法,这一个代理类就会自动地调用InvocationHandler实例的invoke方法。这样,我们就可以在invoke里面实现一些扩展操作了。

    这就是动态代理的基本概念。

    也许你还有这样的问题

    1. 上面的例子倒是很动态,其它的类一传进来都可以生成它对应的代理类,但前置处理和后置处理的操作写死了,并不灵活。
    2. InvocatioHandler中的invoke方法第一个参数proxy就是代理类实例了,在BrokerDynamic类中invoke方法里,直接调用proxy的方法不就可以了,为什么在构造方法中还要另外存一个star实例。

    第一个问题确实存在一点局限,或许我们可以用两个抽象方法在BrokerDynamic类的invoke方法中分别作为前置和后置处理,之后再创建子类实类把这两个“坑”补上,便可实现前置后置处理的不同需求。

    第二个问题中,形参proxy是指代理类(经纪人),即调用show方法时的代理类实例本身,不是原来的实类(明星),如果我们在invoke方法中,把Object result = method.invoke(star, args); 中的star换为proxy中进行调用,就会再执行一次invoke方法,invoke方法里又是proxy在调用show方法,会形成递归循环。所以,在BrokerDynamic里,我们还是需要另存一个原始实类(明星),在invoke方法中,调用原始实类对应的方法。

    相关文章

      网友评论

        本文标题:Java代理模式中的静态代理和动态代理

        本文链接:https://www.haomeiwen.com/subject/ddhdgftx.html