黑马程序员技术交流社区

标题: 【成都校区】动态代理入门讲解 [打印本页]

作者: 小蜀哥哥    时间: 2019-5-30 12:02
标题: 【成都校区】动态代理入门讲解
本帖最后由 小蜀哥哥 于 2019-5-30 13:12 编辑

动态代理入门讲解
前言
动态代理作为23种设计模式之一,发挥着很重要的作用,在各大程序框架中,底层都用到了动态代理的设计模式,那么问题来了,到底什么是动态代理呢?接下来我们来详细的讲解一番。
代理
首先,我们先不去考虑动态,我们先来说一说代理,面向对象程序设计中的代理,是指为其他对象提供一种代理以控制对这个对象的访问。用大白话讲,就是说如果我们想要去引用一个对象,第一种方案,直接去引用即可。第二种方案,我们不去引用这个对象,而是找一个代理对象,这个代理对象可以在我们和目标对象之间起到中介作用,它起到的作用和目标对象起到的作用是一样的,甚至它还可以去掉一些我们不能看到的内容,当然也可以增添一些我们可以看到的一些额外的内容。在程序设计中,代理分为很多类,但是我们在这里主要讲解的是动态代理,但是如果我们想要弄明白动态代理,首先要先弄明白什么是静态代理。那我们接下来就挨个来讲解一下这些看似比较抽象的知识点。
静态代理
我们就以现实生活中的例子来举例说明。比方说,冰淇淋店,大家都应该吃过冰激凌,也见过冰激凌是怎么做成的,冰激凌都是由机器直接做出成型的,而且不同口味的冰激凌由不同的冰激凌机器来制作。也就是说,可能会有制作草莓味冰激凌的机器,也有制作提拉米苏味冰激凌的机器等等。好,接下来,我们把这个场景用Java语言来描述一下。
[Java] 纯文本查看 复制代码
interface IcecreamMachine {     //做冰激凌的通用机器 
   public void makeIcecream();
}
​class StrawberryMachine implements IcecreamMachine {
   //草莓味冰激凌专用机器
   public void makeIcecream() {
             System.out.println("制作草莓味冰激凌");
   }}
​class TiramisuMachine implements IcecreamMachine {
   //提拉米苏味冰激凌专用机器
    public void makeIcecream() {
              System.out.println("制作提拉米苏味冰激凌");
    }}​
public class IcecreamDemo {
    public static void main(String[] args) {
          IcecreamMachine icecreamMachine = new StrawberryMachine();
          icecreamMachine.makeIcecream();
          //制作草莓味冰激凌
          icecreamMachine = new TiramisuMachine();  
          icecreamMachine.makeIcecream();
          //制作提拉米苏味冰激凌   
     }}

上面的代码就是描述了现实生活中的冰激凌使用不同的机器制作出不同口味的冰激凌,可以抽取出一个通用的IcecreamMachine接口,以及实现了该接口的StrawberryMachine和TiramisuMachine的实现类。好,接下来提出一个问题,在我们现实生活中,年轻人的口味越来越多,越来越古怪,如果只有这两种的话,那生意可能会不太好做,那怎么办呢,所以有的时候,冰激凌店是需要变换一些口味的,比如说,在草莓冰激凌上涂上一层奥利奥,或者说在提拉米苏冰激凌上涂上一层蓝莓酱等等。如果这样的话,我们该如何用代码来实现呢?我们知道,这种在原有口味上涂抹东西也不是长久之计,一般都是临时的一段时间,所以如果为了应付现在的需求而专门再买一台冰激凌机器不太好,那我们可以这么办,先制作出草莓口味的冰激凌,再人工的将奥利奥撒到草莓味冰激凌上不就可以了吗,接下来我们来看代码的实现。
[Java] 纯文本查看 复制代码
class OreoIcecreamProxy implements IcecreamMachine { 
      private IcecreamMachine icecreamMachine;
      public OreoIcecreamProxy(IcecreamMachine icecreamMachine) {
                 this.icecreamMachine = icecreamMachine;
       }
       public void makeIcecream() {
                 icecreamMachine.makeIcecream();
                System.out.println("撒上奥利奥");
        }
}
​public class IcecreamDemo {
       public static void main(String[] args) {
               StrawberryMachine strawberryMachine = new StrawberryMachine();
               OreoIcecreamProxy oreoIcecreamProxy = new OreoIcecreamProxy(strawberryMachine);
               oreoIcecreamProxy.makeIcecream(); //制作草莓面包,撒上奥利奥
       }
}

OK,我们制作出我们想要的冰激凌了。通过这种设计,我们就不需要在原有的StrawberryMachine类上面修改内容,而定义一个这样的代理类OreoIcecreamProxy,就可以帮助我们实现功能,这种设计就被称为静态代理
动态代理
按照上面的现实场景需求,我们继续来说,如果一个冰激凌店只卖冰激凌,是肯定赚不了钱的,所以小店又推出了新的产品面包甜点,同冰激凌一样,根据不同口味的面包,会有不同的面包机来做,那我们就用代码来体现一下。
[Java] 纯文本查看 复制代码
interface BreadMachine { 
       //做面包的通用机器
       public void makeBread();
}
​class ButterBreadMachine implements BreadMachine {
       //奶油面包专用机器
       public void makeBread() {
                 System.out.println("制作奶油面包");
        }}​
class OrmosiaBreadMachine implements BreadMachine {
        //红豆面包专用机器
        public void makeBread() {
                  System.out.println("制作红豆面包");
        }}​
class ChocolateBreadProxy implements BreadMachine {
       private BreadMachine breadMachine;
       public ChocolateBreadProxy(BreadMachine breadMachine) {
                  this.breadMachine = breadMachine;
        }
       public void makeBread() {
                 breadMachine.makeBread();
                 System.out.println("涂上一层巧克力");
       }
}
​public class BreadDemo {
       public static void main(String[] args) {
                 ButterBreadMachine butterBreadMachine = new ButterBreadMachine();
                 butterBreadMachine.makeBread();
                 //制作奶油面包
                 OrmosiaBreadMachine ormosiaBreadMachine = new OrmosiaBreadMachine();
                 ormosiaBreadMachine.makeBread();
                 //制作红豆面包
                 ChocolateBreadProxy chocolateBreadProxy = new ChocolateBreadProxy(butterBreadMachine);
                 chocolateBreadProxy.makeBread();
       }
}

好,通过用上面的代码,已经将新产品做的很好了。但是为了将店面发展壮大,未来,小店还会有很多个产品,那问题来了,今天有客人想将巧克力涂在面包上,我们可以使用之前写好的代理类,那明天客人想将巧克力涂在冰激凌上怎么办,那未来出来很多种新产品,客人想将巧克力涂在那些新产品上怎么办,我们总不能要写N多个代理类吧,显然这样做的不可取的,这也正是静态代理的弊端,只可以给一种产品提供代理,就像只能给面包涂巧克力,不能给冰激凌涂巧克力一样。那咋办?我们想一想,可不可以写一个巧克力代理类,可以实现给不同种类的产品都涂上巧克力呢,好,接下来我们来看代码。
[Java] 纯文本查看 复制代码
class ChocolateHandler implements InvocationHandler {
        private Object obj;
        public ChocolateHandler(Object obj) {
                this.obj = obj;
         }
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object returnValue = method.invoke(obj, args);
                System.out.println("涂上一层巧克力");
                 return returnValue;
         }
}
​public class Demo {
          public static void main(String[] args) {
                   ButterBreadMachine butterBreadMachine = new ButterBreadMachine();
                   ChocolateHandler chocolateHandler = new ChocolateHandler(butterBreadMachine);
                    BreadMachine breadMachine =(BreadMachine)Proxy.newProxyInstance(butterBreadMachine.getClass().getClassLoader(),butterBreadMachine.getClass().getInterfaces(), chocolateHandler);
                    breadMachine.makeBread();
                    //制作奶油面包,涂上一层巧克力
                   StrawberryMachine strawberryMachine = new StrawberryMachine();
                   chocolateHandler = new ChocolateHandler(strawberryMachine);
                   IcecreamMachine icecreamMachine = (IcecreamMachine)Proxy.newProxyInstance(strawberryMachine.getClass().getClassLoader(),strawberryMachine.getClass().getInterfaces(), chocolateHandler);
                  icecreamMachine.makeIcecream();
          }
}

按照上面这种写法,就可以只写一个代理,然后给不同种类提供代理服务了。这种设计就被称为动态代理。InvocationHandler:是Java给我们提供的一个动态代理接口。Proxy.newProxyInstance:是固定的写法,返回的就是一个代理类对象,但是接受返回值的类型必须得是代理类对象和目标类对象共同实现的接口类型。Proxy.newProxyInstance(参数A, 参数B, 参数C):参数A是指代理类的类加载器对象,由于代理类的类加载器对象和目标类的类加载器对象是一个,所以可以传递目标类的类加载器对象。参数B是指代理类所实现的接口都有哪些,因为想要按照我们这种写法实现动态代理,那代理类也要实现目标类实现的接口,它们实现的接口是一样的,所以可以传递目标类实现的接口。参数C是指我们之前自己定义的那个实现了InvocationHandler的实现类对象。ChocolateHandler类里的invoke():因为实现了InvocationHandler接口,所以重写的方法,固定格式。invoke(Object proxy, Method method, Object[] args):第一个参数是代理类对象,没有什么用处。第二个参数是代理类对象调用方法时,将封装变成Method类型传递进来。第三个参数就是代理类调用方法时传递的方法所需要的参数。
总结
此文章只是作为动态代理入门的讲解,因为很多刚学习动态代理的兄弟们在理解动态代理上有一定的难度,所以希望我举的这个例子能够帮助大家更好更简单易懂的让兄弟们理解动态代理。











欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2